Skip to content
This repository has been archived by the owner on Jul 12, 2024. It is now read-only.

Ingest the remaining entities and relationships #5

Merged
merged 5 commits into from
Sep 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,28 @@ To set up a JFrog Artifactory account, please follow these steps:
CLIENT_ACCESS_TOKEN="paste the access token value here"
```

6. The final step is to also include the hostname in the `.env` file. Put the
host part of the URL that you received from the email that you received from
JFrog ([this part].jfrog.io).
6. The next step is to include the hostname in the `.env` file. Put the host
part of the URL that you received from the email that you received from JFrog
([this part].jfrog.io).

```bash
CLIENT_ACCESS_TOKEN="paste the access token value here"
CLIENT_NAMESPACE="your organization name"
```

7. The final step is to also include the pipeline access token. Creation process
is similar, you just make sure that the "Pipelines" service is selected on
the modal after you click on the "+ Generate Admin Token" button on the same
Access Tokens page.

![Generate pipeline token](images/generate_pipeline_token.png)

```bash
CLIENT_ACCESS_TOKEN="paste the access token value here"
CLIENT_NAMESPACE="your organization name"
CLIENT_PIPELINE_ACCESS_TOKEN="paste the pipeline token generated from the previous step here"
```

After following the above steps, you should now be able to start contributing to
this integration. The integration will pull in the `CLIENT_ACCESS_TOKEN` and
`CLIENT_NAMESPACE` variables from the `.env` file and use them when making
Expand Down
Binary file added docs/images/generate_pipeline_token.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
41 changes: 29 additions & 12 deletions docs/jupiterone.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,40 @@ https://github.com/JupiterOne/sdk/blob/master/docs/integrations/development.md

The following entities are created:

| Resources | Entity `_type` | Entity `_class` |
| ---------- | ------------------------ | --------------- |
| Account | `artifactory_account` | `Account` |
| Group | `artifactory_group` | `UserGroup` |
| User | `artifactory_user` | `User` |
| Repository | `artifactory_repository` | `Repository` |
| Resources | Entity `_type` | Entity `_class` |
| ------------------ | --------------------------------- | ------------------ |
| Account | `artifactory_account` | `Account` |
| AccessToken | `artifactory_access_token` | `Key`, `AccessKey` |
| Group | `artifactory_group` | `UserGroup` |
| User | `artifactory_user` | `User` |
| RepositoryGroup | `artifactory_repository_group` | `Group` |
| Repository | `artifactory_repository` | `Repository` |
| ArtifactCodeModule | `artifactory_artifact_codemodule` | `CodeModule` |
| Build | `artifactory_build` | `Configuration` |
| Permission | `artifactory_permission` | `AccessPolicy` |
| PipelineSource | `artifactory_pipeline_source` | `CodeRepo` |

### Relationships

The following relationships are created/mapped:

| Source Entity `_type` | Relationship `_class` | Target Entity `_type` |
| --------------------- | --------------------- | ------------------------ |
| `artifactory_account` | **HAS** | `artifactory_group` |
| `artifactory_account` | **HAS** | `artifactory_user` |
| `artifactory_group` | **HAS** | `artifactory_user` |
| `artifactory_account` | **HAS** | `artifactory_repository` |
| Source Entity `_type` | Relationship `_class` | Target Entity `_type` |
| -------------------------- | --------------------- | --------------------------------- |
| `artifactory_account` | **HAS** | `artifactory_access_token` |
| `artifactory_access_token` | **ASSIGNED** | `artifactory_user` |
| `artifactory_account` | **HAS** | `artifactory_group` |
| `artifactory_account` | **HAS** | `artifactory_user` |
| `artifactory_group` | **HAS** | `artifactory_user` |
| `artifactory_account` | **HAS** | `artifactory_repository_group` |
| `artifactory_account` | **HAS** | `artifactory_repository` |
| `artifactory_repository` | **HAS** | `artifactory_artifact_codemodule` |
| `artifactory_build` | **CREATED** | `artifactory_artifact_codemodule` |
| `artifactory_permission` | **ASSIGNED** | `artifactory_user` |
| `artifactory_permission` | **ASSIGNED** | `artifactory_group` |
| `artifactory_permission` | **ALLOWS** | `artifactory_repository` |
| `artifactory_permission` | **ALLOWS** | `artifactory_build` |
| `artifactory_permission` | **ALLOWS** | `artifactory_repository_group` |
| `artifactory_account` | **HAS** | `artifactory_pipeline_source` |

<!--
********************************************************************************
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
},
"dependencies": {
"node-fetch": "^2.6.0",
"node-match-path": "^0.4.4",
"type-fest": "^0.16.0"
}
}
177 changes: 175 additions & 2 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@ import {
ArtifactoryRepository,
ArtifactoryPermission,
ArtifactoryPermissionRef,
ArtifactoryAccessToken,
ArtifactoryBuild,
ArtifactoryBuildRef,
ArtifactoryBuildResponse,
ArtifactoryArtifactRef,
ArtifactoryArtifactResponse,
ArtifactoryBuildArtifactsResponse,
ArtifactoryPipelineSource,
ArtifactoryBuildDetailsResponse,
ArtifactoryAccessTokenResponse,
} from './types';

/**
Expand All @@ -25,11 +35,13 @@ export class APIClient {
private readonly clientNamespace: string;
private readonly clientAccessToken: string;
private readonly clientAdminName: string;
private readonly clientPipelineAccessToken: string;

constructor(readonly config: IntegrationConfig) {
this.clientNamespace = config.clientNamespace;
this.clientAccessToken = config.clientAccessToken;
this.clientAdminName = config.clientAdminName;
this.clientPipelineAccessToken = config.clientPipelineAccessToken;
}

private withBaseUri(path: string): string {
Expand All @@ -38,13 +50,18 @@ export class APIClient {

private async request(
uri: string,
method: 'GET' | 'HEAD' = 'GET',
method: 'GET' | 'HEAD' | 'POST' = 'GET',
body?,
headers = {},
): Promise<Response> {
return fetch(uri, {
method,
headers: {
Authorization: `Bearer ${this.clientAccessToken}`,
'Content-Type': 'application/json',
...headers,
},
body,
});
}

Expand Down Expand Up @@ -161,16 +178,172 @@ export class APIClient {
iteratee: ResourceIteratee<ArtifactoryPermission>,
): Promise<void> {
const response = await this.request(
this.withBaseUri('artifactory/api/security/permissions'),
this.withBaseUri('artifactory/api/v2/security/permissions'),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No clue about this, but should the rest of the API calls also be to api/v2?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are the docs to the v2 api: https://www.jfrog.com/confluence/display/JFROG/Artifactory+REST+API+V2, so it only contains a few extending endpoints. We used the v2 here because the response format looked a bit better.

);

const permissionRefs: ArtifactoryPermissionRef[] = await response.json();

for (const permission of permissionRefs) {
const resp = await this.request(permission.uri);

await iteratee(await resp.json());
}
}

/**
* Iterates each access token in the provider.
*
* @param iteratee receives each resource to produce entities/relationships
*/
public async iterateAccessTokens(
iteratee: ResourceIteratee<ArtifactoryAccessToken>,
): Promise<void> {
const response = await this.request(
this.withBaseUri('artifactory/api/security/token'),
);

const accessTokens: ArtifactoryAccessTokenResponse = await response.json();

for (const accessToken of accessTokens.tokens || []) {
await iteratee(accessToken);
}
}

/**
* Iterates each repository artifact in the provider.
*
* @param iteratee receives each resource to produce entities/relationships
*/
public async iterateRepositoryArtifacts(
key: string,
iteratee: ResourceIteratee<ArtifactoryArtifactRef>,
): Promise<void> {
const response = await this.request(
this.withBaseUri(`artifactory/api/storage/${key}`),
);

await this.recurseArtifacts(await response.json(), iteratee);
}

private async recurseArtifacts(
response: ArtifactoryArtifactResponse,
iteratee: ResourceIteratee<ArtifactoryArtifactRef>,
): Promise<void> {
for (const artifact of response.children || []) {
if (artifact.folder) {
const nextUri = `${response.uri}${artifact.uri}`;
const nextResponse = await this.request(nextUri);

await this.recurseArtifacts(await nextResponse.json(), iteratee);
} else {
await iteratee({
...artifact,
uri: this.withBaseUri(
`artifactory/${response.repo}${response.path}${artifact.uri}`,
),
});
}
}
}

/**
* Iterates each build in the provider.
*
* @param iteratee receives each resource to produce entities/relationships
*/
public async iterateBuilds(
iteratee: ResourceIteratee<ArtifactoryBuild>,
): Promise<void> {
const response = await this.request(
this.withBaseUri('artifactory/api/build'),
);

const jsonResponse: ArtifactoryBuildResponse = await response.json();

// A list of artifactory/api/build/<name>
for (const build of jsonResponse.builds || []) {
const buildList = await this.getBuildList(build);

// A list of artifactory/api/build/<name>/<number>
for (const buildUri of buildList) {
const name = build.uri.split('/')[1];
const number = buildUri.split('/')[1];
const artifacts = await this.getBuildArtifacts(name, number);

if (artifacts.length === 0) {
return;
}

const repository = artifacts[0]
.split(this.withBaseUri('artifactory'))[1]
.split('/')[1];

await iteratee({
name,
number,
repository,
artifacts,
uri: this.withBaseUri(`ui/builds${build.uri}`),
});
}
}
}

private async getBuildList(buildRef: ArtifactoryBuildRef): Promise<string[]> {
const response = await this.request(
this.withBaseUri(`artifactory/api/build${buildRef.uri}`),
);

const jsonResponse: ArtifactoryBuildDetailsResponse = await response.json();

return (jsonResponse.buildsNumbers || []).map((b) => b.uri);
}

private async getBuildArtifacts(
name: string,
number: string,
): Promise<string[]> {
const response = await this.request(
this.withBaseUri('artifactory/api/search/buildArtifacts'),
'POST',
JSON.stringify({
buildName: name,
buildNumber: number,
}),
);

const jsonResponse: ArtifactoryBuildArtifactsResponse = await response.json();

if (jsonResponse.errors) {
return [];
}

return jsonResponse.results.map((r) => r.downloadUri);
}

/**
* Iterates each pipeline source in the provider.
*
* @param iteratee receives each resource to produce entities/relationships
*/
public async iteratePipelineSources(
iteratee: ResourceIteratee<ArtifactoryPipelineSource>,
): Promise<void> {
const response = await this.request(
this.withBaseUri('pipelines/api/v1/pipelinesources'),
'GET',
null,
{
Authorization: `Bearer ${this.clientPipelineAccessToken}`,
},
);

const sources: ArtifactoryPipelineSource[] = await response.json();

for (const source of sources || []) {
await iteratee(source);
}
}
}

export function createAPIClient(config: IntegrationConfig): APIClient {
Expand Down
Loading