-
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
StackSet/Conformance Packs support - make stacks available as an asset #11896
Comments
@pgarbe I am curious if you can use export interface StackSetStackProps extends cdk.StackProps {
stack: cdk.Stack
...
}
export class StackSetStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props: StackSetStackProps) {
super(scope, id, props);
const template = stack.addFileAsset({
packaging: cdk.FileAssetPackaging.FILE,
sourceHash: 'git-commit',
fileName: props.stack.templateFile,
});
new cfn.CfnStackSet(this, 'StackSet', {
...
templateUrl: template.s3ObjectUrl
});
}
} I'd be interested in adding official support for |
Copy @rix0rrr |
@eladb Thanks, that actually works :) I'm not just sure what could be a good source hash. Will look into that. |
That's definitely the tricky part. You could use the current git commit in production but for dev iterations you'd need something else |
I really like this solution. And it makes so much sense if you see it ;-) I would love to have this wrapped in a construct so this magic does not need to be copied. The hash might really be tricky. |
Only the synth part works. Within a pipeline the upload action is missing. How can I enforce that? |
What do you mean only the synth part works? Can you paste the |
Previously, I had issues that synth failed to circular dependencies of the PipelineStack and StackSetStack. With your snippet this has been solved and it synthesized the correct template url. But the pipeline does not contain a stage to upload the assets. Here's a sample project: https://github.com/pgarbe/cdk-stackset |
It might be useful to upload the synthesized stack into a separate bucket as there might be a different lifecycle. For me assets (and the assets bucket) are ephemeral and I can recreate everything needed when I run the pipeline. But in case of a service catalog product it's needed to keep different versions of a template for a longer time. |
Multipel +1 on this. I have quite a few stacks i want to deploy to every account in our org. |
Theres a good chance StackSets are managed by centralized teams so a separate bucket would make a lot sense with different access permissions. The initial bootstrap for CDK is targeted at teams deploying workloads IMO but alot of enterprises will have governance models that would prevent this e.g. multi-tenanted account structure where customers can only deploy in to their accounts, but admins can deploy stacks in to all accounts. A trivial example might be a ChatOps stack. |
@eladb and everyone else. I've just been standing up some stack sets using the L1 construct.. Just some thoughts about using that ( and pipelines )
cdkqualifier = parameters['env_parameters']['CdkQualifier'] # TODO. Load this from cdk.json
execution_role_name = f'cdk-${cdkqualifier}-stacksetExecution-${account}-${self.region}'
for vpc in vpc_to_use:
routeresolver_rules = {}
for resolver_rule in resolver_rules:
routeresolver_rules[resolver_rule['Name']] = {
'Type': 'AWS::Route53Resolver::ResolverRuleAssociation',
'Properties': {
'ResolverRuleId': resolver_rule['Id'],
'VPCId': vpc['VpcId']
}
}
stack_set = cfn.CfnStackSet(self, 'stackset',
permission_model= 'SELF_MANAGED',
stack_set_name= 'Route53Assn',
administration_role_arn= stack_set_administrator.arn,
execution_role_name= execution_role_name,
stack_instances_group = [
{
'Regions': self.region, #list of regions
'DeploymentTargets': {
'Accounts': account #list of accounts
}
}
],
template_body= {'Resources': routeresolver_rules}
) For non trivial stacks this becomes really limiting.. I'd want to be able to create stack sets more natively with Cdk.. Sure i can create them, and copy them from cdk.out to a bucket by hand, but thats going to be quite a few layers of hackery... ( not impossible though )... |
Making the stack available as an asset use cases is also needed for AWS Config conformance packs. |
@pgarbe I got it working by utilizing the new Service Catalog ProductStack support. This way, the asset for the stack set is also uploaded during class DeployedViaStackSet extends servicecatalog.ProductStack {
constructor(scope: Construct, id: string) {
super(scope, id);
new aws_codecommit.Repository(this, 'CodeRepo', {
repositoryName: "example"
})
}
}
export class CdkStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
const deployedViaStackSet = new DeployedViaStackSet(this, 'DeployedViaStackSet');
new CfnStackSet(this, 'StackSet', {
templateUrl: servicecatalog.CloudFormationTemplate.fromProductStack(DeployedViaStackSet).bind(this).httpUrl,
...
});
}
} |
@s0enke that's great, that's exactly what this functionality is for 🙂. The documentation is here: https://docs.aws.amazon.com/cdk/api/latest/docs/aws-servicecatalog-readme.html#creating-a-product-from-a-stack I will close this issue as "done", if anyone runs into problems and can't use the ServiceCatalog Thanks, |
|
Hmm... could the solution be as simple as adding a class to the |
@skinny85 @fitzoh I assume that Conformance Packs can be populated in the same way, but I did not test it (they are no real CFN templates, but a subset IIRC): class DeployedViaConformancePack extends servicecatalog.ProductStack {
constructor(scope: Construct, id: string) {
super(scope, id);
new aws_config_rule(...)
}
}
export class CdkStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
const deployedViaConformancePack = new DeployedViaConformancePack(this, 'DeployedViaConformancePack');
const cfnOrganizationConformancePack = new config.CfnOrganizationConformancePack(this, 'MyCfnOrganizationConformancePack', {
...
templateS3Uri: servicecatalog.CloudFormationTemplate.fromProductStack( DeployedViaConformancePack).bind(this).httpUrl,
});
}
} I agree that it looks a bit more like a workaround/hack, since we are utilizing something from the Service Catalog Construct, so a convenience method/construct in the core might make sense. I count four occurrences which work with CFN templates under the hood: Service Catalog, Stack Sets, and Conformance Packs, and Organizational Conformance Packs. |
Ok. I've edited the title of the issue, let's re-open this one. |
BTW, contributions are always welcome 😉. |
Same topic.... ( request for conformance packs.. ). I solved this problem by creating two apps, the first one creates templates, synths them and sticks them in cfassests.out. The 'main' stack, picks those templates which become 'assests'.. This
|
This is AWESOME.. Maybe I am missing something @eladb ? Wy wouldn't you just use the hash of the synthesized template as the sourceHash? |
@peterb154 when this code is executed there isn't a synthesized template yet :-) |
I ended up doing something absolutely horrible to make assets work with StackSet stack instances. Had to change the staging bucket encryption by using a custom bootstrap template. One stackset per region because the bucket is passed in parameters. One stack with all the stacksets to allow parallel deployment. Let me know if you guys find a better way. class ComplianceStackInstance extends Stack {
constructor(scope: Construct) {
super(scope, 'Custom-Config-Rules', {
env: {
region: MAIN_REGION,
account: ACCOUNTS.Root.Management.toString(),
},
});
new AccountDefaults(this);
}
}
const DEPLOYMENT_ORG_UNITS = [ORGUNITS.Development];
class ComplianceAssetStack extends Stack {
readonly toolkitStagingBucket: IBucket;
constructor(
scope: App,
env: Environment,
region: string,
virtualStackArtifact: CloudFormationStackArtifact,
) {
super(scope, `${env.stage}-ComplianceAssets-${region}`, {
env: {
account: ACCOUNTS.Root.Management.toString(),
region,
},
});
for (const asset of virtualStackArtifact.assets) {
if (asset.packaging === FileAssetPackaging.ZIP_DIRECTORY)
this.synthesizer.addFileAsset({
packaging: FileAssetPackaging.ZIP_DIRECTORY,
fileName: asset.path,
sourceHash: asset.sourceHash,
});
}
this.toolkitStagingBucket = Bucket.fromBucketName(
this,
'CDKStagingBucket',
(MANAGEMENT_STAGING_BUCKETS as Record<string, string>)[this.region],
);
const pol = new BucketPolicy(this, 'CDKStagingBucketPolicy', {
bucket: this.toolkitStagingBucket,
});
pol.document.addStatements(
new PolicyStatement({
actions: ['s3:GetObject'],
resources: virtualStackArtifact.assets.map((asset) =>
this.toolkitStagingBucket.arnForObjects(
`assets/${asset.sourceHash}.zip`,
),
),
principals: [new AnyPrincipal()],
conditions: {
'ForAnyValue:StringEquals': {
'aws:PrincipalOrgPaths': DEPLOYMENT_ORG_UNITS.map(
(ou) => `${ORGID}/${ORGUNITS.Root}/${ou}/`,
),
},
},
}),
);
}
}
export class ComplianceStack extends Stack {
constructor(scope: App, env: Environment) {
super(scope, `${env.stage}-Compliance`, {
env: {
region: MAIN_REGION,
account: ACCOUNTS.Root.Management.toString(),
},
terminationProtection: env.isProductionStage,
});
const app = new App();
const virtualComplianceStack = new ComplianceStackInstance(app);
const templateAsset = this.synthesizer.addFileAsset({
packaging: FileAssetPackaging.FILE,
sourceHash: randomBytes(18).toString('base64'),
fileName: virtualComplianceStack.templateFile,
});
const virtualStackArtifact = app
.synth()
.getStackArtifact(virtualComplianceStack.artifactId);
for (const region of GOVERNED_REGIONS) {
const assetStack = new ComplianceAssetStack(
scope,
env,
region,
virtualStackArtifact,
);
this.addDependency(assetStack);
new CfnStackSet(this, `StackSet-${region}`, {
autoDeployment: {
enabled: true,
retainStacksOnAccountRemoval: false,
},
stackInstancesGroup: [
{
regions: [region],
deploymentTargets: { organizationalUnitIds: DEPLOYMENT_ORG_UNITS },
},
],
capabilities: ['CAPABILITY_IAM'],
operationPreferences: {
maxConcurrentPercentage: 50,
failureTolerancePercentage: 50,
},
parameters: virtualStackArtifact.assets.flatMap((asset) => {
return 's3BucketParameter' in asset
? [
{
parameterKey: asset.s3BucketParameter,
parameterValue: assetStack.toolkitStagingBucket.bucketName,
},
{
parameterKey: asset.s3KeyParameter,
parameterValue: `assets/||${asset.sourceHash}.zip`,
},
{
parameterKey: asset.artifactHashParameter,
parameterValue: asset.sourceHash,
},
]
: [];
}),
stackSetName: `Custom-Compliance-${region}`,
permissionModel: 'SERVICE_MANAGED',
templateUrl: templateAsset.httpUrl,
});
}
}
} |
Started to work on a StackSet L2 construct which supports |
I think we want a |
A stack should be made available as an asset. At the moment it's not possible to add the generated template file as asset in another stack, as the file does not exist at this point in time.
Use Case
If you want to deploy a StackSet or ServiceCatalog Product (in a pipeline) two stacks are used. One stack contains the StackSet construct and the other stack (let's call it template stack) is the one which should be deployed in target accounts via StackSet. This template stack will never deployed directly but just synthesized. The StackSet stack needs an s3 url of the template stack.
This could be also related to an integration with Proton where it's also the synthesized stack template needs to be available as asset as well.
Proposed Solution
The usage could look like this:
Not sure how the implementation could look like.
Other
This is a 🚀 Feature Request
The text was updated successfully, but these errors were encountered: