How to get the URL of the ALB after creating in CDK? - amazon-elb

I'm creating an Application Load Balancer using the AWS CDK v2.
This is my code:
const lb = new elb.ApplicationLoadBalancer(this, 'LB', {
vpc: ec2.Vpc.fromLookup(this, 'vpc-lookup', {
isDefault: true
}),
internetFacing: true
});
const listener = lb.addListener('Listener', {
port: 80,
});
My question is how do I get the URL (DNS name) of the load balancer? I need it in the CDK after to update something

TL;DR The name's actual value is resolved at deploy-time. At synth-time, you can pass loadBalancerDnsName to other constructs and CDK will create the necessary references.
Resource identifiers like DNS addresses are generally known only only at deploy-time. The CDK uses Tokens to "represent values that can only be resolved at a later time in the lifecycle of an app" . ApplicationLoadBalancer's loadBalancerDnsName: string property is one of those properties whose value resolves to a string Token placeholder
at synth-time and an actual value at deploy-time.
Here's an example of passing the loadBalancerDnsName between constructs:
export class AlbStack extends cdk.Stack {
constructor(scope: Construct, id: string, props: cdk.StackProps) {
super(scope, id, props);
const alb = new elb.ApplicationLoadBalancer(this, 'MyALB', {
vpc: ec2.Vpc.fromLookup(this, 'DefaultVpc', { isDefault: true }),
});
// WON'T WORK: at synth-time, the name attribute returns a Token, not the expected DNS name
console.log(alb.loadBalancerDnsName); // ${Token[TOKEN.220]}
// WILL WORK - CDK will wire up the token in CloudFormation as
new ssm.StringParameter(this, 'MyAlbDns', {
stringValue: alb.loadBalancerDnsName,
});
}
}
The CDK's CloudFormation template output has Fn::GetAtt placeholder for the DNS name that resolves at deploy-time:
// CDK CloudFormation stack template
// Resources section
"MyAlbDnsFD44EB27": {
"Type": "AWS::SSM::Parameter",
"Properties": {
"Type": "String",
"Value": { "Fn::GetAtt": [ "MyALB911A8556", "DNSName" ] } // this will resolve to the string at deploy
},
"Metadata": {
"aws:cdk:path": "TsCdkPlaygroundAlbStack/MyAlbDns/Resource"
}
},

Related

cdk watch command forces full deploy with unrelated error on file change

I'm developing a little CDKv2 script to instantiate a few AWS services.
I have some lambda code deployed in the lambda/ folder and the frontend stored in a bucket populated using the frontend/ folder in the source.
I've noticed that whenever I make a change to any of the file inside these two, cdk watch return the following error and falls back to perform a full redeploy (which is significantly slow).
Could not perform a hotswap deployment, because the CloudFormation template could not be resolved: Parameter or resource 'DomainPrefix' could not be found for evaluation
Falling back to doing a full deployment
Is there any way to make changes in these folders only trigger updating the related bucket content or the related lambda?
Following here the stack.ts for quick reference, just in case here you can take a look at the repo.
export class CdkAuthWebappStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
const domainPrefixParam = new CfnParameter(this, 'DomainPrefix', {
type: 'String',
description: 'You have to set it in google cloud as well', //(TODO: add link to explain properly)
default: process.env.DOMAIN_NAME || ''
})
const googleClientIdParam = new CfnParameter(this, 'GoogleClientId', {
type: 'String',
description: 'From google project',
noEcho: true,
default: process.env.GOOGLE_CLIENT_ID || ''
})
const googleClientSecretParam = new CfnParameter(this, 'GoogleClientSecret', {
type: 'String',
description: 'From google project',
noEcho: true,
default: process.env.GOOGLE_CLIENT_SECRET || ''
})
if(!domainPrefixParam.value || !googleClientIdParam.value || !googleClientSecretParam.value){
throw new Error('Make sure you initialized DomainPrefix, GoogleClientId and GoogleClientSecret in the stack parameters')
}
const s3frontend = new s3.Bucket(this, 'Bucket', {
bucketName: domainPrefixParam.valueAsString+'-frontend-bucket',
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
encryption: s3.BucketEncryption.S3_MANAGED,
enforceSSL: true,
versioned: false,
removalPolicy: cdk.RemovalPolicy.DESTROY,
autoDeleteObjects: true,
websiteIndexDocument: "index.html",
});
//TODO: fare in modo che questa origin access identity non sia legacy quando deployo
const cfdistributionoriginaccessidentity = new cloudfront.OriginAccessIdentity(this, 'CFOriginAccessIdentity', {
comment: "Used to give bucket read to cloudfront"
})
const cfdistribution = new cloudfront.CloudFrontWebDistribution(this, 'CFDistributionFrontend', {
originConfigs: [
{
s3OriginSource: {
s3BucketSource: s3frontend,
originAccessIdentity: cfdistributionoriginaccessidentity
},
behaviors: [{
isDefaultBehavior: true,
allowedMethods: cloudfront.CloudFrontAllowedMethods.GET_HEAD_OPTIONS,
forwardedValues: {
queryString: true,
cookies: { forward: 'all' }
},
minTtl: cdk.Duration.seconds(0),
defaultTtl: cdk.Duration.seconds(3600),
maxTtl: cdk.Duration.seconds(86400)
}]
}
]
})
s3frontend.grantRead(cfdistributionoriginaccessidentity)
const cfdistributionpolicy = new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ['cloudfront:CreateInvalidation'],
resources: [`"arn:aws:cloudfront::${this.account}:distribution/${cfdistribution.distributionId}"`]
});
const userpool = new cognito.UserPool(this, 'WebAppUserPool', {
userPoolName: 'web-app-user-pool',
selfSignUpEnabled: false
})
const userpoolidentityprovidergoogle = new cognito.UserPoolIdentityProviderGoogle(this, 'WebAppUserPoolIdentityGoogle', {
clientId: googleClientIdParam.valueAsString,
clientSecret: googleClientSecretParam.valueAsString,
userPool: userpool,
attributeMapping: {
email: cognito.ProviderAttribute.GOOGLE_EMAIL
},
scopes: [ 'email' ]
})
// this is used to make the hostedui reachable
userpool.addDomain('Domain', {
cognitoDomain: {
domainPrefix: domainPrefixParam.valueAsString
}
})
const CLOUDFRONT_PUBLIC_URL = `https://${cfdistribution.distributionDomainName}/`
const client = userpool.addClient('Client', {
oAuth: {
flows: {
authorizationCodeGrant: true
},
callbackUrls: [
CLOUDFRONT_PUBLIC_URL
],
logoutUrls: [
CLOUDFRONT_PUBLIC_URL
],
scopes: [
cognito.OAuthScope.EMAIL,
cognito.OAuthScope.OPENID,
cognito.OAuthScope.PHONE
]
},
supportedIdentityProviders: [
cognito.UserPoolClientIdentityProvider.GOOGLE
]
})
client.node.addDependency(userpoolidentityprovidergoogle)
// defines an AWS Lambda resource
const securedlambda = new lambda.Function(this, 'AuhtorizedRequestsHandler', {
runtime: lambda.Runtime.NODEJS_14_X,
code: lambda.Code.fromAsset('lambda'),
handler: 'secured.handler'
});
const lambdaapiintegration = new apigw.LambdaIntegration(securedlambda)
const backendapigw = new apigw.RestApi(this, 'AuthorizedRequestAPI', {
restApiName: domainPrefixParam.valueAsString,
defaultCorsPreflightOptions: {
"allowOrigins": apigw.Cors.ALL_ORIGINS,
"allowMethods": apigw.Cors.ALL_METHODS,
}
})
const backendapiauthorizer = new apigw.CognitoUserPoolsAuthorizer(this, 'BackendAPIAuthorizer', {
cognitoUserPools: [userpool]
})
const authorizedresource = backendapigw.root.addMethod('GET', lambdaapiintegration, {
authorizer: backendapiauthorizer,
authorizationType: apigw.AuthorizationType.COGNITO
})
const s3deploymentfrontend = new s3deployment.BucketDeployment(this, 'DeployFrontEnd', {
sources: [
s3deployment.Source.asset('./frontend'),
s3deployment.Source.data('constants.js', `const constants = {domainPrefix:'${domainPrefixParam.valueAsString}', region:'${this.region}', cognito_client_id:'${client.userPoolClientId}', apigw_id:'${backendapigw.restApiId}'}`)
],
destinationBucket: s3frontend,
distribution: cfdistribution
})
new cdk.CfnOutput(this, 'YourPublicCloudFrontURL', {
value: CLOUDFRONT_PUBLIC_URL,
description: 'Navigate to the URL to access your deployed application'
})
}
}
Recording the solution from the comments:
Cause:
cdk watch apparently does not work with template parameters. I guess this is because the default --hotswap option bypasses CloudFormation and deploys instead via SDK commands.
Solution:
Remove the CfnParamters from the template. CDK recommends not using parameters in any case.
Perhaps cdk watch --no-hotswap would also work?

Nest + Swagger not recognizing endpoints or parameters

I'm having an issue with Nest + Swagger. When I open my swagger docs I see all of the endpoints I expect but am having two issues:
When I click on a method it expands all of the methods for that controller
The post method says No parameters despite having a DTO defined for the body
Ultimately I think the issue is: Swagger + Nest is not creating unique operationId's for each method. My understanding is that methods will only fail to get unique operationId's when they are not sufficiently unique: 2 methods with identical call signatures for example.
In the past when I've had issues like this it was either because I was missing the #ApiTags decorator, or I had accidentally included duplicate endpoints.
In general it feels like I missed a step in configuring Swagger, or I didn't set it up properly with Fastify. I installed fastify-swagger but I'm not actually using it anywhere, but according the docs on Nest's site the route for the swagger JSON should be /api/json when using Fastify, which it is for me.
Things that didn't work:
Annotating method with unique #ApiOperation
Adding a addTag to the DocumentBuilder chain
Deleting the swagger-ui-express and #nestjs/platform-express dependencies
Removing all of the Fastify deps and switching to the Express equivalents
Update:
Adding #ApiOperation({ operationId: 'test' }) to a method does fix this, but I was under impression that #nest/swagger did this automatically. Are my methods not unique enough?
main.ts
async function bootstrap() {
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter(),
);
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
}),
);
app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector))); // allows automatic serialization
app.enableCors();
const config = new DocumentBuilder().setTitle('PIM API').build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('docs', app, document);
await app.listen(process.env.APP_PORT || 3001);
}
bootstrap();
some controller
#ApiTags('chat-messages')
#Controller('chat-messages')
export class ChatMessagesController {
constructor(
private readonly service: ChatMessagesService,
) {}
#Post()
create(#Body() createChatMessageDto: CreateChatMessageDto) {
return this.service.create(createChatMessageDto);
}
#Get(':stream_id')
findByStreamId(#Param('stream_id') streamId: string) {
return this.service.findByStreamId(streamId);
}
ChatMessageDto
export class CreateChatMessageDto {
constructor(partial: Partial<CreateChatMessageDto>) {
Object.assign(this, partial);
}
#IsString()
value: string;
#IsRFC3339()
timestamp: Date;
#IsNotEmpty()
streamId: string;
}
Swagger JSON
{
"/chat-messages":{
"post":{
"operationId":"ChatMessagesController_",
"parameters":[
],
"responses":{
"201":{
"description":"",
"content":{
"application/json":{
"schema":{
"$ref":"#/components/schemas/ChatMessage"
}
}
}
}
},
"tags":[
"chat-messages"
]
}
},
"/chat-messages/{stream_id}":{
"get":{
"operationId":"ChatMessagesController_",
"parameters":[
],
"responses":{
"200":{
"description":"",
"content":{
"application/json":{
"schema":{
"type":"array",
"items":{
"$ref":"#/components/schemas/ChatMessage"
}
}
}
}
}
},
"tags":[
"chat-messages"
]
}
}
}
Did you try putting #ApiProperty in your dto ?
Like this:
export class CreateChatMessageDto {
constructor(partial: Partial<CreateChatMessageDto>) {
Object.assign(this, partial);
}
#ApiProperty()
#IsString()
value: string;
#ApiProperty()
#IsRFC3339()
timestamp: Date;
#ApiProperty()
#IsNotEmpty()
streamId: string;
}
This allows Swagger to see the properties.
This is what NestJs recommends in their documentation See here

How edit asset name?

How i can edit asset name? its doesnt work. Thanks
let assetService = $injector.get(self.ctx.servicesMap.get('assetService'));
let activeID = self.ctx.data[0].datasource.entityId
let tenantId = self.ctx.dashboard.authUser.tenantId
let asset = {
additionalInfo: null,
createdTime: 1599121131415, // временно
customerId: {
entityType: "CUSTOMER",
id: self.ctx.dashboard.authUser.customerId
},
id: {
entityType: "ASSET",
id: activeID
},
label: null,
name: "kuku", // временно
tenantId: {
entityType: "TENANT",
id: tenantId
},
type: "справочник"
}
assetService.saveAsset(asset)
Thingsboard is built using Angular 10 currently See releases. You correctly injected the Angular service 'assetService'. You need to follow the Angular method of subscribing to the observable from assetService.
Calling
assetService.saveAsset(asset)
without subscribing means nothing happens. From the Angular University Blog
The multiple versions of the Angular HTTP module all have an RxJS Observable-based API. This means that the multiple calls to the HTTP module will all return an observable, that we need to subscribe to one way or the other.
So here's the code to 'subscribe' to the observable described above
assetService.saveAsset(asset).subscribe(
(response) => {
console.log(
"saveAsset call Success:",
response);
},
response => {
console.log(
"saveAsset call Error:",
response);
},
() => {
console.log(
"saveAsset observable Complete"
);
});
Let me know if there's a mistake in the code above, I didn't test it. And thanks for your question Anzor - it led me to a solution to make a custom Thingsboard widget along with the Widgets Development Guide.

"Client network socket disconnected before secure TLS connection was established" - Neo4j/GraphQL

Starting up NestJS & GraphQL using yarn start:dev using await app.listen(3200);. When trying to connect to my Neo4J Desktop, I get this error trying to get my queries at localhost:3200/graphQL:
"errors": [
{
"message": "Client network socket disconnected before secure TLS connection was established",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"getMovies"
],
"extensions": {
"code": "INTERNAL_SERVER_ERROR",
"exception": {
"code": "ServiceUnavailable",
"name": "Neo4jError"
}
}
}
],
"data": null
}
So I figured my local Neo4J desktop graph is not running correctly, but I can't seem to find any answer how to solve it.. Currently I have a config.ts file which has:
export const HOSTNAME = 'localhost';
export const NEO4J_USER = 'neo4j';
export const NEO4J_PASSWORD = '123';
and a file neogql.resolver.ts:
import {
Resolver,
Query,
Args,
ResolveProperty,
Parent,
} from '#nestjs/graphql';
import { HOSTNAME, NEO4J_USER, NEO4J_PASSWORD } from '../config';
import { Movie } from '../graphql';
import { Connection, relation, node } from 'cypher-query-builder';
import { NotFoundException } from '#nestjs/common';
const db = new Connection(`bolt://${HOSTNAME}`, {
username: NEO4J_USER,
password: NEO4J_PASSWORD,
});
#Resolver('Movie')
export class NeogqlResolver {
#Query()
async getMovies(): Promise<Movie> {
const movies = (await db
.matchNode('movies', 'Movie')
.return([
{
movies: [{ id: 'id', title: 'title', year: 'year' }],
},
])
.run()) as any;
return movies;
}
#Query('movie')
async getMovieById(
#Args('id')
id: string,
): Promise<any> {
const movie = (await db
.matchNode('movie', 'Movie')
.where({ 'movie.id': id })
.return([
{
movie: [{ id: 'id', title: 'title', year: 'year' }],
},
])
.run<any>()) as any;
if (movie.length === 0) {
throw new NotFoundException(
`Movie id '${id}' does not exist in database `,
);
}
return movie[0];
}
#ResolveProperty()
async actors(#Parent() movie: any) {
const { id } = movie;
return (await db
.match([node('actors', 'Actor'), relation('in'), node('movie', 'Movie')])
.where({ 'movie.id': id })
.return([
{
actors: [
{
id: 'id',
name: 'name',
born: 'born',
},
],
},
])
.run()) as any;
}
}
Be sure to pass the Config object like this:
var hostname = this.configService.get<string>('NEO4J_URL');
var username = this.configService.get<string>('NEO4J_USERNAME');
var password = this.configService.get<string>('NEO4J_PASSWORD');
db = new Connection(`${hostname}`, {
username: username,
password: password,
}, {
driverConfig: { encrypted: "ENCRYPTION_OFF" }
});
I had the same problem with grandSTACK when running against a neo4j version 4 server. According to Will Lyon this is due to mismatched encryption defaults between driver and database: https://community.neo4j.com/t/migrating-an-old-grandstack-project-to-neo4j-4/16911/2
So passing a config object with
{ encrypted: "ENCRYPTION_OFF"}
to the Connection constructor should do the trick.

executing query via middleware

While using strongloop loopback I want check the database for accesstoken and username existence in every request.
So i am making a middleware code:
module.exports = function() {
return function xAuth(req, res, next) {
console.log(req);
};
};
I have added it to :initial" middleware json
"initial": {
"compression": {},
"cors": {
"params": {
"origin": true,
"credentials": true,
"maxAge": 86400
}
},
"./middleware/trumptAuth": {},
"helmet#xssFilter": {},
"helmet#frameguard": {
"params": [
"deny"
]
},
"helmet#hsts": {
"params": {
"maxAge": 0,
"includeSubdomains": true
}
},
"helmet#hidePoweredBy": {},
"helmet#ieNoOpen": {},
"helmet#noSniff": {},
"helmet#noCache": {
"enabled": false
}
}
i want to execute an sql query here but i have no idea on how can i do that, I probably just need "app" variable access or directly "dataSource" access.
Any help is appreciated.
From the docs, "Using variables in middleware" (https://docs.strongloop.com/display/LB/Defining+middleware#Definingmiddleware-Usingvariablesinvalues), it looks like you can pass any part of the app object using this syntax: ${var}. The datasource property exists under the core app object, so you should be able to pass it in that way.
To access "app" variable from middleware use
app.use(function(req, res, next) {
var app = req.app;
...
});
To access "dataSource" variable from middleware use
app.use(function(req, res, next) {
var dataSource = app.datasources.db;
...
});
To access an specific model from middleware use
app.use(function(req, res, next) {
var app = req.app;
var modelName = app.model.modelName;
...
});
for more methods/sources use this documentation link
https://docs.strongloop.com/display/public/LB/Working+with+LoopBack+objects

Resources