feat(servicecatalog): ProductStackHistory can retain old ProductStack iterations#20244
feat(servicecatalog): ProductStackHistory can retain old ProductStack iterations#20244mergify[bot] merged 6 commits intoaws:masterfrom
Conversation
28059cb to
522ed7f
Compare
|
This PR cannot be merged because it has conflicts. Please resolve them. The PR will be considered stale and closed if it remains in an unmergeable state. |
|
Hi i am not sure how to fix the conflicting files. I tried both force updating and creating a separate commit but it does not seem to help. The second commit has no conflict with the first commit. It should be as simple as taking the latest |
|
This PR cannot be merged because it has conflicts. Please resolve them. The PR will be considered stale and closed if it remains in an unmergeable state. |
|
And port your changes from |
48166c3 to
b4e67c6
Compare
…loyed ProductStacks.
b4e67c6 to
6ab53e6
Compare
|
@wanjacki Looks good. 👍 |
|
@robertd Yes got it working. Thanks alot |
comcalvi
left a comment
There was a problem hiding this comment.
This is a good first attempt! Some clarification needs to be added around the versioningStrategy and how it works.
| We can also define a `versioningStrategy` for your `ProductStack` deployment. | ||
| With the `Default` strategy, a new `ProductStack` is needed for each `productVersion` | ||
| and each `productVersion` will get overwritten with the latest changes to your `ProductStack`. | ||
| With the `RetainPreviousVersions` strategy, a local template will be retained for each `ProductStack` deployed. | ||
| Subsequently, any `ProductStack` changes will not overwrite the current version. | ||
| A new `productVersionName` must be specified in order for `ProductStack` changes to be deployed. | ||
| When the `ProductStack` is updated with a new version, the previous version can still be deployed. | ||
| The template of previously deployed `ProductStack` can be referenced using `fromProductStackContext` | ||
| and passing the corresponding `ProductStack` id. |
There was a problem hiding this comment.
what happens if a user specifies the Default strategy, adds several new productVersions, then changes the strategy to RetainPreviousVersions? What happens in the inverse (specifying RetainPreviousVersions, and then switching to Default)? Are the retained versions then lost, if the strategy changes to Default?
There was a problem hiding this comment.
This might be a sign of poor naming.
There was a problem hiding this comment.
If a user specifies the Default strategy then switches to RetainPreviousVersions. Only versions deployed after the switch will be retained.
If they do the reverse, then all further deployed versions will no longer be retained. Previously retained versions will not be deleted.
Do you have a suggestion on how we can name this behavior better?
There was a problem hiding this comment.
So RetainPreviousVersions and Default only apply to the version on which they are attached. I find this confusing because we have the concept of a Version referring to both a version of a product and a version of a version. I understand it's up to the user to attach semantics to these terms, but I'd prefer to see versions of a product called variants (or something better). @rix0rrr thoughts?
There was a problem hiding this comment.
Are you not trying to say something like:
const product = new CloudFormationProduct(this, 'Product', {
productVersions: [
{
productVersionName: "v1",
cloudFormationTemplate: CloudFormationTemplate.fromHistoricalSnapshot('v1'),
},
],
});?
The fact that it uses fromHistoricalSnapshot implies RETAIN_PREVIOUS_VERSIONS (because what else would you do).
| { | ||
| productVersionName: "v2", | ||
| cloudFormationTemplate: servicecatalog.CloudFormationTemplate.fromProductStack(new S3BucketProduct(this, 'S3BucketProduct')), | ||
| versioningStrategy: servicecatalog.VersioningStrategy.RETAIN_PREVIOUS_VERSIONS |
There was a problem hiding this comment.
this is a bit confusing. Why can the versioningStrategy be specified for each member of productVersions, instead of being set for the product? Setting v2 to have RETAIN_PREVIOUS_VERSIONS, and having v1 be DEFAULT, is confusing because RETAIN_PREVIOUS_VERSIONS implies that v1 will be retained, and I don't think that's the intent here. This would make more sense to me if the versioningStrategy was specified for the entire product, and not for each version.
There was a problem hiding this comment.
versioningStrategy only applies to fromProductStack looks like... so shouldn't it be part of the fromProductStack constructor?
There was a problem hiding this comment.
@rix0rrr is correct. The versioningStrategy currently only applies to ProductStacks.
The reason we cannot set it directly on the product is because we may have two separate productVersions for a single product.
For example a product may have:
v1_blue and v1_green, which can both be updated to a v2 version. We could have a strategy for the entire product, but that will enforce that strategy for each version. Making it tied to each version allows the user to have one use DEFAULT and another use RETAIN_PREVIOUS_VERSIONS
Setting a versioningStrategy on v1 would not do anything since it using fromProductStackContext. Think of the versioningStrategy as applying to that specific ProductStack rather than to a specific version.
The reason we choose not to make it apart of the fromProductStack constructor is because we want it leave it open to have future versioningStrategies that don't apply to just ProductStack. For example, we could add the RETAIN_PREVIOUS_VERSIONS or another strategy to fromAsset if there is a useful use case for it. However, I did originally have it in the constructor, but was suggested by Adam (left CDK team) to move it.
I can see how it is confusing, so if you think we should have it tied only to the fromProductStack constructor we can change this and we should still be able to make it more flexible in the future by adding an optional field to the other constructors.
There was a problem hiding this comment.
I'm not sure I agree versioningStrategy applies only to fromProductStack(). A Product Version from an Asset (created using fromAsset()), or referencing some file uploaded somewhere (created using fromUrl()) can be changed in the same way one created from ProductStack can be, and so you might want to prevent those changes from being deployed the same way you want to prevent changes to ProductStack from being deployed.
There was a problem hiding this comment.
Right we could extend the same functionality to fromAsset() or fromUrl(), however this PR itself is focusing on ProductStack since that was the issue a customer brought up with us.
With the other two the user can version their templates themselves either with additoinal local asset or from ie. git (and they pass in the additional urls).
The only way to version fromProductStack() is to create an entirely new ProductStack in code, which doesn't make sense the more versions we have and could result in a lot of duplicate code. We are trying to make it easier for them to do this by automatically saving their generated templates instead and making it easy to reference those templates.
There was a problem hiding this comment.
Not sure I understand. If you store the URL, you would also pass the URL for the Version for that Product Version.
There was a problem hiding this comment.
I mean when we try to read it from our local context. The user would only be passing in the productVersionName they would like to retrieve. We would need to determine if this productVersionName was stored from a productStack or from a URL (url would be stored in file instead of actual contents) as they would take different logic.
There was a problem hiding this comment.
No one is asking us to retain Assets or retain URLs, and it seems like its will add more complexity, edge cases and possibly unintended behavior for the user.
I think its better to focus on the customer ask here and only retain Product Stacks. ChangeVersioningStrategy to RetentionStrategy as suggested by Adam and scope it down to a field in fromProductStack()
There was a problem hiding this comment.
I mean when we try to read it from our local context. The user would only be passing in the productVersionName they would like to retrieve. We would need to determine if this productVersionName was stored from a productStack or from a URL (url would be stored in file instead of actual contents) as they would take different logic.
Of course, but that should be easy, right? We are the ones who control the format of these files, and the logic that both writes, and reads them. So I don't think there should be any issues with implementing this.
There was a problem hiding this comment.
I think we are trying to design in too much flexibility here. All the customer was asking for is a way for them to update their ProductStack and make sure that their previously deployed copies of that ProductStack don't get overwritten.
Since this whole "retain versions" feature is not implemented in the Service, but layered on top by CDK, we're naturally going to be limited in what we can do. Whether assets should be able to be snapshotted can be debated, I definitely do not intend to be able to snapshot arbitrary URLs. If it was necessary to snapshot arbitrary URLs, then users can wrap a script that downloads to a file and proceeds to use assets, or something else.
But honestly, I think a new API makes more sense, rather than trying to shoehorn this behavior into the existing API with flags.
| /** | ||
| * The strategy to use for a ProductStack deployment. | ||
| * Determines how a productVersion is saved and deployed. | ||
| */ |
There was a problem hiding this comment.
minor style comment, but can these opening comments (/** and */) line up?
| export enum VersioningStrategy { | ||
| /** | ||
| * Default Strategy for ProductStack deployment. | ||
| * This strategy will overwrite existing versions when deployed. |
There was a problem hiding this comment.
I don't understand these comments if a VersioningStrategy is defined for each version. This comment makes me think that this is defined per product; if one version has a retain strategy, and another version has the overwrite strategy, how do we decide which versions are retained? Are there versions of versions?
There was a problem hiding this comment.
There can be versions of a versions.
It is more like one ProductStack is using a retain strategy, and another ProductStack has the overwrite strategy. Both ProductStacks are "versions" of the product.
I know its a bit confusing, but there currently is not implementation of versioning in Service Catalog and ProductStacks are a CDK-only concept. So we are attempting to kind of version ProductStacks in CDK code, but once its deployed to Service Catalog, each template is just a version of the Product.
There was a problem hiding this comment.
I see. Effectively we have a product version A, B, C and then A, B and C can each have versions. I'd prefer we call product versions something other than versions, if we're also going to call the versions of versions "versions".
There was a problem hiding this comment.
I think the mistake here is the VersioningStrategy name, as it re-uses the term "version" to mean something else than does it now.
How about calling it RetentionStrategy instead, with two values: OVERRIDE (the default), and RETAIN?
There was a problem hiding this comment.
If we do this then let's call it OVERWRITE. But I favor a different type of API in general.
| } | ||
| break; | ||
| case TemplateType.PRODUCT_STACK_CONTEXT: | ||
| const templateFileKey = `${this.productPathUniqueId}.${template.productVersionDetails?.productStackId}.${productVersion.productVersionName}.product.template.json`; |
There was a problem hiding this comment.
what happens if template.productVersionDetails is undefined here?
There was a problem hiding this comment.
It can't be undefined since we determined that that this is the case TemplateType is PRODUCT_STACK_CONTEXT.
In the constructor for CloudFormationProductStackContextTemplate we create the productVersionDetails, so it will always be defined.
class CloudFormationProductStackContextTemplate extends CloudFormationTemplate {
private readonly productVersionDetails: ProductVersionDetails;
/**
* @param baseProductStackId The id of the product stack where the version was deployed from.
*/
constructor(public readonly baseProductStackId: string) {
super();
this.productVersionDetails = new ProductVersionDetails();
this.productVersionDetails.productStackId = this.baseProductStackId;
}
}
The reason there's an optional check here is because productVersionDetails can be optional as in its not required for other TemplateType.
There was a problem hiding this comment.
ahh okay, this makes sense. Thanks for clarifying. @rix0rrr should we use instanceof here instead? That would remove the ? operator here.
There was a problem hiding this comment.
Never instanceof! If you see instanceof it's a bug.
There was a problem hiding this comment.
@rix0rrr is that because inheritance should be used instead? Could you elaborate?
There was a problem hiding this comment.
It's because instanceof for type testing of custom classes is unreliable. It's too big of a topic to go in here, but we can talk about it in person if you want.
| productVersions.push( | ||
| { |
There was a problem hiding this comment.
| productVersions.push( | |
| { | |
| productVersions.push({ |
| ); | ||
| } |
There was a problem hiding this comment.
| ); | |
| } | |
| }); |
There was a problem hiding this comment.
ESLint is not happy and suggesting the original braces here.
Also the suggestion is moving the bracket before the parenthesis which doesn't make sense.
There was a problem hiding this comment.
ahh woops that was the wrong pair
| expect(assembly.stacks[0].assets.length).toBe(1); | ||
| }), | ||
|
|
||
| test('product test from product stack with versioning strategy retain', () => { |
There was a problem hiding this comment.
we need a test for the default case as well.
rix0rrr
left a comment
There was a problem hiding this comment.
This is a tough feature, and deviates A LOT from how CDK normally works.
Normally the service itself keeps product version histories (Lambda, CloudFormation) so CDK can just reason about the "current" state of the world and have the service keep track of changes.
I would urge you to reconsider implementing this in the service: not sure CDK is the place for it. If you want to push through, spend some more effort on naming and explaining the feature very well to users who haven't gone through the same design process you have and are intimately familiar with every idiosyncracity. Again: this is very different from how CDK usually behaves: be cognizant of that while explaining to users who are used to the usual way.
| }); | ||
| ``` | ||
|
|
||
| We can also define a `versioningStrategy` for your `ProductStack` deployment. |
There was a problem hiding this comment.
Lead with the use case instead of the mechanism. WHY would a user want to read this section? Also explain how it works in general, because this has implications on the user's workflow, not just on the API they need to call. Add a section header.
I'd recommend something like:
## Immutable products
The default behavior of Service Catalog is to overwrite each product version upon deployment.
If instead you want to never overwrite existing versions, but only add new versions... (etc)
...all product templates will be written to disk, so that they will still be available in the future
as the definition of the `ProductStack` subclass changes over time. **It is very important** that you commit these
old versions to source control...
| We can also define a `versioningStrategy` for your `ProductStack` deployment. | ||
| With the `Default` strategy, a new `ProductStack` is needed for each `productVersion` | ||
| and each `productVersion` will get overwritten with the latest changes to your `ProductStack`. | ||
| With the `RetainPreviousVersions` strategy, a local template will be retained for each `ProductStack` deployed. | ||
| Subsequently, any `ProductStack` changes will not overwrite the current version. | ||
| A new `productVersionName` must be specified in order for `ProductStack` changes to be deployed. | ||
| When the `ProductStack` is updated with a new version, the previous version can still be deployed. | ||
| The template of previously deployed `ProductStack` can be referenced using `fromProductStackContext` | ||
| and passing the corresponding `ProductStack` id. |
There was a problem hiding this comment.
This might be a sign of poor naming.
| With the `Default` strategy, a new `ProductStack` is needed for each `productVersion` | ||
| and each `productVersion` will get overwritten with the latest changes to your `ProductStack`. | ||
| With the `RetainPreviousVersions` strategy, a local template will be retained for each `ProductStack` deployed. | ||
| Subsequently, any `ProductStack` changes will not overwrite the current version. |
There was a problem hiding this comment.
What does the workflow look while you are developing/testing the latest ProductStack?
- Are you supposed to keep the most recent version at
Defaultwhile iterating on it, and then flip it toRetainPreviousVersionsas you are done? - From the code I'm reading, you must then remember to
synthone final time and then commit. - And then, for the next version, change it to a
fromTemplate, add a newfromProductStackwithRetainPreviousVersionsand repeat?
If that's what you are thinking, best to explain that here as well, because I'm not sure that's obvious to readers.
There was a problem hiding this comment.
(As you are explaining the process to users, I hope you will then internally go "hmm that seems like a sharp edge that I can shave off by redesigning/guard against with validation")
There was a problem hiding this comment.
The workflow would ideally have user stay with Default or stay with RetainPreviousVersions the whole way and not be constantly switching between the strategies.
If they are using RetainPreviousVersions. They would just update the version on their current fromProductStack ie. from v1tov2.
Then if they want the previous version to be deployed as well, they would have to add another version with fromProductStackContext
The end result would be the same as you explained, but I guess the workflow for the user would be to update their "current" version, then add any "previous" versions if they choose to, rather than change their "current" to use fromProductStackContext then add a new version.
There was a problem hiding this comment.
I think you are making the assumption that a user gets their new version of the ProductStack exactly right on the first attempt. Remember that if we have RetainPreviousVersions enabled, what happens on synth is:
- If no snapshot of the stack exists, we create it
- If a snapshot already exists, the current stack definition must be exactly equal to the snapshot
That means:
- If a user changes the product version name FIRST before changing the
ProductStackand (accidentally or to check something) runcdk synth, their stack definition is now locked in. They can't change it anymore! (And they haven't even begun to make their changes yet)- If a user is using
cdk watch, synthing is done continuously on their behalf. They may be synthing (and locking in their template) without even consciously taking an action.
- If a user is using
- Even if they made the changes to the
ProductStackbefore changing the version, and synth it, and potentially evendeployit to their developer account... their changes are now ALSO locked in. Iteration is effectively impossible, hope they got it right on the first try!
That means that in a realistic development workflow, people will have to switch back and forth between those versioning strategies to iterate.
💡 Here's a tip that will last you the rest of your career: Blog post driven development. Look at the AWS Blog and read a couple of posts. Notice how they all take the customer by the hand and say: "to use this feature, you first do X, then Y, then Z", and they don't skip any steps or handwave. For every feature you design henceforth, try to do the same. Pretend you are writing an AWS Blog Post, write in a friendly voice to the customer that has a particular problem or use case, telling them how awesome your feature is and that they "only" need to do X, Y and Z in that order (and don't skip anything). This will force you to be explicit about certain things you are probably handwaving away right now, and potentially cause you to scratch your chin and internally go "oh that isn't as easy as I thought after all".
| { | ||
| productVersionName: "v2", | ||
| cloudFormationTemplate: servicecatalog.CloudFormationTemplate.fromProductStack(new S3BucketProduct(this, 'S3BucketProduct')), | ||
| versioningStrategy: servicecatalog.VersioningStrategy.RETAIN_PREVIOUS_VERSIONS |
There was a problem hiding this comment.
versioningStrategy only applies to fromProductStack looks like... so shouldn't it be part of the fromProductStack constructor?
| }, | ||
| { | ||
| productVersionName: "v1", | ||
| cloudFormationTemplate: servicecatalog.CloudFormationTemplate.fromProductStackContext('S3BucketProduct'), |
There was a problem hiding this comment.
Why is this called Context ? This is confusing because it's not like CDK context, plus context is a vague term.
Why not fromProductStackSnapshot ?
There was a problem hiding this comment.
We can change this.
| /** | ||
| * Constant for the context directory to store retained ProductStack templates. | ||
| */ | ||
| export const PRODUCT_STACK_CONTEXT_DIRECTORY = 'product-stack-context'; |
There was a problem hiding this comment.
| export const PRODUCT_STACK_CONTEXT_DIRECTORY = 'product-stack-context'; | |
| export const PRODUCT_STACK_CONTEXT_DIRECTORY = 'product-stack-history'; |
?
There was a problem hiding this comment.
If we are changing it to fromProductStackSnapshot. Shouldn't this be product-stack-snapshots as well?
|
"This is a tough feature, and deviates A LOT from how CDK normally works. Normally the service itself keeps product version histories (Lambda, CloudFormation) so CDK can just reason about the "current" state of the world and have the service keep track of changes. I would urge you to reconsider implementing this in the service: not sure CDK is the place for it. If you want to push through, spend some more effort on naming and explaining the feature very well to users who haven't gone through the same design process you have and are intimately familiar with every idiosyncracity. Again: this is very different from how CDK usually behaves: be cognizant of that while explaining to users who are used to the usual way." @rix0rrr Sorry I should of included this design document I worked on with Adam Ruka that might help explain a bit more on the customer ask, motivations and some of the design choices we made: The reason we decided to use |
|
|
|
I see your point. This would depend on how Versioning is implemented in Service Catalog but it likely can be integrated with ProductStack. This is probably a big effort on Service Catalog side and would take time. Implementing it on CDK would be a quick win and would still allow the additional feature of disabling deployment from automatically overwriting of existing versions. To add on to this. It still is a uniquely CDK issue as in Service Catalog versions are not overwritten at all. You can only just keep adding new versions or delete old versions manually. However in CDK, if we make code changes and deploy. The current state reflected in CDK will end up overwriting the current state of versions in Service Catalog. |
|
Is it possible that the versioning in service catalog will break the versioning in CDK? |
|
|
| }); | ||
| }).toThrowError('Template MyProduct.ProductStack.v2.product.template.json cannot be found in product stack context'); | ||
| }), | ||
|
|
There was a problem hiding this comment.
I feel like we're missing a test here for when versioningStrategy is RETAIN, but the ProductStack code chages between the time when the template was saved on disk, and then synthesized again? (That should result in an exception being thrown)
Pull request has been modified.
|
Since there was no update on comments, I went ahead with a commit that updated naming and documentation, moved the renamed |
d2e2af0 to
90b18de
Compare
It's up to us to decide what a good workflow is. We are developers, we can put ourselves in the shoes of another developer. A developer workflow typically looks like this:
(Obviously there will probably be 2 such loops: one with the code on your developer desktop, and then another one with the code going through CI and running through the test environments). Here's my take:
That seems to imply to me: yes we deploy the current version from the |
rix0rrr
left a comment
There was a problem hiding this comment.
Thanks for rolling with my proposal! I like this a lot better. I think we can move more logic into the History class though, and keep the other ones simple.
| public static fromProductStackHistory(productStack: ProductStack, locked: boolean, directory?: string ): CloudFormationTemplate { | ||
| return new CloudFormationProductStackTemplate(productStack, locked, directory); | ||
| } | ||
|
|
||
| /** | ||
| * Creates a product from a previously deployed product stack snapshot. | ||
| */ | ||
| public static fromProductStackSnapshot(productStack: ProductStack, directory?: string ): CloudFormationTemplate { | ||
| return new CloudFormationProductStackSnapshotTemplate(productStack, directory); | ||
| } |
There was a problem hiding this comment.
I think both of these are not necessary anymore, as long as productStackHistory.versionFromSnapshot('v1') can return a subclass of CloudFormationTemplate.
There was a problem hiding this comment.
Okay I think we should be able to do that
| const productStackHistory = new servicecatalog.ProductStackHistory(this, 'ProductStackHistory', { | ||
| productStack: new S3BucketProduct(this, 'S3BucketProduct'), | ||
| currentVersionName: 'v2', | ||
| locked: true |
There was a problem hiding this comment.
After some consideration, I think currentVersionLocked would be a better name? WDYT?
There was a problem hiding this comment.
Sounds good we can change it to be more clear.
| * Creates a product with the resources defined in the given product stack and retains all previously deployed product stack versions. | ||
| */ | ||
| public static fromProductStackHistory(productStack: ProductStack, locked: boolean, directory?: string ): CloudFormationTemplate { | ||
| return new CloudFormationProductStackTemplate(productStack, locked, directory); |
There was a problem hiding this comment.
locked and directory don't have to go in here. Either the current ProductStack is valid (in which case it's not necessary here), or it's not (in which case the build will fail anyway).
There was a problem hiding this comment.
I think the issue is we use the locked and directory to determine if we need to write to snapshot and determine if we should deploy from snapshot as an asset or deploy it from our productStack.
If we are able to move the write logic (and possibly more logic) to the product-stack-history we might be able to remove this.
| constructor(public readonly productStack: ProductStack, | ||
| public readonly locked?: boolean, public readonly directory?: string) { | ||
| super(); |
There was a problem hiding this comment.
This class doesn't have to change.
| productVersionDetails.locked = this.locked; | ||
| productVersionDetails.directory = this.directory; |
There was a problem hiding this comment.
I'm only now noticing you are doing this with mutability as intent.
Relying on side effects is a bad idea: it will make the data flow and dependencies between pieces of code VERY hard to reason about. Make as much as you can readonly please, and don't do this.
A better way to do this would have been below:
public bind(_scope: Construct): CloudFormationTemplateConfig {
const productDetails = this.productStack._getProductVersionDetails();
return {
httpUrl: this.productStack._getTemplateUrl(),
productVersionDetails: {
...productDetails,
locked: this.locked,
directory: this.directory,
},
templateType: TemplateType.PRODUCT_STACK,
};
}That's a general advice. In this particular case, don't modify this class at all.
There was a problem hiding this comment.
I'll remove as many of these as possible. There is one case of productPathUniqueId that is only available when the Product is created and this value needs to be passed to ProductStack or ProductStackHistory to create a unique key for the file. The only link between the two is through cloudFormationTemplate, so I am unable to remove it in that instance (unless you have any ideas).
| /** | ||
| * ProductStackSnapshotTemplate | ||
| */ | ||
| PRODUCT_STACK_SNAPSHOT = 'ProductStackSnapshotTemplate' |
There was a problem hiding this comment.
Is this still relevant? Isn't the snapshot template ultimately the same as an ASSET template?
There was a problem hiding this comment.
Yes, there is a bit more logic in the SNAP_STACK_SNAPSHOT like a more detailed error when file is not found. Also currently ASSET (actual asset creation) is implemented in the bind method CloudFormationTemplate (path is passed by user).
I need to create the asset in the Product as we don't have access to the details to form the path in CloudFormationTemplate.
There was a problem hiding this comment.
Yes, there is a bit more logic in the SNAP_STACK_SNAPSHOT like a more detailed error when file is not found.
What I'm saying is that if you do:
history.snapshotVersion('v1')Then the implementation can look like:
class History {
public snapshotVersion(version: string) {
const snapshotFile = this.fileForVersion(version);
if (!fileExists(snapshotFile)) {
throw new Error('oh no you should have used this differently');
}
return Template.fromAsset(snapshotFile);
}
}The error message becomes part of the History class and can be as targeted and explicit as we need.
packages/@aws-cdk/aws-servicecatalog/lib/product-stack-history.ts
Outdated
Show resolved
Hide resolved
| */ | ||
| readonly productStack: ProductStack; | ||
| /** |
There was a problem hiding this comment.
Empty lines between the property and the start of the next comment block please. Look at other code and follow the style there.
| */ | ||
| export interface ProductStackHistoryProps { | ||
| /** | ||
| * The base Product Stack. |
There was a problem hiding this comment.
"The base" ?
What does "base" mean? Please talk in terms that customer using your API will care and think about. Something like:
The ProductStack whose history will be snapshotted to files.
Or something to that effect.
| if (this._productVersionDetails.locked !== undefined) { | ||
| this.writeTemplateToSnapshot(cfn); | ||
| } |
There was a problem hiding this comment.
Why doesn't the History class write and validate the snapshots?
There was a problem hiding this comment.
I think it could, it would involve giving the productStack access to it's History class and calling a method from it, I'll try to implement it.
| } | ||
| break; | ||
| case TemplateType.PRODUCT_STACK_CONTEXT: | ||
| const templateFileKey = `${this.productPathUniqueId}.${template.productVersionDetails?.productStackId}.${productVersion.productVersionName}.product.template.json`; |
There was a problem hiding this comment.
@rix0rrr is that because inheritance should be used instead? Could you elaborate?
packages/@aws-cdk/aws-servicecatalog/lib/product-stack-history.ts
Outdated
Show resolved
Hide resolved
Pull request has been modified.
|
@rix0rrr |
f13f4d4 to
e04d282
Compare
rix0rrr
left a comment
There was a problem hiding this comment.
Again, very close! I'd prefer the change to be additive as much as possible.
Rather than changing and exposing implementation details of existing classes, I'd prefer to introduce new classes that conform to existing public contracts. Potentially it's okay to extend the existing contracts if new features need to be added, and it's definitely possible that we may need to change something in existing classes... but it shouldn't be the default option.
| /** | ||
| * Template from a previously deployed product stack. | ||
| */ | ||
| export class CloudFormationProductStackSnapshotTemplate extends CloudFormationTemplate { |
There was a problem hiding this comment.
Please don't expose this class publicly. I feel it can go into product-stack-history.ts as a private class.
| /** | ||
| * Fetch the product version details. | ||
| * | ||
| * @internal | ||
| */ | ||
| public _getProductVersionDetails(): ProductVersionDetails { | ||
| return this._productVersionDetails; | ||
| } | ||
|
|
||
| /** | ||
| * Set the parent product stack history | ||
| * | ||
| * @internal | ||
| */ | ||
| public _setParentProductStackHistory(parentProductStackHistory: ProductStackHistory) { | ||
| return this._parentProductStackHistory = parentProductStackHistory; | ||
| } | ||
|
|
There was a problem hiding this comment.
Please try to find a way to implement this feature without changing ProductStack (or convince me it's not possible).
| } | ||
| break; | ||
| case TemplateType.PRODUCT_STACK_CONTEXT: | ||
| const templateFileKey = `${this.productPathUniqueId}.${template.productVersionDetails?.productStackId}.${productVersion.productVersionName}.product.template.json`; |
There was a problem hiding this comment.
It's because instanceof for type testing of custom classes is unreliable. It's too big of a topic to go in here, but we can talk about it in person if you want.
| const templateFilePath = path.join(productStackSnapshotDirectory, templateFileKey); | ||
| if (!fs.existsSync(templateFilePath)) { | ||
| throw new Error(`Template ${templateFileKey} cannot be found in ${productStackSnapshotDirectory}`); | ||
| } | ||
| httpUrl = new s3_assets.Asset(this, `Template${hashValues(templateFileKey)}`, { | ||
| path: templateFilePath, | ||
| }).httpUrl; |
There was a problem hiding this comment.
This is tying the contents of the ProductStack class a little too much to where it's being used, in my mind.
This code could go into the bind() of CloudFormationProductStackSnapshotTemplate , right? Wouldn't that behave exactly the same, except we don't need to change this class so much?
What I'm looking for is roughly that the output of bind() is ~what goes into the productVersions list (modulo some information that may come from this construct or some simple transformation).
| if (this._parentProductStackHistory) { | ||
| this._parentProductStackHistory._writeTemplateToSnapshot(cfn); | ||
| } |
There was a problem hiding this comment.
I think this can be replaced with:
class ProductStackHistory {
constructor(...) {
Aspects.of(this).addAspect({
visit: (c) => {
if (c === this) {
this.writeTemplateToSnapshot(props.currentProductStack);
}
}
});
}
}And that would get rid of most of the poking at the innards of existing classes.
|
@rix0rrr There are now no changes to |
|
Thank you for contributing! Your pull request will be updated from master and then merged automatically (do not update manually, and be sure to allow changes to be pushed to your fork). |
AWS CodeBuild CI Report
Powered by github-codebuild-logs, available on the AWS Serverless Application Repository |
|
Thank you for contributing! Your pull request will be updated from master and then merged automatically (do not update manually, and be sure to allow changes to be pushed to your fork). |
|
@wanjacki |
|
@padaszewski |
|
@wanjacki |
|
@padaszewski Yes, you should be able to downgrade using one stack (if I understand your scenario correctly). |
|
@wanjacki And about the naming conventions: the requirement is hat we have very specific and static bucket names in a certain account. For now this cannot be created in a dynamic manner. Thats why two stacks would fail to deploy. |
|
@wanjacki |
|
@padaszewski |
Adding enhancement to ProductStack to allow the specification of a VersioningStrategy.
VersioningStrategy
RetainPreviousVersionsadded to save previously deployed ProductStacks templates in a local context directory. These productVersions can then be easily be deployed using the stored templates.By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license