Controller Reconcile on object changes - amazon-web-services

Im trying to listen to secret change using the operator sdk
The problem is that im not getting the reconcile event when I apply the secret with the labels that I define in the operator
I did the following
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
Scheme: scheme,
…
NewCache: cache.BuilderWithOptions(cache.Options{
SelectorsByObject: cache.SelectorsByObject{
&corev1.Secret{}: {
Label: labels.SelectorFromSet(labels.Set{"foo": "bar"}),
},
},
}),
I run the operator and apply the following secret and the reconcile is not invoked, any idea?
apiVersion: v1
kind: Secret
metadata:
labels:
foo: bar
name: mysecret
namespace: dev
type: Opaque
data:
USER_NAME: YWRtaW4=
PASSWORD: dGVzdBo=

It looks like you are using the cache.Options.SelectorsByObject field to specify the labels that should trigger a reconcile event. However, this field is used to specify the labels that should be used to select objects from the cache, not the labels that should trigger a reconcile event.
To specify the labels that should trigger a reconcile event, you can use the ctrl.Watch function, like this:
mgr.Watch(&source.Kind{Type: &corev1.Secret{}},
&handler.EnqueueRequestForObject{},
predicate.Funcs{
UpdateFunc: func(e event.UpdateEvent) bool {
return labels.Set(e.MetaNew.GetLabels()).Has("foo", "bar")
},
})

Related

AWS CDK and AppSync: Invalid principal in policy: "SERVICE":"appsync"

I'm trying to follow tutorials and use AWS's CDK CLI to deploy a stack using AppSync and some resource creations fail with errors like the following showing under events for the Stack in the CloudFormation console:
Invalid principal in policy: "SERVICE":"appsync" (Service: AmazonIdentityManagement; Status Code: 400; Error Code: MalformedPolicyDocument; Request ID: 8d98f07c-d717-4dfe-af96-14f2d72d993f; Proxy: null)
I suspect what happened is that when cleaning up things on my personal developer account I deleted something I shouldn't have, but due to limited AWS experience I don't know what to create, I suspect it's an IAM policy, but I don't know the exact settings to use.
I'm trying on a new clean project created using cdk init sample-app --language=typescript. Running cdk deploy immediately after the above command works fine.
I originally tried using cdk-appsync-transformer to create GraphQL endpoints to a DynamoDB table and encountered the error.
I tried re-running cdk bootstrap after deleting the CDKToolkit CloudFormation stack, but it's not fixing this problem.
To rule out that it's due to something with the 3rd party library, I tried using AWS's own AppSync Construct Library instead and even following the example there I encountered the same error (although on creation of different resource types).
Reproduction steps
Create a new folder.
In the new folder run cdk init sample-app --language=typescript.
Install the AWS AppSync Construct Library: npm i #aws-cdk/aws-appsync-alpha#2.58.1-alpha.0 --save.
As per AWS's docs:
Create lib/schema.graphql with the following:
type demo {
id: String!
version: String!
}
type Query {
getDemos: [ demo! ]
}
input DemoInput {
version: String!
}
type Mutation {
addDemo(input: DemoInput!): demo
}
Update the lib/<projectName>-stack.ts file to be essentially like the following:
import * as appsync from '#aws-cdk/aws-appsync-alpha';
import { Duration, Stack, StackProps } from 'aws-cdk-lib';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
import * as sns from 'aws-cdk-lib/aws-sns';
import * as subs from 'aws-cdk-lib/aws-sns-subscriptions';
import * as sqs from 'aws-cdk-lib/aws-sqs';
import { Construct } from 'constructs';
import * as path from 'path';
export class CdkTest3Stack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
const queue = new sqs.Queue(this, 'CdkTest3Queue', {
visibilityTimeout: Duration.seconds(300)
});
const topic = new sns.Topic(this, 'CdkTest3Topic');
topic.addSubscription(new subs.SqsSubscription(queue));
const api = new appsync.GraphqlApi(this, 'Api', {
name: 'demo',
schema: appsync.SchemaFile.fromAsset(path.join(__dirname, 'schema.graphql')),
authorizationConfig: {
defaultAuthorization: {
authorizationType: appsync.AuthorizationType.IAM,
},
},
xrayEnabled: true,
});
const demoTable = new dynamodb.Table(this, 'DemoTable', {
partitionKey: {
name: 'id',
type: dynamodb.AttributeType.STRING,
},
});
const demoDS = api.addDynamoDbDataSource('demoDataSource', demoTable);
// Resolver for the Query "getDemos" that scans the DynamoDb table and returns the entire list.
// Resolver Mapping Template Reference:
// https://docs.aws.amazon.com/appsync/latest/devguide/resolver-mapping-template-reference-dynamodb. html
demoDS.createResolver('QueryGetDemosResolver', {
typeName: 'Query',
fieldName: 'getDemos',
requestMappingTemplate: appsync.MappingTemplate.dynamoDbScanTable(),
responseMappingTemplate: appsync.MappingTemplate.dynamoDbResultList(),
});
// Resolver for the Mutation "addDemo" that puts the item into the DynamoDb table.
demoDS.createResolver('MutationAddDemoResolver', {
typeName: 'Mutation',
fieldName: 'addDemo',
requestMappingTemplate: appsync.MappingTemplate.dynamoDbPutItem(
appsync.PrimaryKey.partition('id').auto(),
appsync.Values.projecting('input'),
),
responseMappingTemplate: appsync.MappingTemplate.dynamoDbResultItem(),
});
//To enable DynamoDB read consistency with the `MappingTemplate`:
demoDS.createResolver('QueryGetDemosConsistentResolver', {
typeName: 'Query',
fieldName: 'getDemosConsistent',
requestMappingTemplate: appsync.MappingTemplate.dynamoDbScanTable(true),
responseMappingTemplate: appsync.MappingTemplate.dynamoDbResultList(),
});
}
}
Run cdk deploy.

Get the list of all annotations

When hovering over the points, I'm trying to toggle one annotation's visibility (display: true/false).
I started by defining the onHover interaction
options: {
plugins: {
annotation: {
annotations: {
one,
two,
three
},
},
},
onHover: (event, elements, chart) => {
if (elements[0]) {
// get the list of all annotations
}
}
},
Now, I'm stuck on getting the list of all defined annotations.
While browsing, I found this issue about annotations. Assuming myLine corresponds to chart, I was expecting to get the first annotation with chart.options.annotation.annotations[0]. However, chart.options.annotation is not defined.
Am I missing anything here? Is it possible to programmatically get the list of denied annotations?
You are configuring your annotations in the options.plugins.annotation namespace so you also need to retrieve them from there instead of options.annotation.
So using this onHover will list your annotations:
onHover: (event, elements, chart) => {
console.log(chart.options.plugins.annotation.annotations)
}

Custom domain name with AWS AppSync using CDK v2

I try to add a custom domain name to my AWS AppSync API using the AWS CDK (v2).
First, I manually added a certificate for my domain. I did this in the us-east-1 region (while my API is hosted in eu-central-1) as this seems to be necessary. APPSYNC_CERT_ARN refers to this certificate's ARN.
This is the TypeScript code I have in my cdk stack:
import * as cdk from "aws-cdk-lib";
import * as appsync from "#aws-cdk/aws-appsync-alpha";
const APPSYNC_CERT_ARN = "arn:aws:acm:us-east-1:xxxx:certificate/xxxx";
export class ApiStack extends cdk.Stack {
constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const certificate = cdk.aws_certificatemanager.Certificate.fromCertificateArn(
this,
"cert",
APPSYNC_CERT_ARN,
);
const api = new appsync.GraphqlApi(this, "Api", {
name: "my-api",
domainName: {
certificate,
domainName: "my.domain.com",
},
...
});
}
}
However, as I add the domainName member, I get the following error during cdk deploy:
Resource of type 'AWS::AppSync::DomainNameApiAssociation' with identifier 'null' was not found.
The feature to add custom AppSync domains via cdk is rather new, so I did not find any hints on what I do wrong. Any help is appreciated.
In order to create an AppsyncDomainNameApiAssociation (which is the underlying cloudformation resource created by the CDK construct you are using) you have to create both the GraphqlAPI and an AppsyncDomainName prior to creating the association. Although the docs don't really reflect this relationship you can read about "AWS::AppSync::DomainName" here.
Including the "domainName" object in your GraphqlApi instantiation creates the association resources but fails to create the actual domain name resources. You'll need to create it before hand by using the L1 construct for CFNDomainName then manually create the association using the L1 construct CfnDomainNameApiAssociation.
The constructs aren't included in the aws_appsync_alpha library. You'll need to import them from aws_appsync and use them like this:
const certificate = cdk.aws_certificatemanager.Certificate.fromCertificateArn(
this,
"cert",
APPSYNC_CERT_ARN,
);Ï
const appsyncDomainName = new aws_appsync.CfnDomainName(
this,
'AppsyncDomainName',
{
certificateArn: certificate.certificateArn,
domainName: "my.domain.com",
}
);
const api = new appsync.GraphqlApi(this, "Api", {
name: "my-api",
// Omit the domainName object
...
});
const assoc = new aws_appsync.CfnDomainNameApiAssociation(
this,
'MyCfnDomainNameApiAssociation',
{
apiId: api.apiId,
domainName: "my.domain.com",
}
);
// Required to ensure the resources are created in order
assoc.addDependsOn(appsyncDomainName);
If you do use Route53:
Keeping the same code you already have you will need to add a CNAME record:
import {
ARecord,
CnameRecord,
HostedZone,
RecordTarget
} from 'aws-cdk-lib/aws-route53'
// Add record
new CnameRecord(this, `ApiAliasRecord`, {
recordName: "my.domain.com", // i.e api.foo.com
zone: HostedZone.fromLookup(this, 'Zone', { domainName: "domain.com", }),
domainName: Fn.select(2, Fn.split('/', api.graphqlUrl))
})
External DNS:
Create a CNAME entry with desired api domain name with the value of the internal API domain name (i.e. https://7sdbasdasad8.appsync-api.us-east-1.amazonaws.com)

CDK: How to get apigateway key value (ie x-api-key: *20 Chars*)

I'm unable to find out how to get the api key out of an apigateway key. I can get its ID and its ARN but not the value. I know you can specify the value when creating the key, but not how to retrieve it once created--short of logging into the AWS GUI and finding it that way.
I've looked at the documentation for aws-apigateway.ApiKey and couldn't find any way to get the value. https://docs.aws.amazon.com/cdk/api/latest/docs/#aws-cdk_aws-apigateway.ApiKey.html I've also looked at kms keys since you can get their value, but I don't know if it's usable in the context of an API Gateway usage plan (not included in code below).
Failing the ability to get the value, is there a way to generate a value that won't change, or will persist? I'm using an ephemeral Jenkins node to run the CDK.
const apiGateway = require('#aws-cdk/aws-apigateway');
...
const apiKey = new apiGateway.ApiKey(this, 'api-key', {
apiKeyName: 'my-api-key',
});
...
new cdk.CfnOutput(this, 'x-api-key-apiKey_id', {
value: apiKey.keyId
});
new cdk.CfnOutput(this, 'x-api-key-apiKey_keyArn', {
value: apiKey.keyArn
});
We can't retrieve the auto generated key via cdk/cloudformation without a custom resource. But we can generate the key , store it in a secret manager or an ssm secret and use that to create api key.
const secret = new secretsmanager.Secret(this, 'Secret', {
generateSecretString: {
generateStringKey: 'api_key',
secretStringTemplate: JSON.stringify({ username: 'web_user' }),
excludeCharacters: ' %+~`#$&*()|[]{}:;<>?!\'/#"\\',
},
});
this.restApi.addApiKey('ApiKey', {
apiKeyName: `web-app-key`,
value: secret.secretValueFromJson('api_key').toString(),
});
I'm going to use https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/SecretsManager.html#getRandomPassword-property to generate the 20 characters and set the API key. Since nothing outside of my stack needs the key I'm ok with regenerating it and updating my resources every time I do a deploy. However if there are things outside of the stack that need the key then using Balu's answer is the best option.
The reason for this is keeping a secret has a cost associated with it.
The accepted answer is perhaps not the best way to go about this.
It can be solved without creating an extra secret using aws-cdk's custom resources.
Here is a snippet that will get you the value of an api key. The value of this key is generated randomly by the api gateway.
import * as iam from "#aws-cdk/aws-iam";
import { RetentionDays } from "#aws-cdk/aws-logs";
import * as cdk from "#aws-cdk/core";
import {
AwsCustomResource,
AwsCustomResourcePolicy,
AwsSdkCall,
PhysicalResourceId,
} from "#aws-cdk/custom-resources";
import { IApiKey } from "#aws-cdk/aws-apigateway";
export interface GetApiKeyCrProps {
apiKey: IApiKey;
}
export class GetApiKeyCr extends cdk.Construct {
apikeyValue: string;
constructor(scope: cdk.Construct, id: string, props: GetApiKeyCrProps) {
super(scope, id);
const apiKey: AwsSdkCall = {
service: "APIGateway",
action: "getApiKey",
parameters: {
apiKey: props.apiKey.keyId,
includeValue: true,
},
physicalResourceId: PhysicalResourceId.of(`APIKey:${props.apiKey.keyId}`),
};
const apiKeyCr = new AwsCustomResource(this, "api-key-cr", {
policy: AwsCustomResourcePolicy.fromStatements([
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
resources: [props.apiKey.keyArn],
actions: ["apigateway:GET"],
}),
]),
logRetention: RetentionDays.ONE_DAY,
onCreate: apiKey,
onUpdate: apiKey,
});
apiKeyCr.node.addDependency(props.apiKey);
this.apikeyValue = apiKeyCr.getResponseField("value");
}
}

How to get the device metadata info from google cloud iot core using listDevices

I would like to use listDevices to get all my devices under a registry.
Google NodeJS Core IOT API spec
I get an array back which seems to contain a metadata obj/json doc but it's empty.
---
- credentials: []
metadata: {}
id: device001
name: ''
numId: '3038801391636994'
config:
lastConfigAckTime:
state:
lastConfigSendTime:
blocked: false
lastStateTime:
logLevel: LOG_LEVEL_UNSPECIFIED
gatewayConfig:
- credentials: []
metadata: {}
id: device002
name: ''
numId: '2991873732633082'
config:
lastConfigAckTime:
state:
lastConfigSendTime:
blocked: false
lastStateTime:
logLevel: LOG_LEVEL_UNSPECIFIED
gatewayConfig:
If I run a getDevice I do get the expected metadata but that requires a request for each device which becomes too slow and hammers resources. Bug or design?
const [response] = await iotClient.getDevice({name: devicePath});
Which actually shows the metadata
Found device: device002 {
credentials: [
{
expirationTime: [Object],
publicKey: [Object],
credential: 'publicKey'
}
],
metadata: {
hasGPS: 'true',
model: 'Pyton_v1',
hasSolar: 'true',
netType: 'WIFI'
},
id: 'device002'
}
I've made some tries with the device list functions and I think it is a design.
If you run the "gcloud iot devices list" command you get only the fields id and num_id, the ones that are filled in your output array too.
I tried using other client libraries and I got the same results, so it looks like it is designed like this but the NodeJS library retrieves additional fields for each device.
It is defined in the fieldMask parameter in listDevices api. Check the example code here:
https://cloud.google.com/iot/docs/how-tos/devices?authuser=1#getting_device_details