AWS CDK error creating an ARecord for CloudFront distro - amazon-cloudfront

Given:
export class MyARecord extends cdk.Stack {
public readonly arecord: ARecord
constructor(scope: cdk.Construct, id: string, props: IcARecordARecordProps) {
super(scope, id, props);
const hostedZone = HostedZone.fromHostedZoneAttributes(this, 'hostedZone', {
hostedZoneId: props.hostedZoneId,
zoneName: props.apexDomainName
})
console.log(hostedZone.hostedZoneId);
console.log(hostedZone.zoneName);
const distribution = Distribution.fromDistributionAttributes(this, 'distribution', {
domainName: props.apexDomainName,
distributionId: props.distributionId
});
this.arecord = new ARecord(this, `${props.projectName}ARecord`, {
zone: hostedZone,
target: RecordTarget.fromAlias(
new CloudFrontTarget(distribution)
)
})
}
}
Deploying I get the following logging and error:
hostedZoneId: 'Z1016966FIW9RVHP46ST',
zoneName: 'testmyroute53domain.com',
08:21:59 | CREATE_FAILED | AWS::Route53::RecordSet | testmyroute53domaincomARecordB9B947F2
[Tried to create an alias that targets testmyroute53domain.com., type A in zone Z2FDTNDATAQYW2, but the alias target name does not lie within the target zone]
Anybody have any idea why it asks about a zone 'Z2FDTNDATAQYW2' when the zone id is clearly 'Z1016966FIW9RVHP46ST'?
Manually I made the A record and it works.... Just AWS CDK does not want to create it for me...

Related

How to delete Azure Static Web App branch preview environments when deleting source branch in Azure DevOps?

Background
I am using Azure DevOps for hosting the source of my web application and building/deploying the application to an Azure Static Web App.
I am using the "branch preview environments" of Static Web App like this (source):
steps:
...
- task: AzureStaticWebApp#0
inputs:
...
production_branch: 'main'
This works fine so far. For example, if I use a branch "dev", a corresponding branch environment is being created.
Question
How can I automatically delete the Azure static web app branch preview environment once the branch it was created for is being deleted?
Use Azure cli?
The only approach I found so far is using Azure CLI - but how to automate?
az staticwebapp environment delete --name my-static-app \
--environment-name an-env-name --subscription my-sub
I solved it by creating a separate pipeline triggered by the main branch. The pipeline removes all deployments that don't have an open pull request.
Here is the pipeline, basically just calling a node script that takes care of the cleanup:
name: Cleanup static web apps
trigger:
- main
# Add the following variables into devops:
# - DEVOPS_PAT: your personal access token for DevOps
# - AZURE_SUBSCRIPTION: the subscription in azure under which your swa lives
variables:
NPM_CONFIG_CACHE: $(Pipeline.Workspace)/.npm
DEVOPS_ORG_URL: "https://dev.azure.com/feedm3"
DEVOPS_PROJECT: "azure-playground"
AZURE_STATIC_WEBAPP_NAME: "react-app"
jobs:
- job: cleanup_preview_environments_job
displayName: Cleanup
pool:
vmImage: ubuntu-latest
steps:
- task: Cache#2
inputs:
key: 'npm | "$(Agent.OS)" | package-lock.json'
restoreKeys: |
npm | "$(Agent.OS)"
path: $(NPM_CONFIG_CACHE)
displayName: "Cache npm"
- script: |
npm ci
displayName: "Install dependencies"
- task: AzureCLI#2
inputs:
azureSubscription: "test-service-connection-name"
scriptType: bash
scriptLocation: inlineScript
inlineScript: |
npm run ci:cleanup-deployments
displayName: "Cleanup outdated deployments"
This is the actual script that removes the deployments:
import { getPersonalAccessTokenHandler, WebApi } from "azure-devops-node-api";
import { exec as callbackExec } from 'child_process';
import { promisify } from 'util';
const exec = promisify(callbackExec);
const DEVOPS_ORG_URL = process.env["DEVOPS_ORG_URL"] as string;
const DEVOPS_PROJECT = process.env["DEVOPS_PROJECT"] as string;
const DEVOPS_PAT = process.env["DEVOPS_PAT"] as string;
const AZURE_SUBSCRIPTION = process.env["AZURE_SUBSCRIPTION"] as string;
const AZURE_STATIC_WEBAPP_NAME = process.env["AZURE_STATIC_WEBAPP_NAME"] as string;
const ALWAYS_DEPLOYED_BRANCHES = ['main'];
const REPO_ID = process.env['BUILD_REPOSITORY_ID'] as string;
const getAllStaticWebAppDeployments = async (): Promise<{ name: string; sourceBranch: string, hostname: string }[]> => {
const { stdout, stderr } = await exec(`az staticwebapp environment list --name ${AZURE_STATIC_WEBAPP_NAME} --subscription ${AZURE_SUBSCRIPTION}`);
if (stderr) {
console.error('Command failed!', stderr);
throw new Error(stderr);
}
return JSON.parse(stdout);
}
const run = async () => {
console.log(`Cleanup outdated deployments ${{REPO_ID, DEVOPS_PROJECT, AZURE_STATIC_WEBAPP_NAME}}...`)
const webAppDeployments = await getAllStaticWebAppDeployments();
// post comment
const authHandler = getPersonalAccessTokenHandler(DEVOPS_PAT);
const connection = new WebApi(DEVOPS_ORG_URL, authHandler);
await connection.connect();
const gitApi = await connection.getGitApi(`${DEVOPS_ORG_URL}/${DEVOPS_PROJECT}`);
// status 1 is active (PullRequestStatus type)
const activePullRequests = await gitApi.getPullRequests(REPO_ID, { status: 1 });
const activePullRequestBranches = activePullRequests.map(pr => pr.sourceRefName).filter(Boolean).map(fullBranchName => fullBranchName!.split('/')[2]);
// main deployment should always be alive
activePullRequestBranches.push(...ALWAYS_DEPLOYED_BRANCHES);
const outdatedDeployments = webAppDeployments.filter(deployment => {
return !activePullRequestBranches.includes(deployment.sourceBranch);
})
console.log('Deployments to delete:', outdatedDeployments);
for await (const deployment of outdatedDeployments) {
const deploymentName = deployment.name;
console.log(`Deleting deployment ${deploymentName}...`);
/**
* Deletion works, but ends with an irrelevant error.
*/
try {
const { stderr } = await exec(`az staticwebapp environment delete --name ${AZURE_STATIC_WEBAPP_NAME} --subscription ${AZURE_SUBSCRIPTION} --environment-name ${deploymentName} --yes`);
if (stderr) {
console.error('Could not delete deployment ', deploymentName);
} else {
console.log('Deleted deployment ', deploymentName);
}
} catch (e) {
console.log('Deleted deployment ', deploymentName);
}
}
console.log('Outdated deployments cleared!')
}
await run();
The full repo can be found here: https://github.com/feedm3/learning-azure-swa-devops

textarea on vue not accepting null

I use:
- "vue": "3.2.26",
- "vee-validate": "4.5.6",
- "typescript": "4.5.4"
While creating a textarea field on vue3 I ran into a problem
i have
example with vee-validate
import { Field, useForm } from 'vee-validate'
<Field v-slot="{ field, errors }" name="name" type="text">
<VControl icon="feather:edit-2" :has-error="Boolean(formErrors.name)">
<input
v-bind="field"
class="input is-primary-focus"
type="text"
placeholder="Placeholder"
autocomplete="name"
/>
<p v-if="errors" class="help is-danger">{{ formErrors.name}}</p>
</VControl>
</Field>
simple example
<textarea
v-model="fieldValues.description"
class="textarea is-success-focus"
rows="3"
placeholder="Description"
></textarea>
for model
export interface iCat {
id: number
name: string
description: string | null
}
but textarea return error
Type 'null' is not assignable to type 'string | number | string[] | undefined'.
for vee-validate
const {
values: fieldValues,
errors: formErrors,
handleSubmit,
} = useForm({
initialValues: {
id: 0,
name: '',
description: ''
},
validationSchema: object({
id: number().required().integer(),
name: string().required(),
description: string().notRequired().default(null).nullable()
}),
})
if check #vue/runtime-dom/dist/runtime-dom.d.ts
export interface TextareaHTMLAttributes extends HTMLAttributes {
....
value?: string | string[] | number
...
}
If I look in node-moduls, I see that the textarea does not accept null as a value - how can I properly solve this problem then?
Unfortunately, you can't change the existing type of value for TextareaHTMLAttributes (at least not in TypeScript 4.5.5). Type augmentation only allows extension (adding properties to the type, or creating a new type that extends the original TextareaHTMLAttributes interface with a new type for value).
A workaround is to use a new type that extends iCat, changing its description type to the expected type of TextareaHTMLAttributes's value:
Declare a new type (named "iFieldValues"), using Omit to exclude the original description property from iCat, and an intersection with a new description property that has a type of TextareaHTMLAttributes['value'].
Use type assertion (as iFieldValues) on the values returned from useForm().
// MyForm.vue
<script setup lang="ts">
import { toRefs } from 'vue'
import type { TextareaHTMLAttributes } from '#vue/runtime-dom'
import { useForm } from 'vee-validate'
import { object, number, string } from 'yup'
export interface iCat {
id: number
name: string
description: string | null
}
1️⃣
type iFieldValues = Omit<iCat, 'description'> & {
description: TextareaHTMLAttributes['value']
}
const {
values,
errors: formErrors,
handleSubmit,
} = useForm({
initialValues: {
id: 0,
name: '',
description: ''
},
validationSchema: object({
id: number().required().integer(),
name: string().required(),
description: string().notRequired().default(null).nullable()
}),
})
2️⃣
const fieldValues = values as iFieldValues
</script>

Jest test gives me "Cannot find module" when running tests, but the code compiles fine outside of tests. Whats up?

I am writing my first test for a large application using NestJS and TypeScript. I want to import an entity which imports an interface from a module in another folder. This is proving challenging.
The project has a structure like
apps
..apis
--..web-api
----..src
----..package.json
----..jest.config.js
------..app
--------..profiles
----------.._entities
------------..profile.entity.spec.ts <--- the test
------------..profile.entity.ts
libs
--app-interfaces
----src
------lib
--------profile
----------index.ts <--- the files the test is trying to import and test
----------gender.enum.ts
----------profile.interface.ts
In profile.entity.spec.ts I have:
import { ProfileEntity } from "./profile.entity";
import { GenderEnum, ProfileTypeEnum } from '../../../../../../../libs/app-interfaces/src/lib/profile';
// all that ../../ is what I have to do to get to the import and have it recognized in this .spec file
// it doesn't recognize the # symbol taking us to the root of the project
describe('Profile class', () => {
it('should make a profile with no fields', () => {
const profile = new ProfileEntity();
expect(profile).toBeTruthy();
});
});
and in profile.entity.ts:
import { Column, CreateDateColumn, DeleteDateColumn, Entity, PrimaryColumn, UpdateDateColumn } from 'typeorm';
import { GenderEnum, IProfile, ProfileTypeEnum } from '#ourProject/app-interfaces';
#Entity({ name: 'profile' })
export class ProfileEntity implements IProfile {
#PrimaryColumn({ nullable: false, unique: true })
id: string;
The profile.entity.ts file works fine when the app is compiled by Docker. However when I run my profile.entity.spec.ts file in Jest, I get:
Cannot find module '#ourProject/app-interfaces' from 'src/app/profiles/_entities/profile.entity.ts' // <--- this is a big clue
Require stack:
src/app/profiles/_entities/profile.entity.ts
src/app/profiles/_entities/profile.entity.spec.ts
1 | import { Column, CreateDateColumn, DeleteDateColumn, Entity, PrimaryColumn, UpdateDateColumn } from 'typeorm';
> 2 | import { GenderEnum, IProfile, ProfileTypeEnum } from '#ourProject/app-interfaces';
| ^
3 |
4 |
5 | #Entity({ name: 'profile' })
at Resolver.resolveModule (node_modules/jest-resolve/build/resolver.js:324:11)
at Object.<anonymous> (src/app/profiles/_entities/profile.entity.ts:2:1)
Is there some special configuration I need to use in jest.config.js to give jest testing access to files outside of the top lvl of the package.json?
here is jest.config.js
module.exports = {
displayName: 'web-api',
globals: {
'ts-jest': {
tsconfig: '<rootDir>/tsconfig.spec.json',
},
},
testEnvironment: 'node',
transform: {
'^.+\\.[tj]s$': 'ts-jest',
},
moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '../../coverage/apps/web-api',
roots: ['../../'],
};
The line Cannot find module '#ourProject/app-interfaces' from 'src/app/profiles/_entities/profile.entity.ts' does not occur when the file runs normally.
Let's say your tsconfig.json alias configuration looks like this:
"paths": {
"#ourProject/*": ["./libs/*"],
}
Then you would need to add a moduleNameMapper line in jest.config.js:
{
moduleNameMapper: {
'^#ourProject/(.*)$': ['<rootDir>/libs/$1'],
},
}
I didn't have an issue with aliased imports being in a different folder, but jest didn't recognize aliases defined in tsconfig.json.

AWS XRAY on Fargate service

I want to add xray to my Fargate service. Everything works (synth/deploy) but in the logs I'am seeing the following error:
2022-02-07T13:38:22Z [Error] Sending segment batch failed with:
AccessDeniedException: 2022-02-07 14:38:22status code: 403, request
id: cdc23f61-5c2e-4ede-8bda-5328e0c8ac8f
The user I'am using to deploy the application has the AWSXrayFullAccess permission.
Do I have to grant the task the permission manually? If so how?
Here is a snippet of the application:
const cdk = require('#aws-cdk/core');
const ecs = require('#aws-cdk/aws-ecs');
const ecsPatterns = require('#aws-cdk/aws-ecs-patterns');
class API extends cdk.Stack {
constructor(parent, id, props) {
super(parent, id, props);
this.apiXRayTaskDefinition = new ecs.FargateTaskDefinition(this, 'apixRay-definition', {
cpu: 256,
memoryLimitMiB: 512,
});
this.apiXRayTaskDefinition.addContainer('api', {
image: ecs.ContainerImage.fromAsset('./api'),
environment: {
"QUEUE_URL": props.queue.queueUrl,
"TABLE": props.table.tableName,
"AWS_XRAY_DAEMON_ADDRESS": "0.0.0.0:2000"
},
logging: ecs.LogDriver.awsLogs({ streamPrefix: 'api' }),
}).addPortMappings({
containerPort: 80
})
this.apiXRayTaskDefinition.addContainer('xray', {
image: ecs.ContainerImage.fromRegistry('public.ecr.aws/xray/aws-xray-daemon:latest'),
logging: ecs.LogDriver.awsLogs({ streamPrefix: 'xray' }),
}).addPortMappings({
containerPort: 2000,
protocol: ecs.Protocol.UDP,
});
// API
this.api = new ecsPatterns.ApplicationLoadBalancedFargateService(this, 'api', {
cluster: props.cluster,
taskDefinition: this.apiXRayTaskDefinition,
desiredCount: 2,
cpu: 256,
memory: 512,
createLogs: true
})
props.queue.grantSendMessages(this.api.service.taskDefinition.taskRole);
props.table.grantReadWriteData(this.api.service.taskDefinition.taskRole);
}
}
The user I'am using to deploy the application has the AWSXrayFullAccess permission.
This is irrelevant, the task will not get all the rights of the user that deploys the stack.
Yes, you need to add the required permissions to the task with
this.apiXRayTaskDefinition.taskRole.addManagedPolicy(
iam.ManagedPolicy.fromAwsManagedPolicyName('AWSXRayDaemonWriteAccess')
);
References:
AWS managed policy with required access for the X-Ray daemon: https://docs.aws.amazon.com/xray/latest/devguide/security_iam_id-based-policy-examples.html#xray-permissions-managedpolicies
Import an AWS-managed policy: https://docs.aws.amazon.com/cdk/api/v1/docs/#aws-cdk_aws-iam.ManagedPolicy.html#static-fromwbrawswbrmanagedwbrpolicywbrnamemanagedpolicyname
Access the task role: https://docs.aws.amazon.com/cdk/api/v1/docs/#aws-cdk_aws-ecs.FargateTaskDefinition.html#taskrole-1
Add a policy: https://docs.aws.amazon.com/cdk/api/v1/docs/#aws-cdk_aws-iam.IRole.html#addwbrmanagedwbrpolicypolicy

Using Nipplejs in Vue with Quasar

i am trying to use Nipplejs in my Vue Project with quasar Components.
I installed nipplejs by npm install nipplejs --save.
I tried to integrate the nipple with the following code:
<template>
<div id="joystick_zone"></div>
</template>
<script lang= "ts">
// Imports
import Vue from "vue";
import nipplejs from 'nipplejs';
export default Vue.extend({
async mounted(): Promise<void> {
var options = {
zone: document.getElementById('joystick_zone') as HTMLElement,
mode: 'static',
color: `'blue'`,
}
var manager = nipplejs.create(options);
}
});
My first problem is that typescript doesnt accept 'static' as mode:
The definition says: mode?: 'dynamic' | 'semi' | 'static';
And i get the following error message:
Argument of type '{ zone: HTMLElement; mode: string; color: string; }' is not assignable to parameter of type 'JoystickManagerOptions'.
Types of property 'mode' are incompatible.
Type 'string' is not assignable to type '"dynamic" | "semi" | "static" | undefined'.
My second problem is that the joystick does not appear on the website.
If someone could help i would be very thankful.
If you would look into the definition of options variable you created. You would see it is of type { zone: HTMLElement; mode: string; color: string; }.
You must assign a type to the options variable.
var options: JoystickManagerOptions = {
zone: document.getElementById('joystick_zone') as HTMLElement,
mode: 'static',
color: 'blue',
};
Other option is to define the variable as const:
var options = {
zone: document.getElementById('joystick_zone') as HTMLElement,
mode: 'static',
color: 'blue',
} as const;
// Variable is now of type
type options = {
zone: HTMLElementHTMLElement;
mode: 'static';
color: 'blue';
}

Resources