From ca217d355edb5cdbde1cf9ed65adfe918f5606d1 Mon Sep 17 00:00:00 2001 From: noamt Date: Thu, 15 Dec 2016 15:00:25 +0200 Subject: [PATCH] #17 Support the build step's metadata annotations: * Add support for post step annotation ops --- schema/1.0/base-schema.js | 30 ++++ schema/1.0/steps/build.js | 11 +- schema/1.0/steps/composition.js | 1 + schema/1.0/steps/freestyle.js | 1 + schema/1.0/steps/git-clone.js | 1 + schema/1.0/steps/launch-composition.js | 1 + schema/1.0/steps/push.js | 1 + tests/unit/validator.unit.js | 207 ++++++++++++++++++++++++- 8 files changed, 238 insertions(+), 15 deletions(-) diff --git a/schema/1.0/base-schema.js b/schema/1.0/base-schema.js index 30d389e4..f8ba5e8b 100644 --- a/schema/1.0/base-schema.js +++ b/schema/1.0/base-schema.js @@ -61,6 +61,36 @@ class BaseSchema { }); } + static _getMetadataAnnotationSetSchema() { + return Joi.array().items( + Joi.alternatives().try( + Joi.object().pattern(/^[A-Za-z0-9_]+$/, Joi.alternatives().try( + [ + Joi.string(), + Joi.boolean(), + Joi.number(), + Joi.object({ evaluate: Joi.string().required() }) + ] + )), Joi.string().regex(/^[A-Za-z0-9_]+$/) + ) + ); + } + + _applyMetadataAnnotationSchemaProperties(schemaProperties) { + const metadataAnnotationSchema = Joi.object({ + metadata: Joi.object({ + set: Joi.array().items( + Joi.object().pattern(/^.+$/, BaseSchema._getMetadataAnnotationSetSchema()) + ) + }) + }); + return Object.assign(schemaProperties, { + 'on_success': metadataAnnotationSchema, + 'on_fail': metadataAnnotationSchema, + 'on_finish': metadataAnnotationSchema, + }); + } + //------------------------------------------------------------------------------ // Public Interface //------------------------------------------------------------------------------ diff --git a/schema/1.0/steps/build.js b/schema/1.0/steps/build.js index 272495b7..4410c827 100644 --- a/schema/1.0/steps/build.js +++ b/schema/1.0/steps/build.js @@ -31,16 +31,7 @@ class Build extends BaseSchema { build_arguments: Joi.array().items(Joi.string()), tag: Joi.string(), metadata: Joi.object({ - set: Joi.array().items( - Joi.alternatives().try( - Joi.object().pattern(/^[A-Za-z09_]+$/, Joi.alternatives().try( - [ - Joi.string(), - Joi.boolean(), - Joi.number(), - Joi.object({ evaluate: Joi.string().required() }) - ] - )), Joi.string())) + set: Build._getMetadataAnnotationSetSchema() }) }; diff --git a/schema/1.0/steps/composition.js b/schema/1.0/steps/composition.js index 4020b88a..254d7f55 100644 --- a/schema/1.0/steps/composition.js +++ b/schema/1.0/steps/composition.js @@ -29,6 +29,7 @@ class Composition extends BaseSchema { 'composition_candidates': Joi.object().required(), 'composition_variables': Joi.array().items(Joi.string()), }; + this._applyMetadataAnnotationSchemaProperties(compositionProperties); return this._createSchema(compositionProperties).unknown(); } diff --git a/schema/1.0/steps/freestyle.js b/schema/1.0/steps/freestyle.js index 94238cd3..77205637 100644 --- a/schema/1.0/steps/freestyle.js +++ b/schema/1.0/steps/freestyle.js @@ -29,6 +29,7 @@ class Freestyle extends BaseSchema { environment: Joi.array().items(Joi.string()), entry_point: Joi.alternatives().try(Joi.string(), Joi.array().items(Joi.string())) }; + this._applyMetadataAnnotationSchemaProperties(freestyleProperties); return this._createSchema(freestyleProperties).unknown(); } diff --git a/schema/1.0/steps/git-clone.js b/schema/1.0/steps/git-clone.js index 8ef76b11..bf9262c7 100644 --- a/schema/1.0/steps/git-clone.js +++ b/schema/1.0/steps/git-clone.js @@ -29,6 +29,7 @@ class GitClone extends BaseSchema { revision: Joi.string(), credentials: BaseSchema._getCredentialsSchema() }; + this._applyMetadataAnnotationSchemaProperties(gitCloneProperties); return this._createSchema(gitCloneProperties); } diff --git a/schema/1.0/steps/launch-composition.js b/schema/1.0/steps/launch-composition.js index ab65cb81..ff9bfa1a 100644 --- a/schema/1.0/steps/launch-composition.js +++ b/schema/1.0/steps/launch-composition.js @@ -28,6 +28,7 @@ class CompositionLaunch extends BaseSchema { composition: Joi.alternatives(Joi.object(), Joi.string()).required(), 'composition_variables': Joi.array().items(Joi.string()), }; + this._applyMetadataAnnotationSchemaProperties(compositionProperties); return this._createSchema(compositionProperties).unknown(); } diff --git a/schema/1.0/steps/push.js b/schema/1.0/steps/push.js index 4d6c4ffb..edfe9304 100644 --- a/schema/1.0/steps/push.js +++ b/schema/1.0/steps/push.js @@ -34,6 +34,7 @@ class Push extends BaseSchema { region: Joi.string() }; + this._applyMetadataAnnotationSchemaProperties(pushProperties); return this._createSchema(pushProperties); } } diff --git a/tests/unit/validator.unit.js b/tests/unit/validator.unit.js index 69b3294d..c7234181 100644 --- a/tests/unit/validator.unit.js +++ b/tests/unit/validator.unit.js @@ -199,6 +199,142 @@ describe('Validate Codefresh YAML', () => { } }, '"tag" must be a string', done); }); + + it('Unknown post-step metadata operation', (done) => { + validateForError({ + version: '1.0', + steps: { + push: { + 'type': 'push', + 'candidate': 'teh-image', + 'on_finish': { + metadata: { + put: [ + { + '${{build_prj.image}}': [ + { 'qa': 'pending' } + ] + } + ] + } + } + } + } + }, '"put" is not allowed', done); + }); + + it('Unknown post-step metadata entry', (done) => { + validateForError({ + version: '1.0', + steps: { + push: { + 'type': 'push', + 'candidate': 'teh-image', + 'on_finish': { + metadata: { + set: { + '${{build_prj.image}}': [ + { 'qa': 'pending' } + ] + } + } + } + } + } + }, '"set" must be an array', done); + }); + + it('Unspecified image to annotate', (done) => { + validateForError({ + version: '1.0', + steps: { + push: { + 'type': 'push', + 'candidate': 'teh-image', + 'on_finish': { + metadata: { + set: [ + { 'qa': 'pending' } + ] + } + } + } + } + }, '"qa" must be an array', done); + }); + + it('Invalid post-step metadata annotation key', (done) => { + validateForError({ + version: '1.0', + steps: { + push: { + 'type': 'push', + 'candidate': 'teh-image', + 'on_finish': { + metadata: { + set: [ + { + '${{build_prj.image}}': [ + 'an invalid key' + ] + } + ] + } + } + } + } + }, '"an invalid key" fails to match', done); + }); + + it('Invalid post-step metadata annotation value', (done) => { + validateForError({ + version: '1.0', + steps: { + push: { + 'type': 'push', + 'candidate': 'teh-image', + 'on_finish': { + metadata: { + set: [ + { + '${{build_prj.image}}': [ + { 'key1': [] } + ] + } + ] + } + } + } + } + }, '"key1" must be a', done); + }); + + it('Invalid post-step metadata annotation evaluation expression', (done) => { + validateForError({ + version: '1.0', + steps: { + push: { + 'type': 'push', + 'candidate': 'teh-image', + 'on_finish': { + metadata: { + set: [ + { + '${{build_prj.image}}': [ + { + 'jimbob': { + eval: 'jimbob == jimbob' + } + } + ] + } + ] + } + } + } + } + }, '"evaluate" is required', done); + }); }); describe('Freestyle step attributes', () => { @@ -798,7 +934,22 @@ describe('Validate Codefresh YAML', () => { 'commands': ['jim', 'bob'], 'environment': ['key=value', 'key1=value¡'], 'fail_fast': true, - 'when': { branch: { only: ['master'] } } + 'when': { branch: { only: ['master'] } }, + 'on_success': { + metadata: { + set: [ + { + '${{build_prj.image}}': [ + { 'qa': 'pending' }, + { 'healthy': true }, + { 'quality': 67 }, + { 'is_tested': { evaluate: '${{unit_test_step.status}} === success' } }, + 'dangling' + ] + } + ] + } + } }, clone: { 'type': 'git-clone', @@ -809,7 +960,22 @@ describe('Validate Codefresh YAML', () => { 'revision': 'abcdef12345', 'credentials': { username: 'subject', password: 'credentials' }, 'fail_fast': true, - 'when': { branch: { ignore: ['develop'] } } + 'when': { branch: { ignore: ['develop'] } }, + 'on_fail': { + metadata: { + set: [ + { + '${{build_prj.image}}': [ + { 'qa': 'pending' }, + { 'healthy': true }, + { 'quality': 67 }, + { 'is_tested': { evaluate: '${{unit_test_step.status}} === success' } }, + 'dangling' + ] + } + ] + } + } }, build_string_dockerfile: { 'type': 'build', @@ -853,9 +1019,25 @@ describe('Validate Codefresh YAML', () => { 'registry': 'dtr.host.com', 'credentials': { username: 'subject', password: 'credentials' }, 'fail_fast': true, - 'when': { branch: { only: ['/FB-/i'] } } + 'when': { branch: { only: ['/FB-/i'] } }, + 'on_finish': { + metadata: { + set: [ + { + '${{build_prj.image}}': [ + { 'qa': 'pending' }, + { 'healthy': true }, + { 'quality': 67 }, + { 'is_tested': { evaluate: '${{unit_test_step.status}} === success' } }, + 'dangling' + ] + } + ] + } + } }, - composition: { + + composition: { 'type': 'composition', 'description': 'desc', 'title': 'Composition step', @@ -872,7 +1054,22 @@ describe('Validate Codefresh YAML', () => { }, 'composition_variables': ['jim=bob'], 'fail_fast': true, - 'when': { condition: { any: { noDetectedSkipCI: 'includes(\'${{CF_COMMIT_MESSAGE}}\', \'[skip ci]\') == false' } } } + 'when': { condition: { any: { noDetectedSkipCI: 'includes(\'${{CF_COMMIT_MESSAGE}}\', \'[skip ci]\') == false' } } }, + 'on_success': { + metadata: { + set: [ + { + '${{build_prj.image}}': [ + { 'qa': 'pending' }, + { 'healthy': true }, + { 'quality': 67 }, + { 'is_tested': { evaluate: '${{unit_test_step.status}} === success' } }, + 'dangling' + ] + } + ] + } + } } } });