Skip to content
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

Support Condition Functions #222

Merged
merged 13 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions integration/examples_nodejs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,3 +264,16 @@ func bucketExists(ctx context.Context, client *s3.Client, bucketName string) (bo
}
return true, nil
}

func TestKinesis(t *testing.T) {
test := getJSBaseOptions(t).
With(integration.ProgramTestOptions{
Dir: filepath.Join(getCwd(t), "kinesis"),
ExtraRuntimeValidation: func(t *testing.T, stack integration.RuntimeValidationStackInfo) {
kinesisStreamName := stack.Outputs["kinesisStreamName"].(string)
assert.Containsf(t, kinesisStreamName, "mystream", "Kinesis stream name should contain 'mystream'")
},
})

integration.ProgramTest(t, &test)
}
3 changes: 3 additions & 0 deletions integration/kinesis/Pulumi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
name: pulumi-kinesis
runtime: nodejs
description: kinesis integration test
28 changes: 28 additions & 0 deletions integration/kinesis/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import * as pulumi from '@pulumi/pulumi';
import * as kinesis from 'aws-cdk-lib/aws-kinesis';
import * as pulumicdk from '@pulumi/cdk';
import { Duration } from 'aws-cdk-lib/core';

class KinesisStack extends pulumicdk.Stack {
kinesisStreamName: pulumi.Output<string>;

constructor(app: pulumicdk.App, id: string, options?: pulumicdk.StackOptions) {
super(app, id, options);

const kStream = new kinesis.Stream(this, 'my-stream', {
shardCount: 3,
retentionPeriod: Duration.hours(24),
})

this.kinesisStreamName = this.asOutput(kStream.streamName);
}
}

const app = new pulumicdk.App('app', (scope: pulumicdk.App) => {
const stack = new KinesisStack(scope, 'teststack');
return {
kinesisStreamName: stack.kinesisStreamName,
};
});

export const kinesisStreamName = app.outputs['kinesisStreamName'];
15 changes: 15 additions & 0 deletions integration/kinesis/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "pulumi-kinesis",
"devDependencies": {
"@types/node": "^10.0.0"
},
"dependencies": {
"@pulumi/aws": "^6.0.0",
"@pulumi/aws-native": "^1.8.0",
"@pulumi/cdk": "^0.5.0",
"@pulumi/pulumi": "^3.0.0",
"aws-cdk-lib": "2.156.0",
"constructs": "10.3.0",
"esbuild": "^0.24.0"
}
}
18 changes: 18 additions & 0 deletions integration/kinesis/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"compilerOptions": {
"strict": true,
"outDir": "bin",
"target": "es2019",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"experimentalDecorators": true,
"pretty": true,
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true,
"forceConsistentCasingInFileNames": true
},
"include": [
"./*.ts"
]
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@
"@types/glob": "^8.1.0",
"archiver": "^7.0.1",
"cdk-assets": "^2.154.8",
"fs-extra": "^11.2.0"
"fs-extra": "^11.2.0",
"fast-deep-equal": "^3.1.3"
},
"scripts": {
"set-version": "sed -i.bak -e \"s/\\${VERSION}/$(pulumictl get version --language javascript)/g\" package.json && rm package.json.bak",
Expand Down
14 changes: 13 additions & 1 deletion src/assembly/stack.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import * as path from 'path';
import { DestinationIdentifier, FileManifestEntry } from 'cdk-assets';
import { CloudFormationMapping, CloudFormationParameter, CloudFormationResource, CloudFormationTemplate } from '../cfn';
import {
CloudFormationMapping,
CloudFormationParameter,
CloudFormationResource,
CloudFormationTemplate,
CloudFormationCondition,
} from '../cfn';
import { ConstructTree, StackMetadata } from './types';
import { FileAssetPackaging, FileDestination } from 'aws-cdk-lib/cloud-assembly-schema';

Expand Down Expand Up @@ -116,10 +122,15 @@ export class StackManifest {
public readonly mappings?: CloudFormationMapping;

/**
* CloudFormation conditions from the template.
*
* @internal
*/
public readonly conditions?: { [id: string]: CloudFormationCondition };

private readonly metadata: StackMetadata;
public readonly dependencies: string[];

constructor(props: StackManifestProps) {
this.dependencies = props.dependencies;
this.outputs = props.template.Outputs;
Expand All @@ -133,6 +144,7 @@ export class StackManifest {
throw new Error('CloudFormation template has no resources!');
}
this.resources = props.template.Resources;
this.conditions = props.template.Conditions;
}

/**
Expand Down
13 changes: 12 additions & 1 deletion src/cfn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,21 @@ export type CloudFormationMappingValue = string | string[];
export type TopLevelMapping = { [key: string]: SecondLevelMapping };
export type SecondLevelMapping = { [key: string]: CloudFormationMappingValue };

/**
* Models CF conditions. These are possibly nested expressions evaluating to a boolean.
*
* Example value:
*
* {"Fn::Equals": [{"Ref": "EnvType"}, "prod"]}
*
* See https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/conditions-section-structure.html
*/
export interface CloudFormationCondition {}

export interface CloudFormationTemplate {
Parameters?: { [id: string]: CloudFormationParameter };
Resources?: { [id: string]: CloudFormationResource };
Conditions?: { [id: string]: any };
Conditions?: { [id: string]: CloudFormationCondition };
Mappings?: CloudFormationMapping;
Outputs?: { [id: string]: any };
}
Expand Down
39 changes: 38 additions & 1 deletion src/converters/app-converter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { parseSub } from '../sub';
import { getPartition } from '@pulumi/aws-native/getPartition';
import { mapToCustomResource } from '../custom-resource-mapping';
import { processSecretsManagerReferenceValue } from './secrets-manager-dynamic';
import * as intrinsics from "./intrinsics";

/**
* AppConverter will convert all CDK resources into Pulumi resources.
Expand Down Expand Up @@ -90,7 +91,7 @@ export class AppConverter {
/**
* StackConverter converts all of the resources in a CDK stack to Pulumi resources
*/
export class StackConverter extends ArtifactConverter {
export class StackConverter extends ArtifactConverter implements intrinsics.IntrinsicContext {
readonly parameters = new Map<string, any>();
readonly resources = new Map<string, Mapping<pulumi.Resource>>();
readonly constructs = new Map<ConstructInfo, pulumi.Resource>();
Expand Down Expand Up @@ -543,6 +544,18 @@ export class StackConverter extends ArtifactConverter {
}, this.processIntrinsics(params));
}

case 'Fn::Equals': {
return intrinsics.fnEquals.evaluate(this, params);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having separate functions for each intrinsic is much more understandable. Love it!

}
t0yv0 marked this conversation as resolved.
Show resolved Hide resolved

case 'Fn::If': {
return intrinsics.fnIf.evaluate(this, params);
}

case 'Fn::Or': {
return intrinsics.fnOr.evaluate(this, params);
}

default:
throw new Error(`unsupported intrinsic function ${fn} (params: ${JSON.stringify(params)})`);
}
Expand Down Expand Up @@ -633,4 +646,28 @@ export class StackConverter extends ArtifactConverter {
}
return d.value;
}

findCondition(conditionName: string): intrinsics.Expression|undefined {
if (conditionName in (this.stack.conditions||{})) {
return this.stack.conditions![conditionName];
} else {
return undefined;
}
}

evaluate(expression: intrinsics.Expression): intrinsics.Result<any> {
return this.processIntrinsics(expression);
}

fail(msg: string): intrinsics.Result<any> {
throw new Error(msg);
}

succeed<T>(r: T): intrinsics.Result<T> {
return <any>r;
}

apply<T,U>(result: intrinsics.Result<T>, fn: (value: U) => intrinsics.Result<U>): intrinsics.Result<U> {
return lift(fn, result);
}
}
Loading