Skip to content

Commit dd33854

Browse files
annotations (#319)
1 parent 1328fc9 commit dd33854

File tree

12 files changed

+549
-2
lines changed

12 files changed

+549
-2
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/* eslint-disable no-use-before-define,object-curly-newline,arrow-body-style */
2+
3+
const Promise = require('bluebird');
4+
const { sdk } = require('../../../../logic');
5+
6+
class AnnotationLogic {
7+
8+
static async listAnnotations({ entityId, entityType, labels }) {
9+
let annotations = [];
10+
try {
11+
annotations = await sdk.annotations.list({ entityId, entityType });
12+
} catch (error) {
13+
if (error.statusCode === 404) {
14+
throw new Error('Annotations not found for specified entity');
15+
} else {
16+
throw error;
17+
}
18+
}
19+
20+
if (labels && labels.length) {
21+
annotations = annotations.filter(annotation => labels.includes(annotation.key));
22+
}
23+
24+
return annotations;
25+
}
26+
27+
static createAnnotations({ entityId, entityType, labels }) {
28+
if (!labels || !labels.length) {
29+
throw new Error('Labels are required for set command');
30+
}
31+
32+
const annotations = AnnotationLogic._parseAnnotations(labels);
33+
return Promise.map(annotations, ({ key, value }) => sdk.annotations.create({ entityId, entityType, key, value }));
34+
}
35+
36+
static deleteAnnotations({ entityId, entityType, labels }) {
37+
if (labels && labels.length) {
38+
return Promise.map(labels, key => sdk.annotations.delete({ entityId, entityType, key }));
39+
}
40+
41+
return sdk.annotations.delete({ entityId, entityType });
42+
}
43+
44+
static _parseAnnotations(labels) {
45+
return labels.map((label) => {
46+
const [key, value] = label.split('=');
47+
return { key, value };
48+
});
49+
}
50+
}
51+
52+
module.exports = AnnotationLogic;
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
const getCmd = require('./get.cmd').toCommand();
2+
const deleteCmd = require('./delete.cmd').toCommand();
3+
const createCmd = require('./create.cmd').toCommand();
4+
5+
6+
jest.mock('../../../../logic/entities/Annotation');
7+
8+
const request = require('requestretry');
9+
10+
const DEFAULT_RESPONSE = request.__defaultResponse();
11+
12+
describe('annotation commands', () => {
13+
beforeEach(async () => {
14+
request.__reset();
15+
request.mockClear();
16+
await configureSdk(); // eslint-disable-line
17+
});
18+
19+
describe('create', () => {
20+
it('should handle creation', async () => {
21+
const argv = { labels: ['some name'], 'entity-id': 'id', 'entity-type': 'image', _: [0, 0, 'key=value'] };
22+
await createCmd.handler(argv);
23+
await verifyResponsesReturned([DEFAULT_RESPONSE]); // eslint-disable-line
24+
});
25+
});
26+
27+
describe('get', () => {
28+
it('should handle getting given id', async () => {
29+
const argv = { 'entity-id': 'id', 'entity-type': 'image', _: [0, 0, 'test'] };
30+
const response = { statusCode: 200, body: [{ key: 'test', value: 'test' }] };
31+
request.__setResponse(response);
32+
await getCmd.handler(argv);
33+
await verifyResponsesReturned([response]); // eslint-disable-line
34+
});
35+
36+
it('should handle getting given name', async () => {
37+
const argv = { 'entity-id': 'id', 'entity-type': 'image', _: [0, 0] };
38+
const response = { statusCode: 200, body: [{ test: 'test' }] };
39+
request.__setResponse(response);
40+
await getCmd.handler(argv);
41+
await verifyResponsesReturned([response]); // eslint-disable-line
42+
});
43+
44+
it('should handle getting all', async () => {
45+
const argv = { 'entity-id': 'id', 'entity-type': 'image', _: [0, 0] };
46+
const response = { statusCode: 200, body: [DEFAULT_RESPONSE.body] };
47+
request.__setResponse(response);
48+
await getCmd.handler(argv);
49+
await verifyResponsesReturned([response]); // eslint-disable-line
50+
});
51+
});
52+
53+
describe('delete', () => {
54+
it('should handle deletion given name', async () => {
55+
const argv = { 'entity-id': 'id', 'entity-type': 'image', _: [0, 0, 'key'] };
56+
const responses = [
57+
DEFAULT_RESPONSE,
58+
];
59+
request.__queueResponses(responses);
60+
await deleteCmd.handler(argv);
61+
await verifyResponsesReturned(responses); // eslint-disable-line
62+
});
63+
});
64+
});
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
const _ = require('lodash');
2+
const Command = require('../../Command');
3+
const createRoot = require('../root/create.cmd');
4+
const Logic = require('./annotation.logic');
5+
6+
const command = new Command({
7+
parent: createRoot,
8+
command: 'annotation <entity-type> <entity-id> <labels...>',
9+
description: 'Annotate a resource with labels',
10+
webDocs: {
11+
category: 'Annotations',
12+
title: 'create',
13+
// weight: 50,
14+
},
15+
builder: yargs => yargs
16+
.positional('entity-type', {
17+
describe: 'Type of resource for annotation',
18+
required: true,
19+
})
20+
.positional('entity-id', {
21+
describe: 'Id of resource for annotation',
22+
required: true,
23+
})
24+
.example('codefresh create annotation image 2dfacdaad466 coverage=75%', 'Annotate entity with a single label')
25+
.example('codefresh create annotation image 2dfacdaad466 coverage=75% tests_passed=true', 'Annotate entity with multiple labels'),
26+
handler: async (argv) => {
27+
const { entityType, entityId, labels } = argv;
28+
29+
await Logic.createAnnotations({ entityId, entityType, labels });
30+
console.log('Annotations was created');
31+
},
32+
});
33+
34+
module.exports = command;
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
const Command = require('../../Command');
2+
const _ = require('lodash');
3+
const deleteRoot = require('../root/delete.cmd');
4+
const Logic = require('./annotation.logic');
5+
6+
const command = new Command({
7+
parent: deleteRoot,
8+
command: 'annotation <entity-type> <entity-id> [labels...]',
9+
description: 'Delete annotations',
10+
webDocs: {
11+
category: 'Annotations',
12+
title: 'delete',
13+
// weight: 50,
14+
},
15+
builder: yargs => yargs
16+
.positional('entity-type', {
17+
describe: 'Type of resource for annotation',
18+
required: true,
19+
})
20+
.positional('entity-id', {
21+
describe: 'Id of resource for annotation',
22+
required: true,
23+
})
24+
.example('codefresh delete annotation image 2dfacdaad466 coverage tests_passed', 'Delete specified annotations')
25+
.example('codefresh delete annotation image 2dfacdaad466', 'Delete all annotations of entity'),
26+
handler: async (argv) => {
27+
const { entityType, entityId, labels } = argv;
28+
29+
await Logic.deleteAnnotations({ entityId, entityType, labels });
30+
console.log('Annotations was deleted');
31+
},
32+
});
33+
34+
module.exports = command;
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
const _ = require('lodash');
2+
const Command = require('../../Command');
3+
const Logic = require('./annotation.logic');
4+
const Annotation = require('../../../../logic/entities/Annotation');
5+
const Output = require('../../../../output/Output');
6+
7+
const getRoot = require('../root/get.cmd');
8+
9+
const command = new Command({
10+
parent: getRoot,
11+
command: 'annotation <entity-type> <entity-id> [labels...]',
12+
description: 'Get annotations',
13+
webDocs: {
14+
category: 'Annotations',
15+
title: 'get',
16+
// weight: 50,
17+
},
18+
builder: yargs => yargs
19+
.positional('entity-type', {
20+
describe: 'Type of resource for annotation',
21+
required: true,
22+
})
23+
.positional('entity-id', {
24+
describe: 'Id of resource for annotation',
25+
required: true,
26+
})
27+
.example('codefresh get annotation image 2dfacdaad466', 'Get all annotations for entity')
28+
.example('codefresh get annotation image 2dfacdaad466 coverage tests_passed', 'Get specified annotations'),
29+
handler: async (argv) => {
30+
const { entityType, entityId, labels } = argv;
31+
32+
const annotations = await Logic.listAnnotations({ entityId, entityType, labels });
33+
Output.print(annotations.map(Annotation.fromResponse));
34+
},
35+
});
36+
37+
module.exports = command;

lib/interface/cli/commands/image/annotate.cmd.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const { parseFamiliarName } = require('@codefresh-io/docker-reference');
44
const annotateRoot = require('../root/annotate.cmd');
55
const CFError = require('cf-errors');
66
const { sdk } = require('../../../../logic');
7+
const annotationLogic = require('../annotation/annotation.logic');
78

89
const command = new Command({
910
command: 'image <id>',
@@ -53,6 +54,7 @@ const command = new Command({
5354
}
5455

5556
await sdk.images.upsertMetadata({ dockerImageId }, annotations);
57+
await annotationLogic.createAnnotations({ entityId: dockerImageId, entityType: 'image', labels: argv.label });
5658
console.log('Annotations added successfully');
5759
},
5860
});

lib/interface/cli/commands/image/image.sdk.spec.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,15 @@ describe('image commands', () => {
7878
it('should handle annotating given docker image id', async () => {
7979
const argv = { id: 'some id', label: ['test=test'] };
8080
await annotateCmd.handler(argv);
81-
await verifyResponsesReturned([DEFAULT_RESPONSE]); // eslint-disable-line
81+
await verifyResponsesReturned([DEFAULT_RESPONSE, DEFAULT_RESPONSE]); // eslint-disable-line
8282
});
8383

8484
it('should handle annotating given full image name', async () => {
8585
const argv = { id: 'repo/name:tag', label: ['test=test'] };
8686
const responses = [
8787
{ statusCode: 200, body: [{ internalImageId: 'some id' }] },
8888
DEFAULT_RESPONSE,
89+
DEFAULT_RESPONSE,
8990
];
9091
request.__queueResponses(responses);
9192
await annotateCmd.handler(argv);

lib/logic/entities/Annotation.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
const _ = require('lodash');
2+
const Entity = require('./Entity');
3+
4+
class Annotation extends Entity {
5+
constructor(data) {
6+
super();
7+
this.entityType = 'annotation';
8+
this.info = data;
9+
this.id = this.info._id;
10+
this.entity_id = this.info.entityId;
11+
this.entity_type = this.info.entityType;
12+
this.key = this.info.key;
13+
this.value = this.info.value;
14+
this.type = this.info.type;
15+
this.defaultColumns = ['id', 'entity_id', 'entity_type', 'key', 'value'];
16+
this.wideColumns = this.defaultColumns.concat(['type']);
17+
}
18+
19+
static fromResponse(response) {
20+
return new Annotation(_.pick(response, '_id', 'entityId', 'entityType', 'key', 'value'));
21+
}
22+
}
23+
24+
module.exports = Annotation;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const { Formatter, Style } = require('../Formatter');
2+
3+
4+
const FORMATTER = Formatter.build()
5+
.style('key', Style.cyan);
6+
7+
module.exports = FORMATTER;

lib/output/formatters/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const formatters = {
2222
Token: require('./Token.formatter'),
2323
Pipeline: require('./Pipeline.formatter'),
2424
Registry: require('./Registry.formatter'),
25+
Annotation: require('./Annotations.formatter'),
2526
};
2627
/* eslint-enable */
2728

0 commit comments

Comments
 (0)