AWS CDK parameter error: Value of property Parameters must be an object with String (or simple type) properties - aws-cloudformation

Update
The accepted answer bellow by Victor is the correct workaround.
I get in contact with AWS Support that confirmed the bug. They are aware of the issue and linked me to those 2 links to follow the case.
... During my investigation and replication of the issue, I noticed that this issue seems to be with how CDK handles the VPCEndpoint resource as its parameters include a list of DNS entries with them.
This is a known issue with the resource:
https://github.com/aws/aws-cdk/issues/5897
https://github.com/aws/aws-cdk/issues/9488
Until then if you face this problem, the solution is here.
Original question
I was expecting to be able to use Fn.select and Fn.split out of the box, but the bellow code have some very weird behavior, wondering if I'm doing something unexpected.
I expected the output to contains the declared functions fn::Select [1, fn:split [ fn:select [0, getAttr [ something ] ] ] ]
But actually what I see is just getAttr [something], what leads to Value of property Parameters must be an object with String (or simple type) properties during deployment
Any help is appreciated
Here's the code to reproduce the error
#!/usr/bin/env node
import 'source-map-support/register';
import {App, aws_ec2, aws_lambda, Fn, NestedStack, NestedStackProps, Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
const app = new App();
export class MainStack extends Stack {
constructor(scope: Construct, id: string, props: StackProps) {
super(scope, id, props);
new Nested1(this, 'first', {})
}
}
export class Nested1 extends NestedStack {
constructor(scope: Construct, id: string, props: NestedStackProps) {
super(scope, id, props);
const vpc = new aws_ec2.Vpc(this, 'vpc', {
subnetConfiguration: [{
cidrMask: 28,
name: 'private-test-bug',
subnetType: aws_ec2.SubnetType.PRIVATE_ISOLATED,
}]
})
const apiEndpoint = new aws_ec2.InterfaceVpcEndpoint(this, `apiEndpoint`, {
service: {
name: 'com.amazonaws.eu-central-1.execute-api',
port: 443,
},
vpc: vpc,
subnets: {
subnets: vpc.isolatedSubnets,
onePerAz: true
},
open: true,
privateDnsEnabled: false,
});
// BUG IS HERE
new Nested2(this, 'nested2', { VpcEndpoint: apiEndpoint }) // CDK should handle and parse the reference, but it dont
}
}
export interface Nested2StackProps extends NestedStackProps { VpcEndpoint: aws_ec2.InterfaceVpcEndpoint; }
export class Nested2 extends NestedStack {
constructor(scope: Construct, id: string, props: Nested2StackProps) {
super(scope, id, props);
const lambda = new aws_lambda.Function(this, 'lambda-function', {
code: aws_lambda.Code.fromInline(`exports.handler = (event, context) => { console.log(process.env.dns_name) }`),
handler: 'index.handler',
runtime: aws_lambda.Runtime.NODEJS_16_X,
environment: { dns_name: Fn.select(1, Fn.split(':', Fn.select(0, props.VpcEndpoint.vpcEndpointDnsEntries))) } // USAGE IS HERE
})
}
}
new MainStack (app, 'TestStack', {
env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION },
});

In a nutshell, it looks like a bug in the CDK. The CDK tries to send an array as a parameter to the nested template. Instead, it should join the array and pass it as a string.
I suggest a workaround for the issue. I split my code into two parts. First, I explain the idea and show some code snippets. Finally, I show the complete solution.
We define parameter in the nested stack like this:
const stack = new NestedStack(scope, id, {
parameters: {
VpcEndpointDnsEntries: props.VpcEndpointDnsEntries
},
})
In the code, we create an object for this parameter and use it to get the parameter value.
const endpoints = new CfnParameter(stack, 'VpcEndpointDnsEntries', {
type: 'List<String>',
description: 'List of entries.'
})
// Read the value:
const strings = endpoints.valueAsList
In the final step, we pass parameters to the nested stack almost as usual, except that we join the string.
createNestedStack(stack, 'NestedStack',
{ VpcEndpointDnsEntries: Fn.join(',', apiEndpoint.vpcEndpointDnsEntries) })
Note that the lambda function is not working, it throws a runtime exception. Otherwise, the stack and nested stack deploys normally.
Please find the complete code below.
Sorry, I am really in a hurry now. I plan to fix grammar and update the answer this evening.
import { App, CfnParameter, Fn, NestedStack, Stack } from 'aws-cdk-lib'
import { env } from 'process'
import { InterfaceVpcEndpoint, Vpc } from 'aws-cdk-lib/aws-ec2'
import { Code, Function, Runtime } from 'aws-cdk-lib/aws-lambda'
function createMainStack (scope, id, props) {
const stack = new Stack(scope, id, props)
const vpc = Vpc.fromLookup(stack, 'Vpc', { vpcName: 'WarehousePrinters' })
const apiEndpoint = new InterfaceVpcEndpoint(stack, `ApiEndpoint`, {
service: {
name: 'com.amazonaws.eu-west-1.execute-api',
port: 443,
},
vpc,
subnets: { subnets: vpc.privateSubnets, onePerAz: true },
open: true,
privateDnsEnabled: false,
})
createNestedStack(stack, 'NestedStack', { VpcEndpointDnsEntries: Fn.join(',', apiEndpoint.vpcEndpointDnsEntries) })
return stack
}
function createNestedStack (scope, id, props) {
const stack = new NestedStack(scope, id, {
parameters: {
VpcEndpointDnsEntries: props.VpcEndpointDnsEntries
},
})
const endpoints = new CfnParameter(stack, 'VpcEndpointDnsEntries', {
type: 'List<String>',
description: 'List of entries.'
})
new Function(stack, 'LambdaFunction', {
code: Code.fromInline(`export function handler = () => console.log(env.dns_name)`),
handler: 'index.handler',
runtime: Runtime.NODEJS_16_X,
environment: {
dns_name: Fn.select(1, Fn.split(':', Fn.select(0, endpoints.valueAsList)))
}
})
return stack
}
const app = new App()
createMainStack(app, 'MainStack', {
env: { account: env.CDK_DEFAULT_ACCOUNT, region: env.CDK_DEFAULT_REGION }
})

Related

Vue authorization with Pinia - How do I get my functions to return values and not promises?

I'm working on front end authorization with Vue and Pinia. My goal is to fetch a list of permissions belonging to the current user into my component file and then check if the User has the permission to see certain aspects of my component.
The problem arises when I use v-if to call my methods and check whether the user has the required permission. My function values are never returned and it always defaults to true.
This is what my auth.js file looks like:
export const useAuthStore = defineStore({
id: "authentication",
state: () => ({
...,
...,
userPermissions: fetchUserPerm(), //!!!
)},
const fetchUserPerm = async () => {
const res = await axios.get("api/users/me");
// My Laravel back end returns a User object with a Role property
which contains a list of all the permissions tied to this user:
return res.data.role.permissions;
};
And this is code contained within the component that I want to fetch the permissions to:
<script>
import router from "../../../router";
import axios from "axios";
import Modal from "../../UI/Modal.vue";
import { useAuthStore } from "../../../stores/auth";
export default {
setup() {
const authStore = useAuthStore();
return { authStore };
},
data() {
return {
...,
...,
userPermissions: this.authStore.userPermissions,
filteredPermissions: null,
};
},
I tried the following in "methods" and "computed" within my component:
methods:{
async checkUserPermFn(value) {
this.userPermissions = await this.userPermissions;
this.filteredPermissions = this.userPermissions.filter((permission) => {
return permission.name.includes(value);
});
console.log(this.filteredPermissions); //CORRECTLY LOGS AFTER FILTERING:
Proxy { <target>: [], <handler>: {…} }
<target>: Array []
return this.filteredPermissions.length > 0; //Gets ignored
},
}
computed:{
async checkPermission() {
this.userPermissions = await this.userPermissions;
console.log(this.userPermissions,"computed"); //CORRECTLY LOGS :
Proxy { <target>: (22) […], <handler>: {…} }
target>: Array(22)
this.filteredPermissions = this.userPermissions.filter(
(permission) => {
return permission.name.includes('permission.name');
}
);
console.log(this.filteredPermissions,"computed"); // CORRECTLY LOGS AFTER FILTERING:
Proxy { <target>: [], <handler>: {…} }
<target>: Array []
console.log(this.filteredPermissions.length)// CORRECTLY LOGS 0
return this.filteredPermissions.length > 0;
},
},
Now in my component I try check what gets returned from my methods and why does it still render them:
<div v-if="checkUserPermFn(value)"><p>Hi!</p></div>
This div is still shown even though the method should return false, and when I console log what my method returns I just get a promise:
Promise { <state>: "pending" }
​
<state>: "fulfilled"
Doing the same thing but with the computed method:
<div v-if="checkUserPermFn(value)"><p>Hi!</p></div>
The div is shown again even though the method should return false, and when I console log what my computed method returns I get a promise again but this time it contains the correct value too:
Promise { <state>: "fulfilled", <value>: false }
​
<state>: "fulfilled"
What am I missing here? I tried resolving promises but that only works within the method, when I try to return the resolved value I get a promise again. Thanks everyone!

Google analytics user property react-ga4

I'm using react-ga4.
I wonder how i could send user properties using this library and set it up in the google analytics panel as I think that i'm doing something wrong.
This is how i initialize ReactGA4
ReactGA.initialize(
[
{
trackingId: id,
gaOptions: {
role: userRole,
}
},
]
)
any suggestion?
It depends what user properties that you want to send. You can send custom user properties that not reserved by Google.
For example, I want to send account_verified with boolean value and name with string value in user property.
You can use ReactGA.gtag(...args) in this library, and then you can use it directly or put it into utils/analytics.js and wrap it to export function with parameter, so you can use it whenever it needs.
import ReactGA from "react-ga4";
ReactGA.gtag("set", "user_properties", {
account_verified: true,
});
ReactGA.gtag("set", "user_properties", {
name: "John",
});
or
import ReactGA from "react-ga4";
export const setAccountProperty = (value: boolean) => {
ReactGA.gtag("set", "user_properties", {
account_verified: value,
});
};
export const setNameProperty = (value: string) => {
ReactGA.gtag("set", "user_properties", {
name: value,
});
};
After that, you can check in your Google Analytics in DebugView directly to ensure your user properties works well.

Pulumi create instance in existing subnet

I am trying to create an EC2 instance from a custom vpc. I have vpc as Output. But i want to assign to string. Would appreciate help in this context.
Option 1:
const vpcId = args.network.vpc.id.apply(id => id); //type Output<string>; network extends awsx:Vpc
const mySubnet = aws.ec2.gtSubnet({
availabilityZone: 'ap-southeast-2a',
filters: [
{ name: "tag:Name", values: ["*private-0"]}
],
vpcId: vpcId; //Error: Type 'Output<string>' is not assignable to type 'string'
});
this.vm = new aws.ec2.Instance("ec2Instance", {
ami: amiId,
instanceType: instanceClass,
networkInterfaces: [{
networkInterfaceId: networkInterface.id,
deviceIndex: 0,
}]
});
Option 2:
const sg = new awsx.ec2.SecurityGroup("webserver-sg", { vpc }); //Here again I need vpcid string in the SecurityGroupArgs
Output<T> is a promise, also called a future. It's a value that's not yet available. It will be available when the Pulumi engine starts executing. You can pass a callback which will be run when the value is available (and you'll immediately get a new promise for the new, not-yet-available return value of the callback) or you can pass it to methods which will accept a promise (these usually take Input<T>).
Unfortunately, getSubnet (currently) does not accept Input or Promise, so option #2 is out.
What you can do is:
const mySubnetId = args.network.vpc.id.apply(id => {
const getSubnetResult = aws.ec2.getSubnet({
vpcId: id;
})
return getSubnetResult.id
})
This will give you an Output<string> which you can pass to new Instance({subnetId: ...}) because it accepts an Input<string>.

How to integrate Api Gateway and Step Fucnctions in CDK?

I have a statemachine.
const task1 = new sfn.Task(this, 'Assign Case', {
task: new tasks.InvokeFunction(Lambda1),
});
const task2 = new sfn.Task(this, 'Close Case', {
task: new tasks.InvokeFunction(Lambda2),
});
const chain = sfn.Chain.start(task1)
.next(task2);
const StateMachine = new sfn.StateMachine(this, `StateMachine`, {
definition: chain
});
And I need to call this statemachine from Api-gateway resource.I have used the below code and it throws an error like 'statemacine is not assignable to paramaeter of type AwsIntegrationProps'
const resource = this.api.root.addResource(path);
resource.addMethod(method, new apigw.AwsIntegration(handler), { apiKeyRequired: true });
//handler is above statemachine
My api gateway integration request looks like this in console.
You should use apigateway.LambdaIntegration which extends AwsIntegration.
export declare class LambdaIntegration extends AwsIntegration {
private readonly handler;
private readonly enableTest;
constructor(handler: lambda.IFunction, options?: LambdaIntegrationOptions);
bind(method: Method): void;
}
For example :
const getBookIntegration = new apigateway.LambdaIntegration(getBookHandler);
Later, use the lambdaIntegration when creating a new method:
book.addMethod('GET', getBookIntegration);
More about LambdaIntegration.
The error 'statemacine is not assignable to paramaeter of type AwsIntegrationProps' is referring to you instantiation.
The AwsIntegration class takes an AwsIntegrationProps struct as input.
new AwsIntegration(props: AwsIntegrationProps)
Getting API Gateway to kickoff Step Functions directly is a little strange. I found this Creating a Step Functions API Using API Gateway tutorial helpful. The State Machine ARN is passed in the request body of the call, so you need to pass a request template if you don't want to require the user to specify the State Machine.
resource.addMethod(
method,
new apigw.AwsIntegration({
handler: 'states',
action: 'StartExecution',
options: {
requestTemplates: {
'application/json': `{
"stateMachineArn": "${handler.ref}",
"input": "$util.escapeJavaScript($input.body)"
}`
},
},
}),
{ apiKeyRequired: true }
);
(Note: I'm translating my code from Python so I'm not 100% on the strings in TypeScript.)
I also filled in credentialsRole, passthroughBehavior, and integrationResponses to the options to get mine setup the way I wanted.
check here:
api to state
and here:
state machine

Custom Select() with parameter

UPDATE
As of #NGXS v3.1, they finally introduced arguments into #Selector().
https://www.ngxs.io/concepts/select#lazy-selectors
Examples from the DOCS
First, you define the #Selector "pandas"
#State<string[]>({
name: 'animals',
defaults: []
})
#Injectable()
export class ZooState {
#Selector()
static pandas(state: string[]) {
return (type: string) => {
return state.filter(s => s.indexOf('panda') > -1).filter(s => s.indexOf(type) > -1);
};
}
}
Then you just call it in your '.ts' file
import { Store } from '#ngxs/store';
import { map } from 'rxjs/operators';
#Component({ ... })
export class ZooComponent {
babyPandas$: Observable<string[]>;
constructor(private store: Store) {
this.babyPandas$ = this.store
.select(ZooState.pandas)
.pipe(map(filterFn => filterFn('baby')));
}
}
* From Old Post *
I am trying to create a custom #Select () to be able to drill down a particular tree and return the values dynamically. Getting either undefined or it's not making it (executing)
user.component.ts
const location = 'new york'
#Select(state => UserState.getUserLocationSlots(state, location)) slots$;
user.state.ts
#Selector()
static getUserLocationSlots(state: UserStateModel, location: any) {
console.log(state);
console.log(location); // <-- expecting 'new york', but getting undefined
}
You can achieve this by using crateSelector function from #ngxs/store
In your .state.ts file:
static getLocationSlots(location: string) {
return createSelector([UserState], (state: string[) => {
// logic for filtering your data
// eg.: state.filter(element => element == location)
})
}
In your .component.ts file:
#Select(UserState.getLocationSlots('new york')) slots$: Observable<any>
You can also check here for more details
I don't think it is possible to pass parameter to #Selector() decorated functions in ngxs v2. It would be nice though.
A ticket exist for this feature request.
Also, I think you are not using #Selector() correctly. I should be something like (hence, cannot pass parameters):
#Select(UserState.getUserLocationSlots) slots$
Refer to the docs.
Note: I am not an expert in ngxs...this is just based on what I understand now.
This is achievable in NGXS v2 & v3. Copied from my comment in the discussion on dynamic selectors here
We can achieve this at the moment using a pattern often used for redux
selectors...
The #Selector decorator can be written so that it returns a function
with the desired parameter. This enables the desired dynamic selector
arguments as well as late resolution of the selected state. For
Example:
#State<UserStateModel>( ... )
export class UserState {
#Selector()
getFilteredUsersFn(userStateModel: UserStateModel) {
return (filter: string) =>
userStateModel.users.filter((user) => user.indexOf(filter) >= 0);
}
}
And then the component would contain:
#Component({...})
export class AppComponent {
#Select(UserState.getFilteredUsersFn)
filteredUsersFn$: Observable<(filter: string) => User[]>;
get currentFilteredUsers$() {
return this.filteredUsersFn$
.pipe(map(filterFn => filterFn('myFilter')));
}
}
To pass parameters you can have the select return a function, it isn't elegant, however it works.
For example the select statement would look like:
#Selector()
static getItemByIdFn(state: { [id: number]: Entity }) {
return (id: number) => {
return state[id];
};
}
then in the component:
this.store.select(MyState.getItemByIdFn)
.pipe(map(mapByIdFn) => mayByIdFn(1)) // using the returned function
.subscribe(...);
Note the map, which is where you pass your id to the returned function. Here you can place whatever parameters you would like.
Hope this helps :)!