I want to use an existing keypair when creating my ec2 instance using CDK , but I can not find any clear reference on how this is done. I do know it is an InstanceProps.
The only item I seen on this site is in reference to a bastion host. this is not what I am trying to acheive.
This is the error I get....
How would I add reference to keyname: mykey
thanks for any help given!!
import ec2 = require('#aws-cdk/aws-ec2');
export class Ec2Stack extends cdk.Stack {
constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
super(scope, id, props) ;
// Look up VPC to use
const vpc = ec2.Vpc.fromLookup(this, 'vpc-0407979789789', {
vpcName: 'vpc-my-test'
});
// Lookup Existing SecurityGroup
const sg = ec2.SecurityGroup.fromSecurityGroupId(this, 'default', 'sg-06fea98uu9')
const instance = new ec2.Instance(this, 'Instance', {
vpc: vpc,
securityGroup: sg,
KeyName: mykey,
instanceType: new ec2.InstanceType('t2.micro'),
machineImage: new ec2.GenericLinuxImage({
'us-west-2': 'ami-0e8c04af2729ff1bb'
}),
});
}
}
CDK/CFn do not support EC2 keypair. Cannot create nor import/reference. Need to hard-code the keypair name.
Add ability to add EC2 key-pair #5252
Related
I am wondering if it is possible to run Spring Boot test inside a Docker container. I would like to test my service in the environment as close to the real one as possible. I am already using Testcontainers and Localstack to setup my environment (Postgres, AWS secrets manager etc.), but couldn't find any way to run my service inside Docker. Ideally I would like to test my service inside Localstack's EC2 instance, but just testing it as Docker container would be sufficient. I have Dockerfile at the root of my project and am using Gradle Palantir plugin to build the container. The bootstrap section of my test:
#Testcontainers
#ActiveProfiles(profiles = {"test","jpa"})
#SpringBootTest(classes = PayoutsApplication.class, webEnvironment=WebEnvironment.RANDOM_PORT,
properties = {"aws.paramstore.enabled=false", "aws.secretsmanager.enabled=false"})
#EnableAutoConfiguration(exclude = {
org.springframework.cloud.aws.autoconfigure.context.ContextInstanceDataAutoConfiguration.class,
org.springframework.cloud.aws.autoconfigure.context.ContextStackAutoConfiguration.class,
org.springframework.cloud.aws.autoconfigure.context.ContextRegionProviderAutoConfiguration.class
})
#TestMethodOrder(OrderAnnotation.class)
#ContextConfiguration(loader = PayoutsApplicationTests.CustomLoader.class)
#TestExecutionListeners(listeners = {DependencyInjectionTestExecutionListener.class, PayoutsApplicationTests.class})
class MyApplicationTests extends AbstractTestExecutionListener {
private static AWSSecretsManager awsSecretsManager;
private static AWSSimpleSystemsManagement ssmClient;
// we need it to execute the listeners
public static class CustomLoader extends SpringBootContextLoader {
#Override
protected SpringApplication getSpringApplication() {
PropertiesListener listener = new PropertiesListener();
ReflectionTestUtils.setField(listener, "awsSecretsManager", awsSecretsManager);
ReflectionTestUtils.setField(listener, "ssmClient", ssmClient);
SpringApplication app = super.getSpringApplication();
app.addListeners(listener);
return app;
}
}
static DockerImageName localstackImage = DockerImageName.parse("localstack/localstack:0.11.3");
private static final LocalStackContainer.Service[] TEST_SERVICES = {
LocalStackContainer.Service.SECRETSMANAGER,
LocalStackContainer.Service.SSM,
LocalStackContainer.Service.EC2,
};
static LocalStackContainer awsLocalStackContainers = new LocalStackContainer(localstackImage)
.withServices(TEST_SERVICES).withEnv("LOCALSTACK_HOSTNAME", "localhost")
.withEnv("HOSTNAME", "localhost");
#Container
public static PostgreSQLContainer<?> postgreSQL =
new PostgreSQLContainer<>("postgres:13.1")
.withUsername("clusteradmin")
.withPassword("testPassword")
.withDatabaseName("public");
#DynamicPropertySource
static void postgresqlProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgreSQL::getJdbcUrl);
registry.add("spring.datasource.password", postgreSQL::getPassword);
registry.add("spring.datasource.username", postgreSQL::getUsername);
}
...
I've got two Constructs being created in one Stack, and during synthesis I would like to merge a bunch of properties and use the merged result.
The reason for this is to split creation of resources into a specific look in the Stack. In other words, I want to initialize two Constructs in the one Stack, but when CDK deploys the stack, I would like properties from both to be merged, with the resource(s) created in one of the Stacks, (Environment in my example below) being able to use the merged properties from both Constructs.
Here is where I've got to so far, but when running a cdk synth the resulting someProperties object does not reflect the items I've sent through from my ExampleThing Construct that was initialized in the same Stack.
export class IExampleEnvironment extends Construct {
readonly someId?: string;
}
export interface ExampleProps {
readonly environment: Environment;
readonly a: string;
readonly b: string;
}
export class Environment extends IExampleEnvironment {
private someProperties: any;
constructor(scope: Construct, id: string, props: StackProps) {
super(scope, id);
new ExampleResourceThatUsesSomeProperties(this, 'Example' {
foo: this.someProperties // this is where I would like to use some properties that are brought in from another Construct (in the same stack as Environment)
});
}
public addSomeProperties(someProps: ExampleProps) {
this.someProperties = { A: someProps.a, B: someProps.b };
}
}
export class ExampleThing extends Construct {
constructor(scope: Construct, id: string, props: ExampleProps) {
super(scope, id);
props.environment.addSomeProperties(props);
}
}
// Creating the Stack...
const environment = new Environment(this, 'Environment', { someId: "123" });
const exampleThing = new ExampleThing(this, 'ExampleThing', props);
Currently, I'm using a reference to the Environment property sent in, and then calling the internal addSomeProperties() method on it, hoping that would work. I suspect this is my issue, but I'm not sure of how to achieve this.
The important thing here is that the Environment stack must be created first, as the ExampleThing stack needs to supply a environment property as part of its ExampleProps and pass the Environment in for that.
Is there a better way that would work?
So I'll admit this is a very niche requirement, and not really the way CDK is meant to be used, but I wanted to get this very specific use case working.
I dug in a little more and found that it is possible by overriding the prepare() method in the Construct. Here is my working example, which shows how one construct can create an S3 bucket, and another can update this instance's S3 bucket purely by creating an instance and passing in the other instance:
thing.ts:
import { Construct } from "#aws-cdk/core";
import { Foo } from "./foo";
export interface ExampleThingProps {
readonly foo: Foo;
readonly a: string;
readonly b?: string;
}
export class ExampleThing extends Construct {
constructor(scope: Construct, id: string, props: ExampleThingProps) {
super(scope, id);
props.foo.addSomeProperties(props);
}
}
foo.ts:
import { Construct, StackProps } from "#aws-cdk/core";
import * as s3 from "#aws-cdk/aws-s3";
import { ExampleThingProps } from "./thing";
import { IBucket } from "#aws-cdk/aws-s3";
export class IExampleFoo extends Construct {
readonly someId?: string;
}
export interface FooProps {
bucketName: string;
}
export class Foo extends IExampleFoo {
private someProperties: any;
private theBucket: IBucket;
constructor(scope: Construct, id: string, props: FooProps) {
super(scope, id);
this.someProperties = { A: props.bucketName };
this.theBucket = new s3.Bucket(this, 'ExampleBucket', {
bucketName: this.someProperties.A
});
}
prepare() {
const firstBucket = this.node.tryFindChild("ExampleBucket");
if (firstBucket) {
console.log('found first bucket node: ' + firstBucket.node.uniqueId);
const removed = this.node.tryRemoveChild(firstBucket.node.id);
console.log('removed the found node: ' + removed);
}
this.theBucket = new s3.Bucket(this, 'ExampleBucket', {
bucketName: this.someProperties.A
});
}
public addSomeProperties(someProps: ExampleThingProps) {
console.log('updates the existing properties...');
this.someProperties = { A: someProps.a, B: someProps.b };
}
}
Usage:
const foo = new Foo(this, 'Foo', {
bucketName: "this-is-the-name-set-from-foo-construction"
});
const thing = new ExampleThing(this, 'Thing', {
foo: foo,
a: "shiny-new-name"
});
If you don't initialize a new ExampleThing then the bucket will be named this-is-the-name-set-from-foo-construction.
If you initialize a new ExampleThing, then the bucket's name will be changed for synthesize (and will apply in a cdk diff or deploy and be named shiny-new-name.
I think you are looking to create dependencies between the resources, here is an example
const app = new App();
const myFirstStack = new MyFirstStack(
app,
stackNameone
);
const mySecondStack = new MySecondStack(
app,
stackNametwo
);
mySecondStack.addDependency(myFirstStack, "Depends on resources");
You can do this with most resources in CDK so whichever resources you want created first add it as a dependency like the above example.
According to this bug and this bug, ssm.StringListParameter doesn't work in the CDK due to some issues with CFT.
I need to be able to export an arbitrarily length list of subnetIds from one stack and import the list into another using SSM, as the lifetime of subnetIds is completely different than the lifetime of the consumer of subnetIds in a second stack (especially in production, though not in a sandbox). I cannot hard code the subnet IDs, as when creating a sandbox the IDs would vary from sandbox to sandbox. I want the latest version of whatever is in the SSM key.
However, the bugs appear to not be resolvable, and I cannot find a work around.
I tried serializing using JSON, but the item passed around the code is is a late binding Token, which is treated as a string, and the deserialized items is a string [], so it's not possible to get such code to compile.
Here's an example of what I attempted. It doesn't compile:
export function getOtherStackOutputList(stack: cdk.Stack, mangledOtherStackName: string, key: string): string [] {
const globallyUniqueKey = `/${mangledOtherStackName}/${key}`;
const jsonResult = ssm.StringParameter.fromStringParameterName(stack, key + 'SSM', globallyUniqueKey).stringValue;
if (cdk.Token.isUnresolved(jsonResult)) {
return jsonResult;
} else {
const result = JSON.parse(jsonResult);
return result;
}
};
which would be used by code that looks like this:
const efsVpcIsolatedSubnetsIds = StackValueShare.getOtherStackOutputList(this, mangledStackName + efsDbStackSuffix,
'efsTimeSeriesDatabaseVpcIsolatedSubnets');
This works for me:
import { Construct } from '#aws-cdk/core';
import { AwsCustomResource, AwsSdkCall } from '#aws-cdk/custom-resources';
import iam = require("#aws-cdk/aws-iam");
interface SSMParameterReaderProps {
parameterName: string;
region: string;
}
export class SSMParameterReader extends AwsCustomResource {
constructor(scope: Construct, name: string, props: SSMParameterReaderProps) {
const { parameterName, region } = props;
const ssmAwsSdkCall: AwsSdkCall = {
service: 'SSM',
action: 'getParameter',
parameters: {
Name: parameterName
},
region,
physicalResourceId: {id:Date.now().toString()} // Update physical id to always fetch the latest version
};
super(scope, name, { onUpdate: ssmAwsSdkCall,policy:{
statements:[new iam.PolicyStatement({
resources : ['*'],
actions : ['ssm:GetParameter'],
effect:iam.Effect.ALLOW,
}
)]
}});
}
public getParameterValue(): string {
return this.getResponseField('Parameter.Value').toString();
}
}
Suppose i have a resource, say stepfunction activity, in one stack. How can i access it's arn in other stack?
I found CfnConstruct suitables for exporting (https://docs.aws.amazon.com/cdk/api/latest/docs/#aws-cdk_core.CfnOutput.html). As of now i have used CfnConstruct to export it:
this.initiateValidationActivityArn = new cdk.CfnOutput(this, 'InitiateValidationActivityArn', {
value: igvsStateMachine.initiateValidationActivity.activityArn
});
Now how can i access it in other file. I have tried this:
ecsService.initiateValidationActivityArn.value
But value is private to construct so can't use it.
If you have the stacks in one deployable cdk-app, you can access the property value from the stack by making it accessible from outside / not-private.
What I recommend doing is to keep it readonly so that it can't be re-initialized from outside the stack.
// file: ./lib/first-stack.ts
// import statements
export class FirstStack extends Stack {
readonly vpc: IVpc;
constructor(...) {
// setup
this.vpc = new Vpc(this, "VPC", {...});
}
}
In the dependent stack, you can pass it via (custom) props.
// file: ./lib/second-stack.ts
export interface SecondStackProps extends StackProps {
importedVpc: IVpc;
}
export class SecondStack extends Stack {
constructor(scope: cdk.Construct, id: string, props: SecondStackProps) {
super(scope, id, props);
const importedVpc = props.importedVpc;
// do sth. with your imported resource
}
}
Why is that working you may ask...?
It works because CDK generates the Resource IDs and during the synth phase it can put the tokens for the resources inside the generated templates.
This doesn't work, when you have separated cdk-apps / deployments of stacks, because CDK can't resolve the (existing) Resource ID Tokens during cdk-synth.
With that, you need to have another approach:
// file: ./lib/first-stack-separated-deployment.ts
// import statements
export class FirstStackSeparatedDeployment extends Stack {
cfnVpcId: CfnOutput;
constructor(...) {
// setup
const vpc = new Vpc(this, "VPC", {...});
this.cfnVpcId= new cdk.CfnOutput(this, "FirstStackCfnVpcId", {
value: vpc.vpcId,
exportName: "UniqueNameForYourVpcId"
});
}
}
In the other stack, which requires the already deployed resource, you do the following:
// file: ./lib/second-stack-separated-deployment.ts
export class SecondStackSeparatedDeployment extends Stack {
constructor(...) {
// setup
const vpcId = Fn.importValue("UniqueNameForYourVpcId")
const importedVpc = ec2.Vpc.fromVpcAttributes(this, "ImportedVpc", {
vpcId: vpcId,
availabilityZones: [this.region],
})
// proceed further...
}
}
Basically, you take the exportName from the CfnOutput Construct as an identifier and import it via the core Fn.importValue.
With that approach, the first stack already needs to be deployed.
Is it possible to retrieve any #Injectable like:
var config = #Inject(Config);
Thanks
If you want to get new instances from dependency injection, you need a reference to the Injector then you can acquire new instances with
injector.get(Config);
How you can get a reference to Injector depends on where your code is.
In an Angular component or service, just inject it
constructor(private injector:Injector) {}
You can also just create your own injector like
var injector = Injector.resolveAndCreate([Car, Engine]);
where Car and Engine are the providers the injector can create instances for.
To get the injector of your Angular application to be used outside your Angular application you can use
Example from https://github.com/angular/angular/issues/4112#issuecomment-153811572
let appInjectorRef: Injector;
export const appInjector = (injector?: Injector):Injector => {
if (injector) {
appInjectorRef = injector;
}
return appInjectorRef;
};
bootstrap(App, [
Auth,
HTTP_PROVIDERS,
ROUTER_PROVIDERS,
Car,
Engine
]).then((appRef: ComponentRef) => {
// store a reference to the application injector
appInjector(appRef.injector);
});
let injector: Injector = appInjector();
injector.get(Car);
I found it easier to declare injector as a global variable so I could use it a bit easier.
In the file where you bootstrap angular:
declare global {
var injector: Injector;
}
bootstrap(/* your bootstrap stuff */).then((appRef) => {
injector = appRef.injector;
});
The above will give you access to an injector variable anywhere else in your code.
In the file where you need an instance of Config:
import { Config } from './path/to/config.service';
class TestClass {
private config: Config;
constructor() {
this.config = injector.get(Config);
}
}