Skip to content

Commit

Permalink
feat(eks): KubernetesPatch (#6753)
Browse files Browse the repository at this point in the history
feat(eks): KubernetesPatch (#6753)

Exports KubernetesPatch from aws-eks module.

Adds ability to specify a patch "type" for `kubectl patch` to use (fixes #6723)
  • Loading branch information
mattchrist committed Mar 19, 2020
1 parent a4dcfb5 commit c7fab5b
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 27 deletions.
15 changes: 15 additions & 0 deletions packages/@aws-cdk/aws-eks/README.md
Expand Up @@ -321,6 +321,21 @@ CDK. This means that if the resource is deleted from your code (or the stack is
deleted), the next `cdk deploy` will issue a `kubectl delete` command and the
Kubernetes resources will be deleted.

### Patching Kubernetes Resources

The KubernetesPatch construct can be used to update existing kubernetes
resources. The following example can be used to patch the `hello-kubernetes`
deployment from the example above with 5 replicas.

```ts
new KubernetesPatch(this, 'hello-kub-deployment-label', {
cluster,
resourceName: "deployment/hello-kubernetes",
applyPatch: { spec: { replicas: 5 } },
restorePatch: { spec: { replicas: 3 } }
})
```

### AWS IAM Mapping

As described in the [Amazon EKS User Guide](https://docs.aws.amazon.com/en_us/eks/latest/userguide/add-user-role.html),
Expand Down
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-eks/lib/index.ts
Expand Up @@ -4,5 +4,6 @@ export * from './cluster';
export * from './eks.generated';
export * from './fargate-profile';
export * from './helm-chart';
export * from './k8s-patch';
export * from './k8s-resource';
export * from './fargate-cluster';
44 changes: 38 additions & 6 deletions packages/@aws-cdk/aws-eks/lib/k8s-patch.ts
Expand Up @@ -3,21 +3,25 @@ import { Construct, Stack } from "@aws-cdk/core";
import { Cluster } from "./cluster";
import { KubectlProvider } from "./kubectl-provider";

export interface CoreDnsComputeTypeProps {
/**
* Properties for KubernetesPatch
*/
export interface KubernetesPatchProps {
/**
* The cluster to apply the patch to.
* [disable-awslint:ref-via-interface]
*/
readonly cluster: Cluster;

/**
* The JSON object to pass to `kubectl patch` when the resource is created/updated.
*/
readonly applyPatch: any;
readonly applyPatch: { [key: string]: any };

/**
* The JSON object to pass to `kubectl patch` when the resource is removed.
*/
readonly restorePatch: any;
readonly restorePatch: { [key: string]: any };

/**
* The full name of the resource to patch (e.g. `deployment/coredns`).
Expand All @@ -30,14 +34,41 @@ export interface CoreDnsComputeTypeProps {
* @default "default"
*/
readonly resourceNamespace?: string;

/**
* The patch type to pass to `kubectl patch`.
* The default type used by `kubectl patch` is "strategic".
*
* @default PatchType.STRATEGIC
*/
readonly patchType?: PatchType;
}

/**
* Values for `kubectl patch` --type argument
*/
export enum PatchType {
/**
* JSON Patch, RFC 6902
*/
JSON = "json",
/**
* JSON Merge patch
*/
MERGE = "merge",
/**
* Strategic merge patch
*/
STRATEGIC = "strategic"
}

/**
* A CloudFormation resource which applies/restores a JSON patch into a
* Kubernetes resource.
* @see https://kubernetes.io/docs/tasks/run-application/update-api-object-kubectl-patch/
*/
export class KubernetesPatch extends Construct {
constructor(scope: Construct, id: string, props: CoreDnsComputeTypeProps) {
constructor(scope: Construct, id: string, props: KubernetesPatchProps) {
super(scope, id);

const stack = Stack.of(this);
Expand All @@ -52,8 +83,9 @@ export class KubernetesPatch extends Construct {
ApplyPatchJson: stack.toJsonString(props.applyPatch),
RestorePatchJson: stack.toJsonString(props.restorePatch),
ClusterName: props.cluster.clusterName,
RoleArn: props.cluster._getKubectlCreationRoleArn(provider.role)
RoleArn: props.cluster._getKubectlCreationRoleArn(provider.role),
PatchType: props.patchType ?? PatchType.STRATEGIC
}
});
}
}
}
Expand Up @@ -33,6 +33,7 @@ def patch_handler(event, context):
resource_namespace = props['ResourceNamespace']
apply_patch_json = props['ApplyPatchJson']
restore_patch_json = props['RestorePatchJson']
patch_type = props['PatchType']

patch_json = None
if request_type == 'Create' or request_type == 'Update':
Expand All @@ -42,7 +43,7 @@ def patch_handler(event, context):
else:
raise Exception("invalid request type %s" % request_type)

kubectl([ 'patch', resource_name, '-n', resource_namespace, '-p', patch_json ])
kubectl([ 'patch', resource_name, '-n', resource_namespace, '-p', patch_json, '--type', patch_type ])


def kubectl(args):
Expand Down
38 changes: 19 additions & 19 deletions packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json
Expand Up @@ -1756,7 +1756,7 @@
},
"/",
{
"Ref": "AssetParameters6a008e167065eeab066c7f96e7f3c21c2636476b93c075681fba2953ae54a859S3BucketBA51B749"
"Ref": "AssetParametersb2a83bc01824acea756ffd355b4f53ae58aacffc1525dc8c75796ebb74cd8f87S3BucketB4483E96"
},
"/",
{
Expand All @@ -1766,7 +1766,7 @@
"Fn::Split": [
"||",
{
"Ref": "AssetParameters6a008e167065eeab066c7f96e7f3c21c2636476b93c075681fba2953ae54a859S3VersionKey723A87EA"
"Ref": "AssetParametersb2a83bc01824acea756ffd355b4f53ae58aacffc1525dc8c75796ebb74cd8f87S3VersionKey0402E731"
}
]
}
Expand All @@ -1779,7 +1779,7 @@
"Fn::Split": [
"||",
{
"Ref": "AssetParameters6a008e167065eeab066c7f96e7f3c21c2636476b93c075681fba2953ae54a859S3VersionKey723A87EA"
"Ref": "AssetParametersb2a83bc01824acea756ffd355b4f53ae58aacffc1525dc8c75796ebb74cd8f87S3VersionKey0402E731"
}
]
}
Expand All @@ -1789,11 +1789,11 @@
]
},
"Parameters": {
"referencetoawscdkeksclustertestAssetParameters809b8ac7e88704d37fac32bbd5cfa56be7ea4d3e9ddb682d216c4b6868cd8fa2S3Bucket5C1311C2Ref": {
"Ref": "AssetParameters809b8ac7e88704d37fac32bbd5cfa56be7ea4d3e9ddb682d216c4b6868cd8fa2S3Bucket8A1A4BE8"
"referencetoawscdkeksclustertestAssetParametersa6d508eaaa0d3cddbb47a84123fc878809c8431c5466f360912f70b5b9770afbS3Bucket6A8A7186Ref": {
"Ref": "AssetParametersa6d508eaaa0d3cddbb47a84123fc878809c8431c5466f360912f70b5b9770afbS3Bucket0C3A00C2"
},
"referencetoawscdkeksclustertestAssetParameters809b8ac7e88704d37fac32bbd5cfa56be7ea4d3e9ddb682d216c4b6868cd8fa2S3VersionKey33922910Ref": {
"Ref": "AssetParameters809b8ac7e88704d37fac32bbd5cfa56be7ea4d3e9ddb682d216c4b6868cd8fa2S3VersionKeyB580A234"
"referencetoawscdkeksclustertestAssetParametersa6d508eaaa0d3cddbb47a84123fc878809c8431c5466f360912f70b5b9770afbS3VersionKeyA18C5C39Ref": {
"Ref": "AssetParametersa6d508eaaa0d3cddbb47a84123fc878809c8431c5466f360912f70b5b9770afbS3VersionKeyBED95764"
},
"referencetoawscdkeksclustertestAssetParameters6c3e21f76e4ba0bc4b901f71bfa9c1eaf7929edcfd9a1591690d12b024100044S3Bucket24E1CF9DRef": {
"Ref": "AssetParameters6c3e21f76e4ba0bc4b901f71bfa9c1eaf7929edcfd9a1591690d12b024100044S3Bucket75CDEB48"
Expand Down Expand Up @@ -1901,29 +1901,29 @@
"Type": "String",
"Description": "Artifact hash for asset \"6c3e21f76e4ba0bc4b901f71bfa9c1eaf7929edcfd9a1591690d12b024100044\""
},
"AssetParameters809b8ac7e88704d37fac32bbd5cfa56be7ea4d3e9ddb682d216c4b6868cd8fa2S3Bucket8A1A4BE8": {
"AssetParametersa6d508eaaa0d3cddbb47a84123fc878809c8431c5466f360912f70b5b9770afbS3Bucket0C3A00C2": {
"Type": "String",
"Description": "S3 bucket for asset \"809b8ac7e88704d37fac32bbd5cfa56be7ea4d3e9ddb682d216c4b6868cd8fa2\""
"Description": "S3 bucket for asset \"a6d508eaaa0d3cddbb47a84123fc878809c8431c5466f360912f70b5b9770afb\""
},
"AssetParameters809b8ac7e88704d37fac32bbd5cfa56be7ea4d3e9ddb682d216c4b6868cd8fa2S3VersionKeyB580A234": {
"AssetParametersa6d508eaaa0d3cddbb47a84123fc878809c8431c5466f360912f70b5b9770afbS3VersionKeyBED95764": {
"Type": "String",
"Description": "S3 key for asset version \"809b8ac7e88704d37fac32bbd5cfa56be7ea4d3e9ddb682d216c4b6868cd8fa2\""
"Description": "S3 key for asset version \"a6d508eaaa0d3cddbb47a84123fc878809c8431c5466f360912f70b5b9770afb\""
},
"AssetParameters809b8ac7e88704d37fac32bbd5cfa56be7ea4d3e9ddb682d216c4b6868cd8fa2ArtifactHash5CE7C76A": {
"AssetParametersa6d508eaaa0d3cddbb47a84123fc878809c8431c5466f360912f70b5b9770afbArtifactHashBF08C2D7": {
"Type": "String",
"Description": "Artifact hash for asset \"809b8ac7e88704d37fac32bbd5cfa56be7ea4d3e9ddb682d216c4b6868cd8fa2\""
"Description": "Artifact hash for asset \"a6d508eaaa0d3cddbb47a84123fc878809c8431c5466f360912f70b5b9770afb\""
},
"AssetParameters6a008e167065eeab066c7f96e7f3c21c2636476b93c075681fba2953ae54a859S3BucketBA51B749": {
"AssetParametersb2a83bc01824acea756ffd355b4f53ae58aacffc1525dc8c75796ebb74cd8f87S3BucketB4483E96": {
"Type": "String",
"Description": "S3 bucket for asset \"6a008e167065eeab066c7f96e7f3c21c2636476b93c075681fba2953ae54a859\""
"Description": "S3 bucket for asset \"b2a83bc01824acea756ffd355b4f53ae58aacffc1525dc8c75796ebb74cd8f87\""
},
"AssetParameters6a008e167065eeab066c7f96e7f3c21c2636476b93c075681fba2953ae54a859S3VersionKey723A87EA": {
"AssetParametersb2a83bc01824acea756ffd355b4f53ae58aacffc1525dc8c75796ebb74cd8f87S3VersionKey0402E731": {
"Type": "String",
"Description": "S3 key for asset version \"6a008e167065eeab066c7f96e7f3c21c2636476b93c075681fba2953ae54a859\""
"Description": "S3 key for asset version \"b2a83bc01824acea756ffd355b4f53ae58aacffc1525dc8c75796ebb74cd8f87\""
},
"AssetParameters6a008e167065eeab066c7f96e7f3c21c2636476b93c075681fba2953ae54a859ArtifactHash22D2ECF0": {
"AssetParametersb2a83bc01824acea756ffd355b4f53ae58aacffc1525dc8c75796ebb74cd8f87ArtifactHash1FD4BC6F": {
"Type": "String",
"Description": "Artifact hash for asset \"6a008e167065eeab066c7f96e7f3c21c2636476b93c075681fba2953ae54a859\""
"Description": "Artifact hash for asset \"b2a83bc01824acea756ffd355b4f53ae58aacffc1525dc8c75796ebb74cd8f87\""
},
"AssetParameters6348c4414dfcbc19ed407c51ecc75d12faf4ee3219e972437d4ceed53e5b79a0S3BucketEF51ACE0": {
"Type": "String",
Expand Down
61 changes: 60 additions & 1 deletion packages/@aws-cdk/aws-eks/test/test.k8s-patch.ts
Expand Up @@ -2,7 +2,7 @@ import { expect, haveResource } from '@aws-cdk/assert';
import { Stack } from '@aws-cdk/core';
import { Test } from 'nodeunit';
import * as eks from '../lib';
import { KubernetesPatch } from '../lib/k8s-patch';
import { KubernetesPatch, PatchType } from '../lib/k8s-patch';

export = {
'applies a patch to k8s'(test: Test) {
Expand Down Expand Up @@ -41,5 +41,64 @@ export = {
}
}));
test.done();
},
'defaults to "strategic" patch type if no patchType is specified'(test: Test) {
// GIVEN
const stack = new Stack();
const cluster = new eks.Cluster(stack, 'MyCluster');

// WHEN
new KubernetesPatch(stack, 'MyPatch', {
cluster,
applyPatch: { patch: { to: 'apply' } },
restorePatch: { restore: { patch: 123 }},
resourceName: 'myResourceName',
});
expect(stack).to(haveResource('Custom::AWSCDK-EKS-KubernetesPatch', {
PatchType: "strategic"
}));
test.done();
},
'uses specified to patch type if specified'(test: Test) {
// GIVEN
const stack = new Stack();
const cluster = new eks.Cluster(stack, 'MyCluster');

// WHEN
new KubernetesPatch(stack, 'jsonPatch', {
cluster,
applyPatch: { patch: { to: 'apply' } },
restorePatch: { restore: { patch: 123 }},
resourceName: 'jsonPatchResource',
patchType: PatchType.JSON
});
new KubernetesPatch(stack, 'mergePatch', {
cluster,
applyPatch: { patch: { to: 'apply' } },
restorePatch: { restore: { patch: 123 }},
resourceName: 'mergePatchResource',
patchType: PatchType.MERGE
});
new KubernetesPatch(stack, 'strategicPatch', {
cluster,
applyPatch: { patch: { to: 'apply' } },
restorePatch: { restore: { patch: 123 }},
resourceName: 'strategicPatchResource',
patchType: PatchType.STRATEGIC
});

expect(stack).to(haveResource('Custom::AWSCDK-EKS-KubernetesPatch', {
ResourceName: "jsonPatchResource",
PatchType: "json"
}));
expect(stack).to(haveResource('Custom::AWSCDK-EKS-KubernetesPatch', {
ResourceName: "mergePatchResource",
PatchType: "merge"
}));
expect(stack).to(haveResource('Custom::AWSCDK-EKS-KubernetesPatch', {
ResourceName: "strategicPatchResource",
PatchType: "strategic"
}));
test.done();
}
};

0 comments on commit c7fab5b

Please sign in to comment.