-
Notifications
You must be signed in to change notification settings - Fork 3.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(pipelines): changing synth action doesn't restart pipeline #10176
Changes from 4 commits
63d0a02
582494a
123ec5a
8674f09
b69ae1a
15e9c53
543855a
f929a29
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -281,6 +281,90 @@ export function notMatching(matcher: any): PropertyMatcher { | |
}); | ||
} | ||
|
||
export type TypeValidator<T> = (x: any) => x is T; | ||
|
||
/** | ||
* Captures a value onto an object if it matches a given inner matcher | ||
* | ||
* @example | ||
* | ||
* const someValue = Capture.aString(); | ||
* expect(stack).toHaveResource({ | ||
* // ... | ||
* Value: someValue.capture(stringMatching('*a*')), | ||
* }); | ||
* console.log(someValue.capturedValue); | ||
*/ | ||
export class Capture<T=any> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sigh one more thing to consider when rewriting this module as a JSII-able module. I would've asked - why not just const notThisHash = ...;
expect(stack).not.toHaveResourceLike('AWS::CodeBuild::Project', {
...
Value: notThisHash
}); But looking at the usage and the deep nesting, I understand why you had to do this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The functionality is legit and should be ported, I feel. The jsii-able version of this might drop the type assertions but I didn't see any point in having this wobbly-typed since this module is long past jsii-ability anyway. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fine by me. We'll definitely consider for this to be ported. Just wanted to make note that the form might have to change. |
||
/** | ||
* A Capture object that captures any type | ||
*/ | ||
public static anyType(): Capture<any> { | ||
return new Capture(); | ||
} | ||
|
||
/** | ||
* A Capture object that captures a custom type | ||
*/ | ||
public static aString(): Capture<string> { | ||
return new Capture((x: any): x is string => { | ||
if (typeof x !== 'string') { | ||
throw new Error(`Expected to capture a string, got '${x}'`); | ||
} | ||
return true; | ||
}); | ||
} | ||
|
||
/** | ||
* A Capture object that captures a custom type | ||
*/ | ||
public static a<T>(validator: TypeValidator<T>): Capture<T> { | ||
return new Capture(validator); | ||
} | ||
|
||
private _value?: T; | ||
private _didCapture = false; | ||
private _wasInvoked = false; | ||
|
||
protected constructor(private readonly typeValidator?: TypeValidator<T>) { | ||
} | ||
|
||
/** | ||
* Capture the value if the inner matcher successfully matches it | ||
* | ||
* If no matcher is given, `anything()` is assumed. | ||
*/ | ||
public capture(matcher?: any): PropertyMatcher { | ||
if (matcher === undefined) { | ||
matcher = anything(); | ||
} | ||
|
||
return annotateMatcher({ $capture: matcher }, (value: any, failure: InspectionFailure) => { | ||
this._wasInvoked = true; | ||
const result = matcherFrom(matcher)(value, failure); | ||
if (result) { | ||
if (this.typeValidator && !this.typeValidator(value)) { | ||
throw new Error(`Value not of the expected type: ${value}`); | ||
} | ||
this._didCapture = true; | ||
this._value = value; | ||
} | ||
return result; | ||
}); | ||
} | ||
|
||
public get didCapture() { | ||
return this._didCapture; | ||
} | ||
|
||
public get capturedValue(): T { | ||
if (!this.didCapture) { | ||
throw new Error(`Did not capture a value: ${this._wasInvoked ? 'inner matcher failed' : 'never invoked'}`); | ||
} | ||
return this._value!; | ||
} | ||
} | ||
|
||
/** | ||
* Match on the innards of a JSON string, instead of the complete string | ||
*/ | ||
|
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,3 +1,4 @@ | ||||||||||||||
import * as crypto from 'crypto'; | ||||||||||||||
import * as path from 'path'; | ||||||||||||||
import * as codebuild from '@aws-cdk/aws-codebuild'; | ||||||||||||||
import * as codepipeline from '@aws-cdk/aws-codepipeline'; | ||||||||||||||
|
@@ -262,32 +263,47 @@ export class SimpleSynthAction implements codepipeline.IAction { | |||||||||||||
const testCommands = this.props.testCommands ?? []; | ||||||||||||||
const synthCommand = this.props.synthCommand; | ||||||||||||||
|
||||||||||||||
const project = new codebuild.PipelineProject(scope, 'CdkBuildProject', { | ||||||||||||||
projectName: this.props.projectName ?? this.props.projectName, | ||||||||||||||
environment: { buildImage: codebuild.LinuxBuildImage.STANDARD_4_0, ...this.props.environment }, | ||||||||||||||
buildSpec: codebuild.BuildSpec.fromObject({ | ||||||||||||||
version: '0.2', | ||||||||||||||
phases: { | ||||||||||||||
pre_build: { | ||||||||||||||
commands: filterEmpty([ | ||||||||||||||
this.props.subdirectory ? `cd ${this.props.subdirectory}` : '', | ||||||||||||||
...installCommands, | ||||||||||||||
]), | ||||||||||||||
}, | ||||||||||||||
build: { | ||||||||||||||
commands: filterEmpty([ | ||||||||||||||
...buildCommands, | ||||||||||||||
...testCommands, | ||||||||||||||
synthCommand, | ||||||||||||||
]), | ||||||||||||||
}, | ||||||||||||||
const buildSpec = codebuild.BuildSpec.fromObject({ | ||||||||||||||
version: '0.2', | ||||||||||||||
phases: { | ||||||||||||||
pre_build: { | ||||||||||||||
commands: filterEmpty([ | ||||||||||||||
this.props.subdirectory ? `cd ${this.props.subdirectory}` : '', | ||||||||||||||
...installCommands, | ||||||||||||||
]), | ||||||||||||||
}, | ||||||||||||||
build: { | ||||||||||||||
commands: filterEmpty([ | ||||||||||||||
...buildCommands, | ||||||||||||||
...testCommands, | ||||||||||||||
synthCommand, | ||||||||||||||
]), | ||||||||||||||
}, | ||||||||||||||
artifacts: renderArtifacts(this), | ||||||||||||||
}), | ||||||||||||||
environmentVariables: { | ||||||||||||||
...copyEnvironmentVariables(...this.props.copyEnvironmentVariables || []), | ||||||||||||||
...this.props.environmentVariables, | ||||||||||||||
}, | ||||||||||||||
artifacts: renderArtifacts(this), | ||||||||||||||
}); | ||||||||||||||
|
||||||||||||||
const environment = { buildImage: codebuild.LinuxBuildImage.STANDARD_4_0, ...this.props.environment }; | ||||||||||||||
|
||||||||||||||
const environmentVariables = { | ||||||||||||||
...copyEnvironmentVariables(...this.props.copyEnvironmentVariables || []), | ||||||||||||||
...this.props.environmentVariables, | ||||||||||||||
}; | ||||||||||||||
|
||||||||||||||
// A hash over the values that make the CodeBuild Project unique (and necessary | ||||||||||||||
// to restart the pipeline if one of them changes). projectName is not necessary to include | ||||||||||||||
// here because the pipeline will definitely restart if projectName changes. | ||||||||||||||
const projectConfigHash = hash({ | ||||||||||||||
environment, | ||||||||||||||
buildSpecString: buildSpec.toBuildSpec(), | ||||||||||||||
environmentVariables, | ||||||||||||||
}); | ||||||||||||||
|
||||||||||||||
const project = new codebuild.PipelineProject(scope, 'CdkBuildProject', { | ||||||||||||||
projectName: this.props.projectName ?? this.props.projectName, | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ? |
||||||||||||||
environment, | ||||||||||||||
buildSpec, | ||||||||||||||
environmentVariables, | ||||||||||||||
}); | ||||||||||||||
|
||||||||||||||
if (this.props.rolePolicyStatements !== undefined) { | ||||||||||||||
|
@@ -300,6 +316,13 @@ export class SimpleSynthAction implements codepipeline.IAction { | |||||||||||||
actionName: this.actionProperties.actionName, | ||||||||||||||
input: this.props.sourceArtifact, | ||||||||||||||
outputs: [this.props.cloudAssemblyArtifact, ...(this.props.additionalArtifacts ?? []).map(a => a.artifact)], | ||||||||||||||
|
||||||||||||||
// Inclusion of the hash here will lead to the pipeline structure changing if the | ||||||||||||||
// buildspec changes, and hence the pipeline being restarted. This is necessary if | ||||||||||||||
// the users adds (for example) build or test commands to the buildspec. | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not just the buildspec but anything around the CodeBuild project.
Suggested change
|
||||||||||||||
environmentVariables: { | ||||||||||||||
_PROJECT_CONFIG_HASH: { value: projectConfigHash }, | ||||||||||||||
}, | ||||||||||||||
project, | ||||||||||||||
}); | ||||||||||||||
this._actionProperties = this._action.actionProperties; | ||||||||||||||
|
@@ -411,4 +434,10 @@ export interface StandardYarnSynthOptions extends SimpleSynthOptions { | |||||||||||||
* @default 'npx cdk synth' | ||||||||||||||
*/ | ||||||||||||||
readonly synthCommand?: string; | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
function hash<A>(obj: A) { | ||||||||||||||
const d = crypto.createHash('sha256'); | ||||||||||||||
d.update(JSON.stringify(obj)); | ||||||||||||||
return d.digest('hex'); | ||||||||||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add corresponding entries in the README for these new features