├── templates └── hooks │ ├── global_table │ └── sdk_create_pre_build_request.go.tpl │ ├── backup │ └── sdk_read_one_post_set_output.go.tpl │ └── table │ ├── sdk_create_post_set_output.go.tpl │ ├── sdk_delete_pre_build_request.go.tpl │ └── sdk_read_one_post_set_output.go.tpl ├── test └── e2e │ ├── .gitignore │ ├── requirements.txt │ ├── resources │ ├── backup.yaml │ ├── global_table.yaml │ ├── table_basic.yaml │ ├── table_with_replicas_invalid.yaml │ ├── table_basic_pay_per_request.yaml │ ├── table_insights.yaml │ ├── table_resource_policy.yaml │ ├── table_with_replicas.yaml │ ├── table_local_secondary_indexes.yaml │ ├── table_global_secondary_indexes.yaml │ ├── table_with_gsi_and_replicas.yaml │ ├── table_multiple_gsi_and_replicas.yaml │ └── table_multiple_gsi_and_replicas_provisioned.yaml │ ├── replacement_values.py │ ├── service_cleanup.py │ ├── service_bootstrap.py │ ├── bootstrap_resources.py │ ├── conftest.py │ ├── __init__.py │ ├── tests │ ├── test_backup.py │ └── test_global_table.py │ └── condition.py ├── NOTICE ├── config ├── iam │ └── recommended-policy-arn ├── overlays │ └── namespaced │ │ ├── role.json │ │ ├── role-binding.json │ │ └── kustomization.yaml ├── rbac │ ├── service-account.yaml │ ├── kustomization.yaml │ ├── role-reader.yaml │ ├── cluster-role-binding.yaml │ ├── leader-election-role-binding.yaml │ ├── leader-election-role.yaml │ ├── role-writer.yaml │ └── cluster-role-controller.yaml ├── crd │ ├── common │ │ ├── kustomization.yaml │ │ └── bases │ │ │ ├── services.k8s.aws_iamroleselectors.yaml │ │ │ └── services.k8s.aws_fieldexports.yaml │ └── kustomization.yaml ├── controller │ ├── kustomization.yaml │ ├── service.yaml │ └── deployment.yaml └── default │ └── kustomization.yaml ├── .gitignore ├── OWNERS ├── apis └── v1alpha1 │ ├── doc.go │ ├── ack-generate-metadata.yaml │ ├── groupversion_info.go │ ├── global_table.go │ ├── backup.go │ └── generator.yaml ├── .github └── workflows │ ├── postsubmit.yaml │ └── create-release.yml ├── metadata.yaml ├── CODE_OF_CONDUCT.md ├── OWNERS_ALIASES ├── Makefile ├── helm ├── Chart.yaml ├── templates │ ├── service-account.yaml │ ├── NOTES.txt │ ├── role-reader.yaml │ ├── role-writer.yaml │ ├── leader-election-role.yaml │ ├── leader-election-role-binding.yaml │ ├── metrics-service.yaml │ ├── caches-role.yaml │ ├── cluster-role-controller.yaml │ ├── caches-role-binding.yaml │ ├── cluster-role-binding.yaml │ └── _helpers.tpl └── crds │ ├── services.k8s.aws_iamroleselectors.yaml │ └── services.k8s.aws_fieldexports.yaml ├── pkg ├── version │ └── version.go └── resource │ ├── table │ ├── common.go │ ├── identifiers.go │ ├── hooks_global_secondary_indexes_test.go │ ├── hooks_continuous_backup.go │ ├── hooks_ttl.go │ ├── conditions.go │ ├── manager_factory.go │ ├── resource.go │ ├── tags.go │ ├── hooks_tags.go │ ├── descriptor.go │ └── hooks_resource_policy.go │ ├── global_table │ ├── custom_api.go │ ├── identifiers.go │ ├── delta.go │ ├── references.go │ ├── manager_factory.go │ ├── resource.go │ └── descriptor.go │ ├── backup │ ├── hooks.go │ ├── identifiers.go │ ├── delta.go │ ├── references.go │ ├── manager_factory.go │ ├── resource.go │ └── descriptor.go │ └── registry.go ├── README.md ├── olm └── olmconfig.yaml ├── GOVERNANCE.md ├── CONTRIBUTING.md ├── go.mod └── generator.yaml /templates/hooks/global_table/sdk_create_pre_build_request.go.tpl: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/e2e/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | *.py[cod] 3 | **/bootstrap.yaml -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | -------------------------------------------------------------------------------- /config/iam/recommended-policy-arn: -------------------------------------------------------------------------------- 1 | arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.swp 3 | *~ 4 | .idea 5 | .vscode 6 | /docs/site 7 | bin 8 | build 9 | -------------------------------------------------------------------------------- /OWNERS: -------------------------------------------------------------------------------- 1 | # See the OWNERS docs at https://go.k8s.io/owners 2 | 3 | approvers: 4 | - core-ack-team -------------------------------------------------------------------------------- /test/e2e/requirements.txt: -------------------------------------------------------------------------------- 1 | acktest @ git+https://github.com/aws-controllers-k8s/test-infra.git@a53a1840e135ba800b4f2aa42b112ba9389f82a7 2 | -------------------------------------------------------------------------------- /templates/hooks/backup/sdk_read_one_post_set_output.go.tpl: -------------------------------------------------------------------------------- 1 | if isBackupCreating(&resource{ko}) { 2 | return &resource{ko}, requeueWaitWhileCreating 3 | } -------------------------------------------------------------------------------- /config/overlays/namespaced/role.json: -------------------------------------------------------------------------------- 1 | [{"op": "replace", "path": "/kind", "value": "Role"}, 2 | {"op": "add", "path": "/metadata/namespace", "value": "ack-system"}] -------------------------------------------------------------------------------- /config/rbac/service-account.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: ack-dynamodb-controller 6 | namespace: ack-system 7 | -------------------------------------------------------------------------------- /test/e2e/resources/backup.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: dynamodb.services.k8s.aws/v1alpha1 2 | kind: Backup 3 | metadata: 4 | name: $BACKUP_NAME 5 | spec: 6 | backupName: $BACKUP_NAME 7 | tableName: $TABLE_NAME -------------------------------------------------------------------------------- /apis/v1alpha1/doc.go: -------------------------------------------------------------------------------- 1 | // +k8s:deepcopy-gen=package 2 | // Package v1alpha1 is the v1alpha1 version of the dynamodb.services.k8s.aws API. 3 | // +groupName=dynamodb.services.k8s.aws 4 | package v1alpha1 5 | -------------------------------------------------------------------------------- /config/overlays/namespaced/role-binding.json: -------------------------------------------------------------------------------- 1 | [{"op": "replace", "path": "/kind", "value": "RoleBinding"}, 2 | {"op": "add", "path": "/metadata/namespace", "value": "ack-system"}, 3 | {"op": "replace", "path": "/roleRef/kind", "value": "Role"}] -------------------------------------------------------------------------------- /config/rbac/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - cluster-role-binding.yaml 3 | - cluster-role-controller.yaml 4 | - role-reader.yaml 5 | - role-writer.yaml 6 | - service-account.yaml 7 | - leader-election-role.yaml 8 | - leader-election-role-binding.yaml 9 | -------------------------------------------------------------------------------- /test/e2e/resources/global_table.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: dynamodb.services.k8s.aws/v1alpha1 2 | kind: GlobalTable 3 | metadata: 4 | name: $GLOBAL_TABLE_NAME 5 | spec: 6 | globalTableName: $GLOBAL_TABLE_NAME 7 | replicationGroup: 8 | - regionName: $REGION_NAME -------------------------------------------------------------------------------- /.github/workflows/postsubmit.yaml: -------------------------------------------------------------------------------- 1 | name: Hydrate Go Proxy 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | call-hydrate-go-proxy: 10 | uses: aws-controllers-k8s/.github/.github/workflows/reusable-postsubmit.yaml@main 11 | -------------------------------------------------------------------------------- /config/crd/common/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # Code generated in runtime. DO NOT EDIT. 2 | 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | resources: 6 | - bases/services.k8s.aws_iamroleselectors.yaml 7 | - bases/services.k8s.aws_fieldexports.yaml 8 | -------------------------------------------------------------------------------- /config/crd/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - common 5 | - bases/dynamodb.services.k8s.aws_backups.yaml 6 | - bases/dynamodb.services.k8s.aws_globaltables.yaml 7 | - bases/dynamodb.services.k8s.aws_tables.yaml 8 | -------------------------------------------------------------------------------- /templates/hooks/table/sdk_create_post_set_output.go.tpl: -------------------------------------------------------------------------------- 1 | // handle in sdkUpdate, to give resource time until it creates 2 | if desired.ko.Spec.TimeToLive != nil || desired.ko.Spec.ContributorInsights != nil { 3 | ackcondition.SetSynced(&resource{ko}, corev1.ConditionFalse, nil, nil) 4 | } 5 | -------------------------------------------------------------------------------- /config/controller/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - deployment.yaml 3 | - service.yaml 4 | apiVersion: kustomize.config.k8s.io/v1beta1 5 | kind: Kustomization 6 | images: 7 | - name: controller 8 | newName: public.ecr.aws/aws-controllers-k8s/dynamodb-controller 9 | newTag: 1.7.0 10 | -------------------------------------------------------------------------------- /metadata.yaml: -------------------------------------------------------------------------------- 1 | service: 2 | full_name: "Amazon DynamoDB" 3 | short_name: "DynamoDB" 4 | link: "https://aws.amazon.com/dynamodb/" 5 | documentation: "https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Introduction.html" 6 | api_versions: 7 | - api_version: v1alpha1 8 | status: available 9 | -------------------------------------------------------------------------------- /.github/workflows/create-release.yml: -------------------------------------------------------------------------------- 1 | name: Create Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*.*.*" 7 | 8 | permissions: 9 | contents: write # For creating releases 10 | 11 | jobs: 12 | call-create-release: 13 | uses: aws-controllers-k8s/.github/.github/workflows/reusable-create-release.yaml@main 14 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /config/controller/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: ack-dynamodb-metrics-service 5 | namespace: ack-system 6 | spec: 7 | selector: 8 | app.kubernetes.io/name: ack-dynamodb-controller 9 | ports: 10 | - name: metricsport 11 | port: 8080 12 | targetPort: http 13 | protocol: TCP 14 | type: ClusterIP 15 | -------------------------------------------------------------------------------- /config/rbac/role-reader.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | creationTimestamp: null 6 | name: ack-dynamodb-reader 7 | namespace: default 8 | rules: 9 | - apiGroups: 10 | - dynamodb.services.k8s.aws 11 | resources: 12 | - backups 13 | - globaltables 14 | - tables 15 | verbs: 16 | - get 17 | - list 18 | - watch 19 | -------------------------------------------------------------------------------- /config/rbac/cluster-role-binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: ack-dynamodb-controller-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: ack-dynamodb-controller 9 | subjects: 10 | - kind: ServiceAccount 11 | name: ack-dynamodb-controller 12 | namespace: ack-system 13 | -------------------------------------------------------------------------------- /OWNERS_ALIASES: -------------------------------------------------------------------------------- 1 | # See the OWNERS docs at https://go.k8s.io/owners#owners_aliases 2 | 3 | aliases: 4 | core-ack-team: 5 | - a-hilaly 6 | - jlbutler 7 | - michaelhtm 8 | - rushmash91 9 | - knottnt 10 | # emeritus-core-ack-team: 11 | # - TiberiuGC 12 | # - jaypipes 13 | # - jljaco 14 | # - mhausenblas 15 | # - RedbackThomson 16 | # - vijtrip2 17 | # - ivelichkovich -------------------------------------------------------------------------------- /config/rbac/leader-election-role-binding.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: RoleBinding 4 | metadata: 5 | namespace: ack-system 6 | name: dynamodb-leader-election-rolebinding 7 | roleRef: 8 | apiGroup: rbac.authorization.k8s.io 9 | kind: Role 10 | name: dynamodb-leader-election-role 11 | subjects: 12 | - kind: ServiceAccount 13 | name: ack-dynamodb-controller 14 | namespace: ack-system 15 | -------------------------------------------------------------------------------- /config/overlays/namespaced/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - ../../default 3 | patches: 4 | - path: role.json 5 | target: 6 | group: rbac.authorization.k8s.io 7 | version: v1 8 | kind: ClusterRole 9 | name: ack-dynamodb-controller 10 | - path: role-binding.json 11 | target: 12 | group: rbac.authorization.k8s.io 13 | version: v1 14 | kind: ClusterRoleBinding 15 | name: ack-dynamodb-controller-rolebinding -------------------------------------------------------------------------------- /config/rbac/leader-election-role.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | name: dynamodb-leader-election-role 6 | namespace: ack-system 7 | rules: 8 | - apiGroups: 9 | - coordination.k8s.io 10 | resources: 11 | - leases 12 | verbs: 13 | - get 14 | - list 15 | - watch 16 | - create 17 | - update 18 | - patch 19 | - delete 20 | - apiGroups: 21 | - "" 22 | resources: 23 | - events 24 | verbs: 25 | - create 26 | - patch 27 | -------------------------------------------------------------------------------- /apis/v1alpha1/ack-generate-metadata.yaml: -------------------------------------------------------------------------------- 1 | ack_generate_info: 2 | build_date: "2025-11-29T02:57:37Z" 3 | build_hash: 23c7074fa310ad1ccb38946775397c203b49f024 4 | go_version: go1.25.4 5 | version: v0.56.0 6 | api_directory_checksum: d2887bf57c4e94a2687e17c41f74c875131c0beb 7 | api_version: v1alpha1 8 | aws_sdk_go_version: v1.32.6 9 | generator_config_info: 10 | file_checksum: 3c4832feff83bc9c29b40bc73bafc1d7e75ab1cd 11 | original_file_name: generator.yaml 12 | last_modification: 13 | reason: API generation 14 | -------------------------------------------------------------------------------- /test/e2e/resources/table_basic.yaml: -------------------------------------------------------------------------------- 1 | # Table used to test multiple interfering updates at once 2 | apiVersion: dynamodb.services.k8s.aws/v1alpha1 3 | kind: Table 4 | metadata: 5 | name: $TABLE_NAME 6 | spec: 7 | tableName: $TABLE_NAME 8 | billingMode: PAY_PER_REQUEST 9 | tableClass: STANDARD 10 | attributeDefinitions: 11 | - attributeName: Bill 12 | attributeType: S 13 | - attributeName: Total 14 | attributeType: S 15 | keySchema: 16 | - attributeName: Bill 17 | keyType: HASH 18 | - attributeName: Total 19 | keyType: RANGE -------------------------------------------------------------------------------- /config/default/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # Adds namespace to all resources. 2 | # namespace: 3 | 4 | # Value of this field is prepended to the 5 | # names of all resources, e.g. a deployment named 6 | # "wordpress" becomes "alices-wordpress". 7 | # Note that it should also match with the prefix (text before '-') of the namespace 8 | # field above. 9 | # namePrefix: 10 | 11 | # Labels to add to all resources and selectors. 12 | #commonLabels: 13 | # someName: someValue 14 | 15 | resources: 16 | - ../crd 17 | - ../rbac 18 | - ../controller 19 | 20 | patchesStrategicMerge: 21 | -------------------------------------------------------------------------------- /test/e2e/resources/table_with_replicas_invalid.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: dynamodb.services.k8s.aws/v1alpha1 2 | kind: Table 3 | metadata: 4 | name: $TABLE_NAME 5 | spec: 6 | tableName: $TABLE_NAME 7 | tableClass: STANDARD 8 | attributeDefinitions: 9 | - attributeName: PK 10 | attributeType: S 11 | - attributeName: SK 12 | attributeType: S 13 | keySchema: 14 | - attributeName: PK 15 | keyType: HASH 16 | - attributeName: SK 17 | keyType: RANGE 18 | billingMode: PAY_PER_REQUEST 19 | tableReplicas: 20 | - regionName: $REPLICA_REGION_1 21 | -------------------------------------------------------------------------------- /test/e2e/resources/table_basic_pay_per_request.yaml: -------------------------------------------------------------------------------- 1 | # Table used to test GSI creation under on-demand billing mode 2 | apiVersion: dynamodb.services.k8s.aws/v1alpha1 3 | kind: Table 4 | metadata: 5 | name: $TABLE_NAME 6 | spec: 7 | tableName: $TABLE_NAME 8 | billingMode: PAY_PER_REQUEST 9 | tableClass: STANDARD 10 | attributeDefinitions: 11 | - attributeName: Bill 12 | attributeType: S 13 | - attributeName: Total 14 | attributeType: S 15 | keySchema: 16 | - attributeName: Bill 17 | keyType: HASH 18 | - attributeName: Total 19 | keyType: RANGE -------------------------------------------------------------------------------- /test/e2e/resources/table_insights.yaml: -------------------------------------------------------------------------------- 1 | # Table used to test multiple interfering updates at once 2 | apiVersion: dynamodb.services.k8s.aws/v1alpha1 3 | kind: Table 4 | metadata: 5 | name: $TABLE_NAME 6 | spec: 7 | tableName: $TABLE_NAME 8 | billingMode: PAY_PER_REQUEST 9 | tableClass: STANDARD 10 | contributorInsights: ENABLED 11 | attributeDefinitions: 12 | - attributeName: Bill 13 | attributeType: S 14 | - attributeName: Total 15 | attributeType: S 16 | keySchema: 17 | - attributeName: Bill 18 | keyType: HASH 19 | - attributeName: Total 20 | keyType: RANGE 21 | -------------------------------------------------------------------------------- /test/e2e/resources/table_resource_policy.yaml: -------------------------------------------------------------------------------- 1 | # Table used to test resource policy functionality 2 | apiVersion: dynamodb.services.k8s.aws/v1alpha1 3 | kind: Table 4 | metadata: 5 | name: $TABLE_NAME 6 | spec: 7 | tableName: $TABLE_NAME 8 | billingMode: PAY_PER_REQUEST 9 | tableClass: STANDARD 10 | attributeDefinitions: 11 | - attributeName: Bill 12 | attributeType: S 13 | - attributeName: Total 14 | attributeType: S 15 | keySchema: 16 | - attributeName: Bill 17 | keyType: HASH 18 | - attributeName: Total 19 | keyType: RANGE 20 | resourcePolicy: '$RESOURCE_POLICY' 21 | -------------------------------------------------------------------------------- /config/rbac/role-writer.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | creationTimestamp: null 6 | name: ack-dynamodb-writer 7 | namespace: default 8 | rules: 9 | - apiGroups: 10 | - dynamodb.services.k8s.aws 11 | resources: 12 | - backups 13 | - globaltables 14 | - tables 15 | verbs: 16 | - create 17 | - delete 18 | - get 19 | - list 20 | - patch 21 | - update 22 | - watch 23 | - apiGroups: 24 | - dynamodb.services.k8s.aws 25 | resources: 26 | - backups 27 | - globaltables 28 | - tables 29 | verbs: 30 | - get 31 | - patch 32 | - update 33 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL := /bin/bash # Use bash syntax 2 | 3 | # Set up variables 4 | GO111MODULE=on 5 | 6 | # Build ldflags 7 | VERSION ?= "v0.0.0" 8 | GITCOMMIT=$(shell git rev-parse HEAD) 9 | BUILDDATE=$(shell date -u +'%Y-%m-%dT%H:%M:%SZ') 10 | GO_LDFLAGS=-ldflags "-X main.version=$(VERSION) \ 11 | -X main.buildHash=$(GITCOMMIT) \ 12 | -X main.buildDate=$(BUILDDATE)" 13 | 14 | .PHONY: all test 15 | 16 | all: test 17 | 18 | test: ## Run code tests 19 | go test -v ./... 20 | 21 | help: ## Show this help. 22 | @grep -F -h "##" $(MAKEFILE_LIST) | grep -F -v grep | sed -e 's/\\$$//' \ 23 | | awk -F'[:#]' '{print $$1 = sprintf("%-30s", $$1), $$4}' 24 | -------------------------------------------------------------------------------- /helm/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | name: dynamodb-chart 3 | description: A Helm chart for the ACK service controller for Amazon DynamoDB (DynamoDB) 4 | version: 1.7.0 5 | appVersion: 1.7.0 6 | home: https://github.com/aws-controllers-k8s/dynamodb-controller 7 | icon: https://raw.githubusercontent.com/aws/eks-charts/master/docs/logo/aws.png 8 | sources: 9 | - https://github.com/aws-controllers-k8s/dynamodb-controller 10 | maintainers: 11 | - name: ACK Admins 12 | url: https://github.com/orgs/aws-controllers-k8s/teams/ack-admin 13 | - name: DynamoDB Admins 14 | url: https://github.com/orgs/aws-controllers-k8s/teams/dynamodb-maintainer 15 | keywords: 16 | - aws 17 | - kubernetes 18 | - dynamodb 19 | -------------------------------------------------------------------------------- /test/e2e/resources/table_with_replicas.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: dynamodb.services.k8s.aws/v1alpha1 2 | kind: Table 3 | metadata: 4 | name: $TABLE_NAME 5 | spec: 6 | tableName: $TABLE_NAME 7 | tableClass: STANDARD 8 | attributeDefinitions: 9 | - attributeName: PK 10 | attributeType: S 11 | - attributeName: SK 12 | attributeType: S 13 | keySchema: 14 | - attributeName: PK 15 | keyType: HASH 16 | - attributeName: SK 17 | keyType: RANGE 18 | billingMode: PAY_PER_REQUEST 19 | streamSpecification: 20 | streamEnabled: true 21 | streamViewType: "NEW_AND_OLD_IMAGES" 22 | tableReplicas: 23 | - regionName: $REPLICA_REGION_1 24 | - regionName: $REPLICA_REGION_2 25 | -------------------------------------------------------------------------------- /templates/hooks/table/sdk_delete_pre_build_request.go.tpl: -------------------------------------------------------------------------------- 1 | if isTableDeleting(r) { 2 | return nil, requeueWaitWhileDeleting 3 | } 4 | if isTableUpdating(r) { 5 | return nil, requeueWaitWhileUpdating 6 | } 7 | 8 | // If there are replicas, we need to remove them before deleting the table 9 | if len(r.ko.Spec.TableReplicas) > 0 { 10 | desired := &resource{ 11 | ko: r.ko.DeepCopy(), 12 | } 13 | desired.ko.Spec.TableReplicas = nil 14 | 15 | err := rm.syncReplicas(ctx, r, desired) 16 | if err != nil { 17 | return nil, err 18 | } 19 | // Requeue to wait for replica removal to complete before attempting table deletion 20 | // When syncReplicas returns an error other than requeue 21 | return r, requeueWaitWhileDeleting 22 | } -------------------------------------------------------------------------------- /pkg/version/version.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by ack-generate. DO NOT EDIT. 15 | 16 | package version 17 | 18 | var ( 19 | GitVersion string 20 | GitCommit string 21 | BuildDate string 22 | ) 23 | -------------------------------------------------------------------------------- /test/e2e/replacement_values.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | # not use this file except in compliance with the License. A copy of the 5 | # License is located at 6 | # 7 | # http://aws.amazon.com/apache2.0/ 8 | # 9 | # or in the "license" file accompanying this file. This file is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | # express or implied. See the License for the specific language governing 12 | # permissions and limitations under the License. 13 | 14 | """Stores the values used by each of the integration tests for replacing the 15 | DynamoDB-specific test variables. 16 | """ 17 | 18 | REPLACEMENT_VALUES = { 19 | 20 | } 21 | -------------------------------------------------------------------------------- /helm/templates/service-account.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create }} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: {{ include "ack-dynamodb-controller.app.name" . }} 7 | app.kubernetes.io/instance: {{ .Release.Name }} 8 | app.kubernetes.io/managed-by: Helm 9 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 10 | k8s-app: {{ include "ack-dynamodb-controller.app.name" . }} 11 | helm.sh/chart: {{ include "ack-dynamodb-controller.chart.name-version" . }} 12 | name: {{ include "ack-dynamodb-controller.service-account.name" . }} 13 | namespace: {{ .Release.Namespace }} 14 | annotations: 15 | {{- range $key, $value := .Values.serviceAccount.annotations }} 16 | {{ $key }}: {{ $value | quote }} 17 | {{- end }} 18 | {{- end }} 19 | -------------------------------------------------------------------------------- /helm/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | {{ .Chart.Name }} has been installed. 2 | This chart deploys "public.ecr.aws/aws-controllers-k8s/dynamodb-controller:1.7.0". 3 | 4 | Check its status by running: 5 | kubectl --namespace {{ .Release.Namespace }} get pods -l "app.kubernetes.io/instance={{ .Release.Name }}" 6 | 7 | You are now able to create Amazon DynamoDB (DynamoDB) resources! 8 | 9 | The controller is running in "{{ .Values.installScope }}" mode. 10 | The controller is configured to manage AWS resources in region: "{{ .Values.aws.region }}" 11 | 12 | Visit https://aws-controllers-k8s.github.io/community/reference/ for an API 13 | reference of all the resources that can be created using this controller. 14 | 15 | For more information on the AWS Controllers for Kubernetes (ACK) project, visit: 16 | https://aws-controllers-k8s.github.io/community/ 17 | -------------------------------------------------------------------------------- /helm/templates/role-reader.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | creationTimestamp: null 6 | name: {{ include "ack-dynamodb-controller.app.fullname" . }}-reader 7 | namespace: {{ .Release.Namespace }} 8 | labels: 9 | app.kubernetes.io/name: {{ include "ack-dynamodb-controller.app.name" . }} 10 | app.kubernetes.io/instance: {{ .Release.Name }} 11 | app.kubernetes.io/managed-by: Helm 12 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 13 | k8s-app: {{ include "ack-dynamodb-controller.app.name" . }} 14 | helm.sh/chart: {{ include "ack-dynamodb-controller.chart.name-version" . }} 15 | rules: 16 | - apiGroups: 17 | - dynamodb.services.k8s.aws 18 | resources: 19 | - backups 20 | - globaltables 21 | - tables 22 | verbs: 23 | - get 24 | - list 25 | - watch 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ACK service controller for Amazon DynamoDB 2 | 3 | This repository contains source code for the AWS Controllers for Kubernetes 4 | (ACK) service controller for Amazon DynamoDB. 5 | 6 | Please [log issues][ack-issues] and feedback on the main AWS Controllers for 7 | Kubernetes Github project. 8 | 9 | [ack-issues]: https://github.com/aws/aws-controllers-k8s/issues 10 | 11 | ## Contributing 12 | 13 | We welcome community contributions and pull requests. 14 | 15 | See our [contribution guide](/CONTRIBUTING.md) for more information on how to 16 | report issues, set up a development environment, and submit code. 17 | 18 | We adhere to the [Amazon Open Source Code of Conduct][coc]. 19 | 20 | You can also learn more about our [Governance](/GOVERNANCE.md) structure. 21 | 22 | [coc]: https://aws.github.io/code-of-conduct 23 | 24 | ## License 25 | 26 | This project is [licensed](/LICENSE) under the Apache-2.0 License. 27 | -------------------------------------------------------------------------------- /test/e2e/resources/table_local_secondary_indexes.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: dynamodb.services.k8s.aws/v1alpha1 2 | kind: Table 3 | metadata: 4 | name: $TABLE_NAME 5 | spec: 6 | tableName: $TABLE_NAME 7 | tableClass: STANDARD 8 | attributeDefinitions: 9 | - attributeName: ForumName 10 | attributeType: S 11 | - attributeName: LastPostDateTime 12 | attributeType: S 13 | - attributeName: Subject 14 | attributeType: S 15 | keySchema: 16 | - attributeName: ForumName 17 | keyType: HASH 18 | - attributeName: Subject 19 | keyType: RANGE 20 | localSecondaryIndexes: 21 | - indexName: LastPostIndex 22 | keySchema: 23 | - attributeName: ForumName 24 | keyType: HASH 25 | - attributeName: LastPostDateTime 26 | keyType: RANGE 27 | projection: 28 | projectionType: KEYS_ONLY 29 | provisionedThroughput: 30 | readCapacityUnits: 5 31 | writeCapacityUnits: 5 32 | streamSpecification: 33 | streamEnabled: true 34 | streamViewType: "NEW_AND_OLD_IMAGES" -------------------------------------------------------------------------------- /test/e2e/resources/table_global_secondary_indexes.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: dynamodb.services.k8s.aws/v1alpha1 2 | kind: Table 3 | metadata: 4 | name: $TABLE_NAME 5 | spec: 6 | tableName: $TABLE_NAME 7 | attributeDefinitions: 8 | - attributeName: OfficeName 9 | attributeType: S 10 | - attributeName: Rank 11 | attributeType: S 12 | - attributeName: City 13 | attributeType: S 14 | keySchema: 15 | - attributeName: OfficeName 16 | keyType: HASH 17 | - attributeName: Rank 18 | keyType: RANGE 19 | provisionedThroughput: 20 | readCapacityUnits: 5 21 | writeCapacityUnits: 5 22 | billingMode: PROVISIONED 23 | globalSecondaryIndexes: 24 | - indexName: office-per-city 25 | keySchema: 26 | - attributeName: OfficeName 27 | keyType: HASH 28 | - attributeName: City 29 | keyType: RANGE 30 | projection: 31 | nonKeyAttributes: 32 | - Test 33 | projectionType: INCLUDE 34 | provisionedThroughput: 35 | readCapacityUnits: 5 36 | writeCapacityUnits: 5 37 | tableClass: STANDARD -------------------------------------------------------------------------------- /test/e2e/service_cleanup.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | # not use this file except in compliance with the License. A copy of the 5 | # License is located at 6 | # 7 | # http://aws.amazon.com/apache2.0/ 8 | # 9 | # or in the "license" file accompanying this file. This file is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | # express or implied. See the License for the specific language governing 12 | # permissions and limitations under the License. 13 | 14 | """Cleans up the resources created by the DynamoDB bootstrapping process. 15 | """ 16 | 17 | import logging 18 | 19 | from acktest.bootstrapping import Resources 20 | 21 | from e2e import bootstrap_directory 22 | 23 | def service_cleanup(): 24 | logging.getLogger().setLevel(logging.INFO) 25 | 26 | resources = Resources.deserialize(bootstrap_directory) 27 | resources.cleanup() 28 | 29 | if __name__ == "__main__": 30 | service_cleanup() 31 | -------------------------------------------------------------------------------- /helm/templates/role-writer.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | creationTimestamp: null 6 | name: {{ include "ack-dynamodb-controller.app.fullname" . }}-writer 7 | namespace: {{ .Release.Namespace }} 8 | labels: 9 | app.kubernetes.io/name: {{ include "ack-dynamodb-controller.app.name" . }} 10 | app.kubernetes.io/instance: {{ .Release.Name }} 11 | app.kubernetes.io/managed-by: Helm 12 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 13 | k8s-app: {{ include "ack-dynamodb-controller.app.name" . }} 14 | helm.sh/chart: {{ include "ack-dynamodb-controller.chart.name-version" . }} 15 | rules: 16 | - apiGroups: 17 | - dynamodb.services.k8s.aws 18 | resources: 19 | - backups 20 | - globaltables 21 | - tables 22 | verbs: 23 | - create 24 | - delete 25 | - get 26 | - list 27 | - patch 28 | - update 29 | - watch 30 | - apiGroups: 31 | - dynamodb.services.k8s.aws 32 | resources: 33 | - backups 34 | - globaltables 35 | - tables 36 | verbs: 37 | - get 38 | - patch 39 | - update 40 | -------------------------------------------------------------------------------- /helm/templates/leader-election-role.yaml: -------------------------------------------------------------------------------- 1 | {{ if .Values.leaderElection.enabled }} 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | name: {{ include "ack-dynamodb-controller.app.fullname" . }}-leaderelection 6 | {{ if .Values.leaderElection.namespace }} 7 | namespace: {{ .Values.leaderElection.namespace }} 8 | {{ else }} 9 | namespace: {{ .Release.Namespace }} 10 | {{ end }} 11 | labels: 12 | app.kubernetes.io/name: {{ include "ack-dynamodb-controller.app.name" . }} 13 | app.kubernetes.io/instance: {{ .Release.Name }} 14 | app.kubernetes.io/managed-by: Helm 15 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 16 | k8s-app: {{ include "ack-dynamodb-controller.app.name" . }} 17 | helm.sh/chart: {{ include "ack-dynamodb-controller.chart.name-version" . }} 18 | rules: 19 | - apiGroups: 20 | - coordination.k8s.io 21 | resources: 22 | - leases 23 | verbs: 24 | - get 25 | - list 26 | - watch 27 | - create 28 | - update 29 | - patch 30 | - delete 31 | - apiGroups: 32 | - "" 33 | resources: 34 | - events 35 | verbs: 36 | - create 37 | - patch{{- end }} 38 | -------------------------------------------------------------------------------- /helm/templates/leader-election-role-binding.yaml: -------------------------------------------------------------------------------- 1 | {{ if .Values.leaderElection.enabled }} 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: RoleBinding 4 | metadata: 5 | name: {{ include "ack-dynamodb-controller.app.fullname" . }}-leaderelection 6 | {{ if .Values.leaderElection.namespace }} 7 | namespace: {{ .Values.leaderElection.namespace }} 8 | {{ else }} 9 | namespace: {{ .Release.Namespace }} 10 | {{ end }} 11 | labels: 12 | app.kubernetes.io/name: {{ include "ack-dynamodb-controller.app.name" . }} 13 | app.kubernetes.io/instance: {{ .Release.Name }} 14 | app.kubernetes.io/managed-by: Helm 15 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 16 | k8s-app: {{ include "ack-dynamodb-controller.app.name" . }} 17 | helm.sh/chart: {{ include "ack-dynamodb-controller.chart.name-version" . }} 18 | roleRef: 19 | apiGroup: rbac.authorization.k8s.io 20 | kind: Role 21 | name: {{ include "ack-dynamodb-controller.app.fullname" . }}-leaderelection 22 | subjects: 23 | - kind: ServiceAccount 24 | name: {{ include "ack-dynamodb-controller.service-account.name" . }} 25 | namespace: {{ .Release.Namespace }}{{- end }} 26 | -------------------------------------------------------------------------------- /pkg/resource/table/common.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package table 15 | 16 | // TODO(hilalymh) Move these functions to aws-controllers-k8s/runtime 17 | 18 | func emptyString(s *string) bool { 19 | if s == nil { 20 | return true 21 | } 22 | return *s == "" 23 | } 24 | 25 | func equalInt64s(a, b *int64) bool { 26 | if a == nil { 27 | return b == nil || *b == 0 28 | } 29 | return (*a == 0 && b == nil) || *a == *b 30 | } 31 | 32 | func equalStrings(a, b *string) bool { 33 | if a == nil { 34 | return b == nil || *b == "" 35 | } 36 | return (*a == "" && b == nil) || *a == *b 37 | } 38 | -------------------------------------------------------------------------------- /helm/templates/metrics-service.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.metrics.service.create }} 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: {{ .Chart.Name | trimSuffix "-chart" | trunc 44 }}-controller-metrics 6 | namespace: {{ .Release.Namespace }} 7 | labels: 8 | app.kubernetes.io/name: {{ include "ack-dynamodb-controller.app.name" . }} 9 | app.kubernetes.io/instance: {{ .Release.Name }} 10 | app.kubernetes.io/managed-by: Helm 11 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 12 | k8s-app: {{ include "ack-dynamodb-controller.app.name" . }} 13 | helm.sh/chart: {{ include "ack-dynamodb-controller.chart.name-version" . }} 14 | spec: 15 | selector: 16 | app.kubernetes.io/name: {{ include "ack-dynamodb-controller.app.name" . }} 17 | app.kubernetes.io/instance: {{ .Release.Name }} 18 | app.kubernetes.io/managed-by: Helm 19 | k8s-app: {{ include "ack-dynamodb-controller.app.name" . }} 20 | {{- range $key, $value := .Values.deployment.labels }} 21 | {{ $key }}: {{ $value | quote }} 22 | {{- end }} 23 | type: {{ .Values.metrics.service.type }} 24 | ports: 25 | - name: metricsport 26 | port: 8080 27 | targetPort: http 28 | protocol: TCP 29 | {{- end }} 30 | -------------------------------------------------------------------------------- /pkg/resource/global_table/custom_api.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package global_table 15 | 16 | import ( 17 | svcsdk "github.com/aws/aws-sdk-go-v2/service/dynamodb" 18 | svcsdktypes "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" 19 | ) 20 | 21 | func customSetDeleteInput(r *resource, input *svcsdk.UpdateGlobalTableInput) { 22 | for _, replica := range r.ko.Spec.ReplicationGroup { 23 | replicaUpdate := svcsdktypes.ReplicaUpdate{ 24 | Delete: &svcsdktypes.DeleteReplicaAction{ 25 | RegionName: replica.RegionName, 26 | }, 27 | } 28 | input.ReplicaUpdates = append(input.ReplicaUpdates, replicaUpdate) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test/e2e/resources/table_with_gsi_and_replicas.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: dynamodb.services.k8s.aws/v1alpha1 2 | kind: Table 3 | metadata: 4 | name: $TABLE_NAME 5 | spec: 6 | tableName: $TABLE_NAME 7 | tableClass: STANDARD 8 | attributeDefinitions: 9 | - attributeName: PK 10 | attributeType: S 11 | - attributeName: SK 12 | attributeType: S 13 | - attributeName: GSI1PK 14 | attributeType: S 15 | - attributeName: GSI1SK 16 | attributeType: S 17 | keySchema: 18 | - attributeName: PK 19 | keyType: HASH 20 | - attributeName: SK 21 | keyType: RANGE 22 | billingMode: PAY_PER_REQUEST 23 | streamSpecification: 24 | streamEnabled: true 25 | streamViewType: "NEW_AND_OLD_IMAGES" 26 | tableReplicas: 27 | - regionName: $REPLICA_REGION_1 28 | globalSecondaryIndexes: 29 | - indexName: GSI1 30 | - regionName: $REPLICA_REGION_2 31 | globalSecondaryIndexes: 32 | - indexName: GSI1 33 | globalSecondaryIndexes: 34 | - indexName: GSI1 35 | keySchema: 36 | - attributeName: GSI1PK 37 | keyType: HASH 38 | - attributeName: GSI1SK 39 | keyType: RANGE 40 | projection: 41 | projectionType: ALL -------------------------------------------------------------------------------- /apis/v1alpha1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by ack-generate. DO NOT EDIT. 15 | 16 | package v1alpha1 17 | 18 | import ( 19 | "k8s.io/apimachinery/pkg/runtime/schema" 20 | "sigs.k8s.io/controller-runtime/pkg/scheme" 21 | ) 22 | 23 | var ( 24 | // GroupVersion is the API Group Version used to register the objects 25 | GroupVersion = schema.GroupVersion{Group: "dynamodb.services.k8s.aws", Version: "v1alpha1"} 26 | 27 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 28 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 29 | 30 | // AddToScheme adds the types in this group-version to the given scheme. 31 | AddToScheme = SchemeBuilder.AddToScheme 32 | ) 33 | -------------------------------------------------------------------------------- /test/e2e/service_bootstrap.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | # not use this file except in compliance with the License. A copy of the 5 | # License is located at 6 | # 7 | # http://aws.amazon.com/apache2.0/ 8 | # 9 | # or in the "license" file accompanying this file. This file is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | # express or implied. See the License for the specific language governing 12 | # permissions and limitations under the License. 13 | 14 | """Bootstraps the resources required to run the DynamoDB integration tests. 15 | """ 16 | 17 | import logging 18 | 19 | from acktest.bootstrapping import Resources, BootstrapFailureException 20 | 21 | from e2e import bootstrap_directory 22 | from e2e.bootstrap_resources import BootstrapResources 23 | 24 | def service_bootstrap() -> Resources: 25 | logging.getLogger().setLevel(logging.INFO) 26 | 27 | resources = BootstrapResources() 28 | 29 | try: 30 | resources.bootstrap() 31 | except BootstrapFailureException as ex: 32 | exit(254) 33 | 34 | return resources 35 | 36 | if __name__ == "__main__": 37 | config = service_bootstrap() 38 | # Write config to current directory by default 39 | config.serialize(bootstrap_directory) 40 | -------------------------------------------------------------------------------- /config/rbac/cluster-role-controller.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: ack-dynamodb-controller 6 | rules: 7 | - apiGroups: 8 | - "" 9 | resources: 10 | - configmaps 11 | - secrets 12 | verbs: 13 | - get 14 | - list 15 | - patch 16 | - watch 17 | - apiGroups: 18 | - "" 19 | resources: 20 | - namespaces 21 | verbs: 22 | - get 23 | - list 24 | - watch 25 | - apiGroups: 26 | - dynamodb.services.k8s.aws 27 | resources: 28 | - backups 29 | - globaltables 30 | - tables 31 | verbs: 32 | - create 33 | - delete 34 | - get 35 | - list 36 | - patch 37 | - update 38 | - watch 39 | - apiGroups: 40 | - dynamodb.services.k8s.aws 41 | resources: 42 | - backups/status 43 | - globaltables/status 44 | - tables/status 45 | verbs: 46 | - get 47 | - patch 48 | - update 49 | - apiGroups: 50 | - kms.services.k8s.aws 51 | resources: 52 | - keys 53 | - keys/status 54 | verbs: 55 | - get 56 | - list 57 | - apiGroups: 58 | - services.k8s.aws 59 | resources: 60 | - fieldexports 61 | - iamroleselectors 62 | verbs: 63 | - create 64 | - delete 65 | - get 66 | - list 67 | - patch 68 | - update 69 | - watch 70 | - apiGroups: 71 | - services.k8s.aws 72 | resources: 73 | - fieldexports/status 74 | - iamroleselectors/status 75 | verbs: 76 | - get 77 | - patch 78 | - update 79 | -------------------------------------------------------------------------------- /test/e2e/bootstrap_resources.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | # not use this file except in compliance with the License. A copy of the 5 | # License is located at 6 | # 7 | # http://aws.amazon.com/apache2.0/ 8 | # 9 | # or in the "license" file accompanying this file. This file is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | # express or implied. See the License for the specific language governing 12 | # permissions and limitations under the License. 13 | 14 | """Declares the structure of the bootstrapped resources and provides a loader 15 | for them. 16 | """ 17 | 18 | from dataclasses import dataclass 19 | 20 | from acktest.bootstrapping import Resources 21 | from acktest.bootstrapping.sqs import Queue 22 | from acktest.bootstrapping.route53 import HealthCheck 23 | 24 | from e2e import bootstrap_directory 25 | 26 | @dataclass 27 | class BootstrapResources(Resources): 28 | pass 29 | 30 | _bootstrap_resources = None 31 | 32 | def get_bootstrap_resources(bootstrap_file_name: str = "bootstrap.pkl") -> BootstrapResources: 33 | global _bootstrap_resources 34 | if _bootstrap_resources is None: 35 | _bootstrap_resources = BootstrapResources.deserialize(bootstrap_directory, bootstrap_file_name=bootstrap_file_name) 36 | return _bootstrap_resources 37 | -------------------------------------------------------------------------------- /test/e2e/resources/table_multiple_gsi_and_replicas.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: dynamodb.services.k8s.aws/v1alpha1 2 | kind: Table 3 | metadata: 4 | name: $TABLE_NAME 5 | spec: 6 | tableName: $TABLE_NAME 7 | tableClass: STANDARD 8 | attributeDefinitions: 9 | - attributeName: PK 10 | attributeType: S 11 | - attributeName: SK 12 | attributeType: S 13 | - attributeName: GSI1PK 14 | attributeType: S 15 | - attributeName: GSI1SK 16 | attributeType: S 17 | keySchema: 18 | - attributeName: PK 19 | keyType: HASH 20 | - attributeName: SK 21 | keyType: RANGE 22 | billingMode: PAY_PER_REQUEST 23 | streamSpecification: 24 | streamEnabled: true 25 | streamViewType: "NEW_AND_OLD_IMAGES" 26 | tableReplicas: 27 | - regionName: $REPLICA_REGION_1 28 | globalSecondaryIndexes: 29 | - indexName: GSI1 30 | - regionName: $REPLICA_REGION_2 31 | globalSecondaryIndexes: 32 | - indexName: GSI1 33 | globalSecondaryIndexes: 34 | - indexName: GSI1 35 | keySchema: 36 | - attributeName: GSI1PK 37 | keyType: HASH 38 | - attributeName: GSI1SK 39 | keyType: RANGE 40 | projection: 41 | projectionType: ALL 42 | - indexName: GSI2 43 | keySchema: 44 | - attributeName: GSI1PK 45 | keyType: HASH 46 | - attributeName: GSI1SK 47 | keyType: RANGE 48 | projection: 49 | projectionType: KEYS_ONLY -------------------------------------------------------------------------------- /helm/templates/caches-role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: {{ include "ack-dynamodb-controller.app.fullname" . }}-namespaces-cache 5 | labels: 6 | app.kubernetes.io/name: {{ include "ack-dynamodb-controller.app.name" . }} 7 | app.kubernetes.io/instance: {{ .Release.Name }} 8 | app.kubernetes.io/managed-by: Helm 9 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 10 | k8s-app: {{ include "ack-dynamodb-controller.app.name" . }} 11 | helm.sh/chart: {{ include "ack-dynamodb-controller.chart.name-version" . }} 12 | rules: 13 | - apiGroups: 14 | - "" 15 | resources: 16 | - namespaces 17 | verbs: 18 | - get 19 | - list 20 | - watch 21 | --- 22 | apiVersion: rbac.authorization.k8s.io/v1 23 | kind: Role 24 | metadata: 25 | name: {{ include "ack-dynamodb-controller.app.fullname" . }}-configmaps-cache 26 | namespace: {{ .Release.Namespace }} 27 | labels: 28 | app.kubernetes.io/name: {{ include "ack-dynamodb-controller.app.name" . }} 29 | app.kubernetes.io/instance: {{ .Release.Name }} 30 | app.kubernetes.io/managed-by: Helm 31 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 32 | k8s-app: {{ include "ack-dynamodb-controller.app.name" . }} 33 | helm.sh/chart: {{ include "ack-dynamodb-controller.chart.name-version" . }} 34 | rules: 35 | - apiGroups: 36 | - "" 37 | resources: 38 | - configmaps 39 | verbs: 40 | - get 41 | - list 42 | - watch -------------------------------------------------------------------------------- /test/e2e/resources/table_multiple_gsi_and_replicas_provisioned.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: dynamodb.services.k8s.aws/v1alpha1 2 | kind: Table 3 | metadata: 4 | name: $TABLE_NAME 5 | spec: 6 | tableName: $TABLE_NAME 7 | tableClass: STANDARD 8 | attributeDefinitions: 9 | - attributeName: PK 10 | attributeType: S 11 | - attributeName: SK 12 | attributeType: S 13 | - attributeName: GSI1PK 14 | attributeType: S 15 | - attributeName: GSI1SK 16 | attributeType: S 17 | keySchema: 18 | - attributeName: PK 19 | keyType: HASH 20 | - attributeName: SK 21 | keyType: RANGE 22 | billingMode: PROVISIONED 23 | provisionedThroughput: 24 | readCapacityUnits: 2 25 | writeCapacityUnits: 2 26 | streamSpecification: 27 | streamEnabled: true 28 | streamViewType: "NEW_AND_OLD_IMAGES" 29 | globalSecondaryIndexes: 30 | - indexName: GSI1 31 | provisionedThroughput: 32 | readCapacityUnits: 2 33 | writeCapacityUnits: 2 34 | keySchema: 35 | - attributeName: GSI1PK 36 | keyType: HASH 37 | - attributeName: GSI1SK 38 | keyType: RANGE 39 | projection: 40 | projectionType: ALL 41 | - indexName: GSI2 42 | provisionedThroughput: 43 | readCapacityUnits: 2 44 | writeCapacityUnits: 2 45 | keySchema: 46 | - attributeName: GSI1PK 47 | keyType: HASH 48 | - attributeName: GSI1SK 49 | keyType: RANGE 50 | projection: 51 | projectionType: KEYS_ONLY -------------------------------------------------------------------------------- /olm/olmconfig.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | annotations: 3 | capabilityLevel: Basic Install 4 | shortDescription: AWS DynamoDB controller is a service controller for managing DynamoDB resources 5 | in Kubernetes 6 | displayName: AWS Controllers for Kubernetes - Amazon DynamoDB 7 | description: |- 8 | Manage Amazon DynamoDB resources in AWS from within your Kubernetes cluster. 9 | 10 | 11 | **About Amazon DynamoDB** 12 | 13 | 14 | Amazon DynamoDB is a fully managed NoSQL database service that provides fast and predictable performance with seamless scalability. DynamoDB lets you offload the administrative burdens of operating and scaling a distributed database so that you don''t have to worry about hardware provisioning, setup and configuration, replication, software patching, or cluster scaling. DynamoDB also offers encryption at rest, which eliminates the operational burden and complexity involved in protecting sensitive data. For more information, see [DynamoDB Encryption at Rest](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/EncryptionAtRest.html). 15 | 16 | 17 | **About the AWS Controllers for Kubernetes** 18 | 19 | 20 | This controller is a component of the [AWS Controller for Kubernetes](https://github.com/aws/aws-controllers-k8s) 21 | project. 22 | 23 | 24 | **Pre-Installation Steps** 25 | 26 | 27 | Please follow the following link: [Red Hat OpenShift](https://aws-controllers-k8s.github.io/community/docs/user-docs/openshift/) 28 | samples: 29 | - kind: Backup 30 | spec: '{}' 31 | - kind: GlobalTable 32 | spec: '{}' 33 | - kind: Table 34 | spec: '{}' 35 | maintainers: 36 | - name: "dynamodb maintainer team" 37 | email: "ack-maintainers@amazon.com" 38 | links: 39 | - name: Amazon DynamoDB Developer Resources 40 | url: https://aws.amazon.com/dynamodb/resources/ 41 | -------------------------------------------------------------------------------- /helm/templates/cluster-role-controller.yaml: -------------------------------------------------------------------------------- 1 | {{ $labels := .Values.role.labels }} 2 | {{ $appVersion := .Chart.AppVersion | quote }} 3 | {{ $rbacRules := include "ack-dynamodb-controller.rbac-rules" . }} 4 | {{ $fullname := include "ack-dynamodb-controller.app.fullname" . }} 5 | {{ $chartVersion := include "ack-dynamodb-controller.chart.name-version" . }} 6 | {{ if eq .Values.installScope "cluster" }} 7 | apiVersion: rbac.authorization.k8s.io/v1 8 | kind: ClusterRole 9 | metadata: 10 | name: {{ include "ack-dynamodb-controller.app.fullname" . }} 11 | labels: 12 | app.kubernetes.io/name: {{ include "ack-dynamodb-controller.app.name" . }} 13 | app.kubernetes.io/instance: {{ .Release.Name }} 14 | app.kubernetes.io/managed-by: Helm 15 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 16 | k8s-app: {{ include "ack-dynamodb-controller.app.name" . }} 17 | helm.sh/chart: {{ include "ack-dynamodb-controller.chart.name-version" . }} 18 | {{- range $key, $value := $labels }} 19 | {{ $key }}: {{ $value | quote }} 20 | {{- end }} 21 | {{$rbacRules }} 22 | {{ else if eq .Values.installScope "namespace" }} 23 | {{ $wn := include "ack-dynamodb-controller.watch-namespace" . }} 24 | {{ $namespaces := split "," $wn }} 25 | {{ range $namespaces }} 26 | --- 27 | apiVersion: rbac.authorization.k8s.io/v1 28 | kind: Role 29 | metadata: 30 | name: {{ $fullname }}-{{ . }} 31 | namespace: {{ . }} 32 | labels: 33 | app.kubernetes.io/name: {{ $fullname }} 34 | app.kubernetes.io/instance: {{ $.Release.Name }} 35 | app.kubernetes.io/managed-by: Helm 36 | app.kubernetes.io/version: {{ $appVersion }} 37 | k8s-app: {{ $fullname }} 38 | helm.sh/chart: {{ $chartVersion }} 39 | {{- range $key, $value := $labels }} 40 | {{ $key }}: {{ $value | quote }} 41 | {{- end }} 42 | {{ $rbacRules }} 43 | {{ end }} 44 | {{ end }} -------------------------------------------------------------------------------- /helm/templates/caches-role-binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: {{ include "ack-dynamodb-controller.app.fullname" . }}-namespaces-cache 5 | labels: 6 | app.kubernetes.io/name: {{ include "ack-dynamodb-controller.app.name" . }} 7 | app.kubernetes.io/instance: {{ .Release.Name }} 8 | app.kubernetes.io/managed-by: Helm 9 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 10 | k8s-app: {{ include "ack-dynamodb-controller.app.name" . }} 11 | helm.sh/chart: {{ include "ack-dynamodb-controller.chart.name-version" . }} 12 | roleRef: 13 | kind: ClusterRole 14 | apiGroup: rbac.authorization.k8s.io 15 | name: {{ include "ack-dynamodb-controller.app.fullname" . }}-namespaces-cache 16 | subjects: 17 | - kind: ServiceAccount 18 | name: {{ include "ack-dynamodb-controller.service-account.name" . }} 19 | namespace: {{ .Release.Namespace }} 20 | --- 21 | apiVersion: rbac.authorization.k8s.io/v1 22 | kind: RoleBinding 23 | metadata: 24 | name: {{ include "ack-dynamodb-controller.app.fullname" . }}-configmaps-cache 25 | namespace: {{ .Release.Namespace }} 26 | labels: 27 | app.kubernetes.io/name: {{ include "ack-dynamodb-controller.app.name" . }} 28 | app.kubernetes.io/instance: {{ .Release.Name }} 29 | app.kubernetes.io/managed-by: Helm 30 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 31 | k8s-app: {{ include "ack-dynamodb-controller.app.name" . }} 32 | helm.sh/chart: {{ include "ack-dynamodb-controller.chart.name-version" . }} 33 | roleRef: 34 | kind: Role 35 | apiGroup: rbac.authorization.k8s.io 36 | name: {{ include "ack-dynamodb-controller.app.fullname" . }}-configmaps-cache 37 | subjects: 38 | - kind: ServiceAccount 39 | name: {{ include "ack-dynamodb-controller.service-account.name" . }} 40 | namespace: {{ .Release.Namespace }} 41 | -------------------------------------------------------------------------------- /test/e2e/conftest.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | # not use this file except in compliance with the License. A copy of the 5 | # License is located at 6 | # 7 | # http://aws.amazon.com/apache2.0/ 8 | # 9 | # or in the "license" file accompanying this file. This file is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | # express or implied. See the License for the specific language governing 12 | # permissions and limitations under the License. 13 | 14 | import os 15 | import boto3 16 | import pytest 17 | 18 | from acktest import k8s 19 | from acktest.aws.identity import get_region 20 | 21 | 22 | def pytest_addoption(parser): 23 | parser.addoption("--runslow", action="store_true", default=False, help="run slow tests") 24 | 25 | 26 | def pytest_configure(config): 27 | config.addinivalue_line( 28 | "markers", "canary: mark test to also run in canary tests" 29 | ) 30 | config.addinivalue_line( 31 | "markers", "service(arg): mark test associated with a given service" 32 | ) 33 | config.addinivalue_line( 34 | "markers", "slow: mark test as slow to run" 35 | ) 36 | 37 | def pytest_collection_modifyitems(config, items): 38 | if config.getoption("--runslow"): 39 | return 40 | skip_slow = pytest.mark.skip(reason="need --runslow option to run") 41 | for item in items: 42 | if "slow" in item.keywords: 43 | item.add_marker(skip_slow) 44 | 45 | # Provide a k8s client to interact with the integration test cluster 46 | @pytest.fixture(scope='class') 47 | def k8s_client(): 48 | return k8s._get_k8s_api_client() 49 | 50 | @pytest.fixture(scope="class") 51 | def dynamodb_client(): 52 | return boto3.client("dynamodb", region_name=get_region()) -------------------------------------------------------------------------------- /pkg/resource/backup/hooks.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package backup 15 | 16 | import ( 17 | "errors" 18 | 19 | ackrequeue "github.com/aws-controllers-k8s/runtime/pkg/requeue" 20 | 21 | "github.com/aws-controllers-k8s/dynamodb-controller/apis/v1alpha1" 22 | ) 23 | 24 | var ( 25 | // TerminalStatuses are the status strings that are terminal states for a 26 | // backup. 27 | TerminalStatuses = []v1alpha1.BackupStatus_SDK{} 28 | ) 29 | 30 | var ( 31 | requeueWaitWhileCreating = ackrequeue.NeededAfter( 32 | errors.New("Backup in 'CREATING' state, cannot be modified or deleted."), 33 | ackrequeue.DefaultRequeueAfterDuration, 34 | ) 35 | ) 36 | 37 | // backupHasTerminalStatus returns whether the supplied backup is in a 38 | // terminal state 39 | func backupHasTerminalStatus(r *resource) bool { 40 | if r.ko.Status.BackupStatus == nil { 41 | return false 42 | } 43 | ts := *r.ko.Status.BackupStatus 44 | for _, s := range TerminalStatuses { 45 | if ts == string(s) { 46 | return true 47 | } 48 | } 49 | return false 50 | } 51 | 52 | // isBackupCreating returns true if the supplied Dynamodb backup is in the process 53 | // of being created 54 | func isBackupCreating(r *resource) bool { 55 | if r.ko.Status.BackupStatus == nil { 56 | return false 57 | } 58 | dbis := *r.ko.Status.BackupStatus 59 | return dbis == string(v1alpha1.BackupStatus_SDK_CREATING) 60 | } 61 | -------------------------------------------------------------------------------- /pkg/resource/backup/identifiers.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by ack-generate. DO NOT EDIT. 15 | 16 | package backup 17 | 18 | import ( 19 | ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" 20 | ) 21 | 22 | // resourceIdentifiers implements the 23 | // `aws-service-operator-k8s/pkg/types.AWSResourceIdentifiers` interface 24 | type resourceIdentifiers struct { 25 | meta *ackv1alpha1.ResourceMetadata 26 | } 27 | 28 | // ARN returns the AWS Resource Name for the backend AWS resource. If nil, 29 | // this means the resource has not yet been created in the backend AWS 30 | // service. 31 | func (ri *resourceIdentifiers) ARN() *ackv1alpha1.AWSResourceName { 32 | if ri.meta != nil { 33 | return ri.meta.ARN 34 | } 35 | return nil 36 | } 37 | 38 | // OwnerAccountID returns the AWS account identifier in which the 39 | // backend AWS resource resides, or nil if this information is not known 40 | // for the resource 41 | func (ri *resourceIdentifiers) OwnerAccountID() *ackv1alpha1.AWSAccountID { 42 | if ri.meta != nil { 43 | return ri.meta.OwnerAccountID 44 | } 45 | return nil 46 | } 47 | 48 | // Region returns the AWS region in which the resource exists, or 49 | // nil if this information is not known. 50 | func (ri *resourceIdentifiers) Region() *ackv1alpha1.AWSRegion { 51 | if ri.meta != nil { 52 | return ri.meta.Region 53 | } 54 | return nil 55 | } 56 | -------------------------------------------------------------------------------- /pkg/resource/table/identifiers.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by ack-generate. DO NOT EDIT. 15 | 16 | package table 17 | 18 | import ( 19 | ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" 20 | ) 21 | 22 | // resourceIdentifiers implements the 23 | // `aws-service-operator-k8s/pkg/types.AWSResourceIdentifiers` interface 24 | type resourceIdentifiers struct { 25 | meta *ackv1alpha1.ResourceMetadata 26 | } 27 | 28 | // ARN returns the AWS Resource Name for the backend AWS resource. If nil, 29 | // this means the resource has not yet been created in the backend AWS 30 | // service. 31 | func (ri *resourceIdentifiers) ARN() *ackv1alpha1.AWSResourceName { 32 | if ri.meta != nil { 33 | return ri.meta.ARN 34 | } 35 | return nil 36 | } 37 | 38 | // OwnerAccountID returns the AWS account identifier in which the 39 | // backend AWS resource resides, or nil if this information is not known 40 | // for the resource 41 | func (ri *resourceIdentifiers) OwnerAccountID() *ackv1alpha1.AWSAccountID { 42 | if ri.meta != nil { 43 | return ri.meta.OwnerAccountID 44 | } 45 | return nil 46 | } 47 | 48 | // Region returns the AWS region in which the resource exists, or 49 | // nil if this information is not known. 50 | func (ri *resourceIdentifiers) Region() *ackv1alpha1.AWSRegion { 51 | if ri.meta != nil { 52 | return ri.meta.Region 53 | } 54 | return nil 55 | } 56 | -------------------------------------------------------------------------------- /pkg/resource/global_table/identifiers.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by ack-generate. DO NOT EDIT. 15 | 16 | package global_table 17 | 18 | import ( 19 | ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" 20 | ) 21 | 22 | // resourceIdentifiers implements the 23 | // `aws-service-operator-k8s/pkg/types.AWSResourceIdentifiers` interface 24 | type resourceIdentifiers struct { 25 | meta *ackv1alpha1.ResourceMetadata 26 | } 27 | 28 | // ARN returns the AWS Resource Name for the backend AWS resource. If nil, 29 | // this means the resource has not yet been created in the backend AWS 30 | // service. 31 | func (ri *resourceIdentifiers) ARN() *ackv1alpha1.AWSResourceName { 32 | if ri.meta != nil { 33 | return ri.meta.ARN 34 | } 35 | return nil 36 | } 37 | 38 | // OwnerAccountID returns the AWS account identifier in which the 39 | // backend AWS resource resides, or nil if this information is not known 40 | // for the resource 41 | func (ri *resourceIdentifiers) OwnerAccountID() *ackv1alpha1.AWSAccountID { 42 | if ri.meta != nil { 43 | return ri.meta.OwnerAccountID 44 | } 45 | return nil 46 | } 47 | 48 | // Region returns the AWS region in which the resource exists, or 49 | // nil if this information is not known. 50 | func (ri *resourceIdentifiers) Region() *ackv1alpha1.AWSRegion { 51 | if ri.meta != nil { 52 | return ri.meta.Region 53 | } 54 | return nil 55 | } 56 | -------------------------------------------------------------------------------- /GOVERNANCE.md: -------------------------------------------------------------------------------- 1 | # Project governance 2 | 3 | This document lays out the guidelines under which the AWS Controllers for Kubernetes (ACK) project will be governed. 4 | The goal is to make sure that the roles and responsibilities are well defined and clarify on how decisions are made. 5 | 6 | ## Roles 7 | 8 | In the context of ACK, we consider the following roles: 9 | 10 | * __Users__ ... everyone using ACK, typically willing to provide feedback on ACK by proposing features and/or filing issues. 11 | * __Contributors__ ... everyone contributing code, documentation, examples, testing infra, and participating in feature proposals as well as design discussions. Code contributions will require a Developer Certificate of Origin (DCO). 12 | * __Maintainers__ ... are responsible for engaging with and assisting contributors to iterate on the contributions until it reaches acceptable quality. Maintainers can decide whether the contributions can be accepted into the project or rejected. Any active contributor meeting the project quality can be made a Maintainer by the Advisory Board. 13 | * __Advisory Board__ ... is responsible for defining the guidelines and processes that the project operates under. 14 | 15 | The initial members of the Advisory Board are `@jaypipes` and `@mhausenblas`. 16 | 17 | 18 | ## Communication 19 | 20 | The primary mechanism for communication will be via the `#provider-aws` channel on the Kubernetes Slack community. 21 | All features and bug fixes will be tracked as issues in GitHub. All decisions will be documented in GitHub issues. 22 | 23 | In the future, we may consider using a public mailing list, which can be better archived. 24 | 25 | ## Roadmap Planning 26 | 27 | Maintainers will share roadmap and release versions as milestones in GitHub. 28 | 29 | ## Release Management 30 | 31 | The Advisory Board will propose a release management proposal via a GitHub issue and resolve it there. 32 | 33 | ## Other relevant governance resources 34 | 35 | * The ACK [Contributing Guidelines](CONTRIBUTING.md) 36 | * Our [Code of Conduct](CODE_OF_CONDUCT.md) 37 | -------------------------------------------------------------------------------- /pkg/resource/registry.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by ack-generate. DO NOT EDIT. 15 | 16 | package resource 17 | 18 | import ( 19 | ackrt "github.com/aws-controllers-k8s/runtime/pkg/runtime" 20 | acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" 21 | ) 22 | 23 | // +kubebuilder:rbac:groups=services.k8s.aws,resources=iamroleselectors,verbs=get;list;watch;create;update;patch;delete 24 | // +kubebuilder:rbac:groups=services.k8s.aws,resources=iamroleselectors/status,verbs=get;update;patch 25 | // +kubebuilder:rbac:groups=services.k8s.aws,resources=fieldexports,verbs=get;list;watch;create;update;patch;delete 26 | // +kubebuilder:rbac:groups=services.k8s.aws,resources=fieldexports/status,verbs=get;update;patch 27 | // +kubebuilder:rbac:groups="",resources=namespaces,verbs=get;list;watch 28 | // +kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;patch 29 | // +kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;patch 30 | 31 | var ( 32 | reg = ackrt.NewRegistry() 33 | ) 34 | 35 | // GetManagerFactories returns a slice of resource manager factories that are 36 | // registered with this package 37 | func GetManagerFactories() []acktypes.AWSResourceManagerFactory { 38 | return reg.GetResourceManagerFactories() 39 | } 40 | 41 | // RegisterManagerFactory registers a resource manager factory with the 42 | // package's registry 43 | func RegisterManagerFactory(f acktypes.AWSResourceManagerFactory) { 44 | reg.RegisterResourceManagerFactory(f) 45 | } 46 | -------------------------------------------------------------------------------- /helm/templates/cluster-role-binding.yaml: -------------------------------------------------------------------------------- 1 | {{ if eq .Values.installScope "cluster" }} 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRoleBinding 4 | metadata: 5 | name: {{ include "ack-dynamodb-controller.app.fullname" . }}-rolebinding 6 | labels: 7 | app.kubernetes.io/name: {{ include "ack-dynamodb-controller.app.name" . }} 8 | app.kubernetes.io/instance: {{ .Release.Name }} 9 | app.kubernetes.io/managed-by: Helm 10 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 11 | k8s-app: {{ include "ack-dynamodb-controller.app.name" . }} 12 | helm.sh/chart: {{ include "ack-dynamodb-controller.chart.name-version" . }} 13 | roleRef: 14 | kind: ClusterRole 15 | apiGroup: rbac.authorization.k8s.io 16 | name: {{ include "ack-dynamodb-controller.app.fullname" . }} 17 | subjects: 18 | - kind: ServiceAccount 19 | name: {{ include "ack-dynamodb-controller.service-account.name" . }} 20 | namespace: {{ .Release.Namespace }} 21 | {{ else if eq .Values.installScope "namespace" }} 22 | {{ $wn := include "ack-dynamodb-controller.watch-namespace" . }} 23 | {{ $namespaces := split "," $wn }} 24 | {{ $fullname := include "ack-dynamodb-controller.app.fullname" . }} 25 | {{ $releaseNamespace := .Release.Namespace }} 26 | {{ $serviceAccountName := include "ack-dynamodb-controller.service-account.name" . }} 27 | {{ $chartVersion := include "ack-dynamodb-controller.chart.name-version" . }} 28 | {{ $appVersion := .Chart.AppVersion | quote }} 29 | {{ range $namespaces }} 30 | --- 31 | apiVersion: rbac.authorization.k8s.io/v1 32 | kind: RoleBinding 33 | metadata: 34 | name: {{ $fullname }}-{{ . }} 35 | namespace: {{ . }} 36 | labels: 37 | app.kubernetes.io/name: {{ $fullname }} 38 | app.kubernetes.io/instance: {{ $.Release.Name }} 39 | app.kubernetes.io/managed-by: Helm 40 | app.kubernetes.io/version: {{ $appVersion }} 41 | k8s-app: {{ $fullname }} 42 | helm.sh/chart: {{ $chartVersion }} 43 | roleRef: 44 | kind: Role 45 | apiGroup: rbac.authorization.k8s.io 46 | name: {{ $fullname }}-{{ . }} 47 | subjects: 48 | - kind: ServiceAccount 49 | name: {{ $serviceAccountName }} 50 | namespace: {{ $releaseNamespace }} 51 | {{ end }} 52 | {{ end }} -------------------------------------------------------------------------------- /pkg/resource/backup/delta.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by ack-generate. DO NOT EDIT. 15 | 16 | package backup 17 | 18 | import ( 19 | "bytes" 20 | "reflect" 21 | 22 | ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare" 23 | acktags "github.com/aws-controllers-k8s/runtime/pkg/tags" 24 | ) 25 | 26 | // Hack to avoid import errors during build... 27 | var ( 28 | _ = &bytes.Buffer{} 29 | _ = &reflect.Method{} 30 | _ = &acktags.Tags{} 31 | ) 32 | 33 | // newResourceDelta returns a new `ackcompare.Delta` used to compare two 34 | // resources 35 | func newResourceDelta( 36 | a *resource, 37 | b *resource, 38 | ) *ackcompare.Delta { 39 | delta := ackcompare.NewDelta() 40 | if (a == nil && b != nil) || 41 | (a != nil && b == nil) { 42 | delta.Add("", a, b) 43 | return delta 44 | } 45 | 46 | if ackcompare.HasNilDifference(a.ko.Spec.BackupName, b.ko.Spec.BackupName) { 47 | delta.Add("Spec.BackupName", a.ko.Spec.BackupName, b.ko.Spec.BackupName) 48 | } else if a.ko.Spec.BackupName != nil && b.ko.Spec.BackupName != nil { 49 | if *a.ko.Spec.BackupName != *b.ko.Spec.BackupName { 50 | delta.Add("Spec.BackupName", a.ko.Spec.BackupName, b.ko.Spec.BackupName) 51 | } 52 | } 53 | if ackcompare.HasNilDifference(a.ko.Spec.TableName, b.ko.Spec.TableName) { 54 | delta.Add("Spec.TableName", a.ko.Spec.TableName, b.ko.Spec.TableName) 55 | } else if a.ko.Spec.TableName != nil && b.ko.Spec.TableName != nil { 56 | if *a.ko.Spec.TableName != *b.ko.Spec.TableName { 57 | delta.Add("Spec.TableName", a.ko.Spec.TableName, b.ko.Spec.TableName) 58 | } 59 | } 60 | 61 | return delta 62 | } 63 | -------------------------------------------------------------------------------- /pkg/resource/table/hooks_global_secondary_indexes_test.go: -------------------------------------------------------------------------------- 1 | package table 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | svcsdktypes "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" 8 | "github.com/aws/aws-sdk-go/aws" 9 | 10 | "github.com/aws-controllers-k8s/dynamodb-controller/apis/v1alpha1" 11 | ) 12 | 13 | func Test_newSDKProvisionedThroughput(t *testing.T) { 14 | type args struct { 15 | pt *v1alpha1.ProvisionedThroughput 16 | } 17 | tests := []struct { 18 | name string 19 | args args 20 | want *svcsdktypes.ProvisionedThroughput 21 | }{ 22 | { 23 | name: "provisioned throughput is nil", 24 | args: args{ 25 | pt: nil, 26 | }, 27 | want: nil, 28 | }, 29 | { 30 | name: "provisioned throughput is not nil, read capacity units is nil", 31 | args: args{ 32 | pt: &v1alpha1.ProvisionedThroughput{ 33 | ReadCapacityUnits: nil, 34 | WriteCapacityUnits: aws.Int64(10), 35 | }, 36 | }, 37 | want: &svcsdktypes.ProvisionedThroughput{ 38 | ReadCapacityUnits: aws.Int64(1), 39 | WriteCapacityUnits: aws.Int64(10), 40 | }, 41 | }, 42 | { 43 | name: "provisioned throughput is not nil, write capacity units is nil", 44 | args: args{ 45 | pt: &v1alpha1.ProvisionedThroughput{ 46 | ReadCapacityUnits: aws.Int64(10), 47 | WriteCapacityUnits: nil, 48 | }, 49 | }, 50 | want: &svcsdktypes.ProvisionedThroughput{ 51 | ReadCapacityUnits: aws.Int64(10), 52 | WriteCapacityUnits: aws.Int64(1), 53 | }, 54 | }, 55 | { 56 | name: "provisioned throughput is not nil, write and read capacity units are not nil", 57 | args: args{ 58 | pt: &v1alpha1.ProvisionedThroughput{ 59 | ReadCapacityUnits: aws.Int64(5), 60 | WriteCapacityUnits: aws.Int64(5), 61 | }, 62 | }, 63 | want: &svcsdktypes.ProvisionedThroughput{ 64 | ReadCapacityUnits: aws.Int64(5), 65 | WriteCapacityUnits: aws.Int64(5), 66 | }, 67 | }, 68 | } 69 | for _, tt := range tests { 70 | t.Run(tt.name, func(t *testing.T) { 71 | if got := newSDKProvisionedThroughput(tt.args.pt); !reflect.DeepEqual(got, tt.want) { 72 | t.Errorf("newSDKProvisionedThroughput() = %v, want %v", got, tt.want) 73 | } 74 | }) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /pkg/resource/global_table/delta.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by ack-generate. DO NOT EDIT. 15 | 16 | package global_table 17 | 18 | import ( 19 | "bytes" 20 | "reflect" 21 | 22 | ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare" 23 | acktags "github.com/aws-controllers-k8s/runtime/pkg/tags" 24 | ) 25 | 26 | // Hack to avoid import errors during build... 27 | var ( 28 | _ = &bytes.Buffer{} 29 | _ = &reflect.Method{} 30 | _ = &acktags.Tags{} 31 | ) 32 | 33 | // newResourceDelta returns a new `ackcompare.Delta` used to compare two 34 | // resources 35 | func newResourceDelta( 36 | a *resource, 37 | b *resource, 38 | ) *ackcompare.Delta { 39 | delta := ackcompare.NewDelta() 40 | if (a == nil && b != nil) || 41 | (a != nil && b == nil) { 42 | delta.Add("", a, b) 43 | return delta 44 | } 45 | 46 | if ackcompare.HasNilDifference(a.ko.Spec.GlobalTableName, b.ko.Spec.GlobalTableName) { 47 | delta.Add("Spec.GlobalTableName", a.ko.Spec.GlobalTableName, b.ko.Spec.GlobalTableName) 48 | } else if a.ko.Spec.GlobalTableName != nil && b.ko.Spec.GlobalTableName != nil { 49 | if *a.ko.Spec.GlobalTableName != *b.ko.Spec.GlobalTableName { 50 | delta.Add("Spec.GlobalTableName", a.ko.Spec.GlobalTableName, b.ko.Spec.GlobalTableName) 51 | } 52 | } 53 | if len(a.ko.Spec.ReplicationGroup) != len(b.ko.Spec.ReplicationGroup) { 54 | delta.Add("Spec.ReplicationGroup", a.ko.Spec.ReplicationGroup, b.ko.Spec.ReplicationGroup) 55 | } else if len(a.ko.Spec.ReplicationGroup) > 0 { 56 | if !reflect.DeepEqual(a.ko.Spec.ReplicationGroup, b.ko.Spec.ReplicationGroup) { 57 | delta.Add("Spec.ReplicationGroup", a.ko.Spec.ReplicationGroup, b.ko.Spec.ReplicationGroup) 58 | } 59 | } 60 | 61 | return delta 62 | } 63 | -------------------------------------------------------------------------------- /pkg/resource/backup/references.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by ack-generate. DO NOT EDIT. 15 | 16 | package backup 17 | 18 | import ( 19 | "context" 20 | 21 | "sigs.k8s.io/controller-runtime/pkg/client" 22 | 23 | acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" 24 | 25 | svcapitypes "github.com/aws-controllers-k8s/dynamodb-controller/apis/v1alpha1" 26 | ) 27 | 28 | // ClearResolvedReferences removes any reference values that were made 29 | // concrete in the spec. It returns a copy of the input AWSResource which 30 | // contains the original *Ref values, but none of their respective concrete 31 | // values. 32 | func (rm *resourceManager) ClearResolvedReferences(res acktypes.AWSResource) acktypes.AWSResource { 33 | ko := rm.concreteResource(res).ko.DeepCopy() 34 | 35 | return &resource{ko} 36 | } 37 | 38 | // ResolveReferences finds if there are any Reference field(s) present 39 | // inside AWSResource passed in the parameter and attempts to resolve those 40 | // reference field(s) into their respective target field(s). It returns a 41 | // copy of the input AWSResource with resolved reference(s), a boolean which 42 | // is set to true if the resource contains any references (regardless of if 43 | // they are resolved successfully) and an error if the passed AWSResource's 44 | // reference field(s) could not be resolved. 45 | func (rm *resourceManager) ResolveReferences( 46 | ctx context.Context, 47 | apiReader client.Reader, 48 | res acktypes.AWSResource, 49 | ) (acktypes.AWSResource, bool, error) { 50 | return res, false, nil 51 | } 52 | 53 | // validateReferenceFields validates the reference field and corresponding 54 | // identifier field. 55 | func validateReferenceFields(ko *svcapitypes.Backup) error { 56 | return nil 57 | } 58 | -------------------------------------------------------------------------------- /pkg/resource/global_table/references.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by ack-generate. DO NOT EDIT. 15 | 16 | package global_table 17 | 18 | import ( 19 | "context" 20 | 21 | "sigs.k8s.io/controller-runtime/pkg/client" 22 | 23 | acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" 24 | 25 | svcapitypes "github.com/aws-controllers-k8s/dynamodb-controller/apis/v1alpha1" 26 | ) 27 | 28 | // ClearResolvedReferences removes any reference values that were made 29 | // concrete in the spec. It returns a copy of the input AWSResource which 30 | // contains the original *Ref values, but none of their respective concrete 31 | // values. 32 | func (rm *resourceManager) ClearResolvedReferences(res acktypes.AWSResource) acktypes.AWSResource { 33 | ko := rm.concreteResource(res).ko.DeepCopy() 34 | 35 | return &resource{ko} 36 | } 37 | 38 | // ResolveReferences finds if there are any Reference field(s) present 39 | // inside AWSResource passed in the parameter and attempts to resolve those 40 | // reference field(s) into their respective target field(s). It returns a 41 | // copy of the input AWSResource with resolved reference(s), a boolean which 42 | // is set to true if the resource contains any references (regardless of if 43 | // they are resolved successfully) and an error if the passed AWSResource's 44 | // reference field(s) could not be resolved. 45 | func (rm *resourceManager) ResolveReferences( 46 | ctx context.Context, 47 | apiReader client.Reader, 48 | res acktypes.AWSResource, 49 | ) (acktypes.AWSResource, bool, error) { 50 | return res, false, nil 51 | } 52 | 53 | // validateReferenceFields validates the reference field and corresponding 54 | // identifier field. 55 | func validateReferenceFields(ko *svcapitypes.GlobalTable) error { 56 | return nil 57 | } 58 | -------------------------------------------------------------------------------- /templates/hooks/table/sdk_read_one_post_set_output.go.tpl: -------------------------------------------------------------------------------- 1 | if resp.Table.GlobalSecondaryIndexes != nil { 2 | f := []*svcapitypes.GlobalSecondaryIndexDescription{} 3 | for _, fIter := range resp.Table.GlobalSecondaryIndexes { 4 | fElem := &svcapitypes.GlobalSecondaryIndexDescription{} 5 | if fIter.IndexName != nil { 6 | fElem.IndexName = fIter.IndexName 7 | } 8 | if fIter.IndexArn != nil { 9 | fElem.IndexARN = fIter.IndexArn 10 | } 11 | if fIter.ItemCount != nil { 12 | fElem.ItemCount = fIter.ItemCount 13 | } 14 | if fIter.IndexStatus != "" { 15 | fElem.IndexStatus = aws.String(string(fIter.IndexStatus)) 16 | } 17 | if fIter.IndexSizeBytes != nil { 18 | fElem.IndexSizeBytes = fIter.IndexSizeBytes 19 | } 20 | if fIter.Backfilling != nil { 21 | fElem.Backfilling = fIter.Backfilling 22 | } 23 | f = append(f, fElem) 24 | } 25 | ko.Status.GlobalSecondaryIndexesDescriptions = f 26 | } else { 27 | ko.Status.GlobalSecondaryIndexesDescriptions = nil 28 | } 29 | if resp.Table.SSEDescription != nil { 30 | f := &svcapitypes.SSESpecification{} 31 | if resp.Table.SSEDescription.Status != "" { 32 | f.Enabled = aws.Bool(resp.Table.SSEDescription.Status == svcsdktypes.SSEStatusEnabled) 33 | } else { 34 | f.Enabled = aws.Bool(false) 35 | } 36 | if resp.Table.SSEDescription.SSEType != "" { 37 | f.SSEType = aws.String(string(resp.Table.SSEDescription.SSEType)) 38 | } 39 | if resp.Table.SSEDescription.KMSMasterKeyArn != nil { 40 | f.KMSMasterKeyID = resp.Table.SSEDescription.KMSMasterKeyArn 41 | } 42 | ko.Spec.SSESpecification = f 43 | } else { 44 | ko.Spec.SSESpecification = nil 45 | } 46 | if resp.Table.TableClassSummary != nil { 47 | ko.Spec.TableClass = aws.String(string(resp.Table.TableClassSummary.TableClass)) 48 | } else { 49 | ko.Spec.TableClass = aws.String("STANDARD") 50 | } 51 | if resp.Table.BillingModeSummary != nil && resp.Table.BillingModeSummary.BillingMode != "" { 52 | ko.Spec.BillingMode = aws.String(string(resp.Table.BillingModeSummary.BillingMode)) 53 | } else { 54 | ko.Spec.BillingMode = aws.String("PROVISIONED") 55 | } 56 | setTableReplicas(ko, resp.Table.Replicas) 57 | if isTableCreating(&resource{ko}) { 58 | return &resource{ko}, requeueWaitWhileCreating 59 | } 60 | if !canUpdateTableGSIs(&resource{ko}) { 61 | return &resource{ko}, requeueWaitGSIReady 62 | } 63 | if err = rm.setContributorInsights(ctx, ko); err != nil { 64 | return &resource{ko}, err 65 | } 66 | if isTableUpdating(&resource{ko}) || isTableContributorInsightsUpdating(&resource{ko}) { 67 | return &resource{ko}, requeueWaitWhileUpdating 68 | } 69 | 70 | if err := rm.setResourceAdditionalFields(ctx, ko); err != nil { 71 | return nil, err 72 | } 73 | -------------------------------------------------------------------------------- /test/e2e/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | # not use this file except in compliance with the License. A copy of the 5 | # License is located at 6 | # 7 | # http://aws.amazon.com/apache2.0/ 8 | # 9 | # or in the "license" file accompanying this file. This file is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | # express or implied. See the License for the specific language governing 12 | # permissions and limitations under the License. 13 | 14 | import boto3 15 | import pytest 16 | import time 17 | import logging 18 | from typing import Dict, Any 19 | from pathlib import Path 20 | 21 | from acktest.k8s import resource as k8s 22 | from acktest.resources import load_resource_file 23 | from acktest.aws.identity import get_region 24 | 25 | SERVICE_NAME = "dynamodb" 26 | CRD_GROUP = "dynamodb.services.k8s.aws" 27 | CRD_VERSION = "v1alpha1" 28 | 29 | # PyTest marker for the current service 30 | service_marker = pytest.mark.service(arg=SERVICE_NAME) 31 | 32 | bootstrap_directory = Path(__file__).parent 33 | resource_directory = Path(__file__).parent / "resources" 34 | 35 | def load_dynamodb_resource(resource_name: str, additional_replacements: Dict[str, Any] = {}): 36 | """ Overrides the default `load_resource_file` to access the specific resources 37 | directory for the current service. 38 | """ 39 | return load_resource_file(resource_directory, resource_name, additional_replacements=additional_replacements) 40 | 41 | def wait_for_cr_status( 42 | reference: k8s.CustomResourceReference, 43 | status_field: str, 44 | desired_status: str, 45 | wait_periods: int, 46 | period_length: int, 47 | ): 48 | """ 49 | Waits for the specified condition in CR status to reach the desired value. 50 | """ 51 | actual_status = None 52 | for _ in range(wait_periods): 53 | time.sleep(period_length) 54 | resource = k8s.get_resource(reference) 55 | actual_status = resource["status"][status_field] 56 | if actual_status == desired_status: 57 | break 58 | 59 | else: 60 | logging.error( 61 | f"Wait for status: {desired_status} timed out. Actual status: {actual_status}" 62 | ) 63 | 64 | assert actual_status == desired_status 65 | 66 | def get_resource_tags(resource_arn: str): 67 | region = get_region() 68 | ddb_client = boto3.client('dynamodb', region_name=region) 69 | tags = [] 70 | next_token = "" 71 | while True: 72 | resp = ddb_client.list_tags_of_resource( 73 | ResourceArn=resource_arn, 74 | NextToken=next_token, 75 | ) 76 | tags += resp['Tags'] 77 | if not 'NextToken' in resp.keys(): 78 | break 79 | next_token = resp['NextToken'] 80 | 81 | return tags -------------------------------------------------------------------------------- /pkg/resource/table/hooks_continuous_backup.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package table 15 | 16 | import ( 17 | "context" 18 | 19 | ackrtlog "github.com/aws-controllers-k8s/runtime/pkg/runtime/log" 20 | svcsdk "github.com/aws/aws-sdk-go-v2/service/dynamodb" 21 | svcsdktypes "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" 22 | 23 | "github.com/aws-controllers-k8s/dynamodb-controller/apis/v1alpha1" 24 | ) 25 | 26 | // syncContinuousBackup syncs the PointInTimeRecoverySpecification of the dynamodb table. 27 | func (rm *resourceManager) syncContinuousBackup( 28 | ctx context.Context, 29 | desired *resource, 30 | ) (err error) { 31 | rlog := ackrtlog.FromContext(ctx) 32 | exit := rlog.Trace("rm.syncContinuousBackup") 33 | defer func(err error) { exit(err) }(err) 34 | 35 | pitrSpec := &svcsdktypes.PointInTimeRecoverySpecification{} 36 | if desired.ko.Spec.ContinuousBackups != nil && 37 | desired.ko.Spec.ContinuousBackups.PointInTimeRecoveryEnabled != nil { 38 | pitrSpec.PointInTimeRecoveryEnabled = desired.ko.Spec.ContinuousBackups.PointInTimeRecoveryEnabled 39 | } 40 | 41 | _, err = rm.sdkapi.UpdateContinuousBackups( 42 | ctx, 43 | &svcsdk.UpdateContinuousBackupsInput{ 44 | TableName: desired.ko.Spec.TableName, 45 | PointInTimeRecoverySpecification: pitrSpec, 46 | }, 47 | ) 48 | rm.metrics.RecordAPICall("UPDATE", "UpdateContinuousBackups", err) 49 | return err 50 | } 51 | 52 | // getResourcePointInTimeRecoveryWithContext gets the PointInTimeRecoverySpecification of the dynamodb table. 53 | func (rm *resourceManager) getResourcePointInTimeRecoveryWithContext( 54 | ctx context.Context, 55 | tableName *string, 56 | ) (*v1alpha1.PointInTimeRecoverySpecification, error) { 57 | var err error 58 | rlog := ackrtlog.FromContext(ctx) 59 | exit := rlog.Trace("rm.getResourcePointInTimeRecoveryWithContext") 60 | defer func(err error) { exit(err) }(err) 61 | 62 | res, err := rm.sdkapi.DescribeContinuousBackups( 63 | ctx, 64 | &svcsdk.DescribeContinuousBackupsInput{ 65 | TableName: tableName, 66 | }, 67 | ) 68 | 69 | rm.metrics.RecordAPICall("GET", "DescribeContinuousBackups", err) 70 | if err != nil { 71 | return nil, err 72 | } 73 | 74 | isEnabled := false 75 | if res.ContinuousBackupsDescription != nil { 76 | isEnabled = res.ContinuousBackupsDescription.PointInTimeRecoveryDescription.PointInTimeRecoveryStatus == svcsdktypes.PointInTimeRecoveryStatusEnabled 77 | } 78 | 79 | return &v1alpha1.PointInTimeRecoverySpecification{ 80 | PointInTimeRecoveryEnabled: &isEnabled, 81 | }, nil 82 | } 83 | -------------------------------------------------------------------------------- /helm/crds/services.k8s.aws_iamroleselectors.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.19.0 7 | name: iamroleselectors.services.k8s.aws 8 | spec: 9 | group: services.k8s.aws 10 | names: 11 | kind: IAMRoleSelector 12 | listKind: IAMRoleSelectorList 13 | plural: iamroleselectors 14 | singular: iamroleselector 15 | scope: Cluster 16 | versions: 17 | - name: v1alpha1 18 | schema: 19 | openAPIV3Schema: 20 | description: IAMRoleSelector is the schema for the IAMRoleSelector API. 21 | properties: 22 | apiVersion: 23 | description: |- 24 | APIVersion defines the versioned schema of this representation of an object. 25 | Servers should convert recognized schemas to the latest internal value, and 26 | may reject unrecognized values. 27 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources 28 | type: string 29 | kind: 30 | description: |- 31 | Kind is a string value representing the REST resource this object represents. 32 | Servers may infer this from the endpoint the client submits requests to. 33 | Cannot be updated. 34 | In CamelCase. 35 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds 36 | type: string 37 | metadata: 38 | type: object 39 | spec: 40 | properties: 41 | arn: 42 | type: string 43 | x-kubernetes-validations: 44 | - message: Value is immutable once set 45 | rule: self == oldSelf 46 | namespaceSelector: 47 | description: IAMRoleSelectorSpec defines the desired state of IAMRoleSelector 48 | properties: 49 | labelSelector: 50 | description: LabelSelector is a label query over a set of resources. 51 | properties: 52 | matchLabels: 53 | additionalProperties: 54 | type: string 55 | type: object 56 | required: 57 | - matchLabels 58 | type: object 59 | names: 60 | items: 61 | type: string 62 | type: array 63 | required: 64 | - names 65 | type: object 66 | resourceTypeSelector: 67 | items: 68 | properties: 69 | group: 70 | type: string 71 | kind: 72 | type: string 73 | version: 74 | type: string 75 | required: 76 | - group 77 | - kind 78 | - version 79 | type: object 80 | type: array 81 | required: 82 | - arn 83 | type: object 84 | status: 85 | type: object 86 | type: object 87 | served: true 88 | storage: true 89 | subresources: 90 | status: {} 91 | -------------------------------------------------------------------------------- /config/crd/common/bases/services.k8s.aws_iamroleselectors.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.19.0 7 | name: iamroleselectors.services.k8s.aws 8 | spec: 9 | group: services.k8s.aws 10 | names: 11 | kind: IAMRoleSelector 12 | listKind: IAMRoleSelectorList 13 | plural: iamroleselectors 14 | singular: iamroleselector 15 | scope: Cluster 16 | versions: 17 | - name: v1alpha1 18 | schema: 19 | openAPIV3Schema: 20 | description: IAMRoleSelector is the schema for the IAMRoleSelector API. 21 | properties: 22 | apiVersion: 23 | description: |- 24 | APIVersion defines the versioned schema of this representation of an object. 25 | Servers should convert recognized schemas to the latest internal value, and 26 | may reject unrecognized values. 27 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources 28 | type: string 29 | kind: 30 | description: |- 31 | Kind is a string value representing the REST resource this object represents. 32 | Servers may infer this from the endpoint the client submits requests to. 33 | Cannot be updated. 34 | In CamelCase. 35 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds 36 | type: string 37 | metadata: 38 | type: object 39 | spec: 40 | properties: 41 | arn: 42 | type: string 43 | x-kubernetes-validations: 44 | - message: Value is immutable once set 45 | rule: self == oldSelf 46 | namespaceSelector: 47 | description: IAMRoleSelectorSpec defines the desired state of IAMRoleSelector 48 | properties: 49 | labelSelector: 50 | description: LabelSelector is a label query over a set of resources. 51 | properties: 52 | matchLabels: 53 | additionalProperties: 54 | type: string 55 | type: object 56 | required: 57 | - matchLabels 58 | type: object 59 | names: 60 | items: 61 | type: string 62 | type: array 63 | required: 64 | - names 65 | type: object 66 | resourceTypeSelector: 67 | items: 68 | properties: 69 | group: 70 | type: string 71 | kind: 72 | type: string 73 | version: 74 | type: string 75 | required: 76 | - group 77 | - kind 78 | - version 79 | type: object 80 | type: array 81 | required: 82 | - arn 83 | type: object 84 | status: 85 | type: object 86 | type: object 87 | served: true 88 | storage: true 89 | subresources: 90 | status: {} 91 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug 4 | report, new feature, correction, or additional documentation, we greatly value 5 | feedback and contributions from our community. 6 | 7 | Please read through this document before submitting any issues or pull requests 8 | to ensure we have all the necessary information to effectively respond to your 9 | bug report or contribution. 10 | 11 | ## Reporting Bugs/Feature Requests 12 | 13 | We welcome you to use the GitHub issue tracker to report bugs or suggest 14 | features. 15 | 16 | When filing an issue, please check existing open, or recently closed, issues to 17 | make sure somebody else hasn't already reported the issue. Please try to 18 | include as much information as you can. Details like these are incredibly 19 | useful: 20 | 21 | * A reproducible test case or series of steps 22 | * The version of our code being used 23 | * Any modifications you've made relevant to the bug 24 | * Anything unusual about your environment or deployment 25 | 26 | ## Contributing via Pull Requests 27 | 28 | Contributions via pull requests are much appreciated. Before sending us a pull 29 | request, please ensure that: 30 | 31 | 1. You are working against the latest source on the *main* branch. 32 | 2. You check existing open, and recently merged, pull requests to make sure 33 | someone else hasn't addressed the problem already. 34 | 3. You open an issue to discuss any significant work - we would hate for your 35 | time to be wasted. 36 | 37 | To send us a pull request, please: 38 | 39 | 1. Fork the repository. 40 | 2. Modify the source; please focus on the specific change you are contributing. 41 | If you also reformat all the code, it will be hard for us to focus on your 42 | change. 43 | 3. Ensure local tests pass. 44 | 4. Commit to your fork using clear commit messages. 45 | 5. Send us a pull request, answering any default questions in the pull request 46 | interface. 47 | 6. Pay attention to any automated CI failures reported in the pull request, and 48 | stay involved in the conversation. 49 | 50 | GitHub provides additional document on [forking a repository][fork] and 51 | [creating a pull request][pr]. 52 | 53 | [fork]: https://help.github.com/articles/fork-a-repo/ 54 | [pr]: https://help.github.com/articles/creating-a-pull-request/ 55 | 56 | ## Finding contributions to work on 57 | 58 | Looking at the existing issues is a great way to find something to contribute 59 | on. As our projects, by default, use the default GitHub issue labels 60 | (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at 61 | any 'help wanted' issues is a great place to start. 62 | 63 | ## Developer documentation 64 | 65 | [See the documentation][dev-docs] for detailed development information. 66 | 67 | [dev-docs]: https://aws-controllers-k8s.github.io/community/docs/contributor-docs/overview/ 68 | 69 | ## Code of Conduct 70 | 71 | We adhere to the [Amazon Open Source Code of Conduct][coc]. 72 | 73 | [coc]: https://aws.github.io/code-of-conduct 74 | 75 | ## Security issue notifications 76 | 77 | If you discover a potential security issue in this project we ask that you 78 | notify AWS/Amazon Security via our [vulnerability reporting page][vuln]. Please 79 | do **not** create a public Github issue. 80 | 81 | [vuln]: http://aws.amazon.com/security/vulnerability-reporting/ 82 | 83 | ## License 84 | 85 | This project is [licensed][./LICENSE] under the Apache-2.0 License. 86 | -------------------------------------------------------------------------------- /apis/v1alpha1/global_table.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by ack-generate. DO NOT EDIT. 15 | 16 | package v1alpha1 17 | 18 | import ( 19 | ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | ) 22 | 23 | // GlobalTableSpec defines the desired state of GlobalTable. 24 | // 25 | // Represents the properties of a global table. 26 | type GlobalTableSpec struct { 27 | 28 | // The global table name. 29 | // 30 | // Regex Pattern: `^[a-zA-Z0-9_.-]+$` 31 | // +kubebuilder:validation:Required 32 | GlobalTableName *string `json:"globalTableName"` 33 | // The Regions where the global table needs to be created. 34 | // +kubebuilder:validation:Required 35 | ReplicationGroup []*Replica `json:"replicationGroup"` 36 | } 37 | 38 | // GlobalTableStatus defines the observed state of GlobalTable 39 | type GlobalTableStatus struct { 40 | // All CRs managed by ACK have a common `Status.ACKResourceMetadata` member 41 | // that is used to contain resource sync state, account ownership, 42 | // constructed ARN for the resource 43 | // +kubebuilder:validation:Optional 44 | ACKResourceMetadata *ackv1alpha1.ResourceMetadata `json:"ackResourceMetadata"` 45 | // All CRs managed by ACK have a common `Status.Conditions` member that 46 | // contains a collection of `ackv1alpha1.Condition` objects that describe 47 | // the various terminal states of the CR and its backend AWS service API 48 | // resource 49 | // +kubebuilder:validation:Optional 50 | Conditions []*ackv1alpha1.Condition `json:"conditions"` 51 | // The creation time of the global table. 52 | // +kubebuilder:validation:Optional 53 | CreationDateTime *metav1.Time `json:"creationDateTime,omitempty"` 54 | // The current state of the global table: 55 | // 56 | // * CREATING - The global table is being created. 57 | // 58 | // * UPDATING - The global table is being updated. 59 | // 60 | // * DELETING - The global table is being deleted. 61 | // 62 | // * ACTIVE - The global table is ready for use. 63 | // +kubebuilder:validation:Optional 64 | GlobalTableStatus *string `json:"globalTableStatus,omitempty"` 65 | } 66 | 67 | // GlobalTable is the Schema for the GlobalTables API 68 | // +kubebuilder:object:root=true 69 | // +kubebuilder:subresource:status 70 | type GlobalTable struct { 71 | metav1.TypeMeta `json:",inline"` 72 | metav1.ObjectMeta `json:"metadata,omitempty"` 73 | Spec GlobalTableSpec `json:"spec,omitempty"` 74 | Status GlobalTableStatus `json:"status,omitempty"` 75 | } 76 | 77 | // GlobalTableList contains a list of GlobalTable 78 | // +kubebuilder:object:root=true 79 | type GlobalTableList struct { 80 | metav1.TypeMeta `json:",inline"` 81 | metav1.ListMeta `json:"metadata,omitempty"` 82 | Items []GlobalTable `json:"items"` 83 | } 84 | 85 | func init() { 86 | SchemeBuilder.Register(&GlobalTable{}, &GlobalTableList{}) 87 | } 88 | -------------------------------------------------------------------------------- /pkg/resource/table/hooks_ttl.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package table 15 | 16 | import ( 17 | "context" 18 | 19 | ackrtlog "github.com/aws-controllers-k8s/runtime/pkg/runtime/log" 20 | "github.com/aws/aws-sdk-go-v2/aws" 21 | svcsdk "github.com/aws/aws-sdk-go-v2/service/dynamodb" 22 | svcsdktypes "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" 23 | 24 | "github.com/aws-controllers-k8s/dynamodb-controller/apis/v1alpha1" 25 | ) 26 | 27 | // syncTTL updates a dynamodb table's TimeToLive property. 28 | func (rm *resourceManager) syncTTL( 29 | ctx context.Context, 30 | desired *resource, 31 | latest *resource, 32 | ) (err error) { 33 | rlog := ackrtlog.FromContext(ctx) 34 | exit := rlog.Trace("rm.syncTTL") 35 | defer func(err error) { exit(err) }(err) 36 | 37 | ttlSpec := &svcsdktypes.TimeToLiveSpecification{} 38 | if desired.ko.Spec.TimeToLive != nil { 39 | ttlSpec.AttributeName = desired.ko.Spec.TimeToLive.AttributeName 40 | ttlSpec.Enabled = desired.ko.Spec.TimeToLive.Enabled 41 | } else { 42 | // In order to disable the TTL, we can't simply call the 43 | // `UpdateTimeToLive` method with an empty specification. Instead, we 44 | // must explicitly set the enabled to false and provide the attribute 45 | // name of the existing TTL. 46 | currentAttrName := "" 47 | if latest.ko.Spec.TimeToLive != nil && 48 | latest.ko.Spec.TimeToLive.AttributeName != nil { 49 | currentAttrName = *latest.ko.Spec.TimeToLive.AttributeName 50 | } 51 | 52 | ttlSpec.AttributeName = ¤tAttrName 53 | ttlSpec.Enabled = aws.Bool(false) 54 | } 55 | 56 | _, err = rm.sdkapi.UpdateTimeToLive( 57 | ctx, 58 | &svcsdk.UpdateTimeToLiveInput{ 59 | TableName: desired.ko.Spec.TableName, 60 | TimeToLiveSpecification: ttlSpec, 61 | }, 62 | ) 63 | rm.metrics.RecordAPICall("UPDATE", "UpdateTimeToLive", err) 64 | return err 65 | } 66 | 67 | // getResourceTTLWithContext queries the table TTL of a given resource. 68 | func (rm *resourceManager) getResourceTTLWithContext(ctx context.Context, tableName *string) (*v1alpha1.TimeToLiveSpecification, error) { 69 | var err error 70 | rlog := ackrtlog.FromContext(ctx) 71 | exit := rlog.Trace("rm.getResourceTTLWithContext") 72 | defer func(err error) { exit(err) }(err) 73 | 74 | res, err := rm.sdkapi.DescribeTimeToLive( 75 | ctx, 76 | &svcsdk.DescribeTimeToLiveInput{ 77 | TableName: tableName, 78 | }, 79 | ) 80 | rm.metrics.RecordAPICall("GET", "DescribeTimeToLive", err) 81 | if err != nil { 82 | return nil, err 83 | } 84 | 85 | // Treat status "ENABLING" and "ENABLED" as `Enabled` == true 86 | isEnabled := res.TimeToLiveDescription.TimeToLiveStatus == svcsdktypes.TimeToLiveStatusEnabled || 87 | res.TimeToLiveDescription.TimeToLiveStatus == svcsdktypes.TimeToLiveStatusEnabling 88 | 89 | return &v1alpha1.TimeToLiveSpecification{ 90 | AttributeName: res.TimeToLiveDescription.AttributeName, 91 | Enabled: &isEnabled, 92 | }, nil 93 | } 94 | -------------------------------------------------------------------------------- /pkg/resource/table/conditions.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package table 15 | 16 | import ( 17 | corev1 "k8s.io/api/core/v1" 18 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 19 | 20 | ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" 21 | ) 22 | 23 | // getSyncedCondition returns the Condition in the resource's Conditions 24 | // collection that is of type ConditionTypeResourceSynced. If no such condition 25 | // is found, returns nil. 26 | // 27 | // TODO(jaypipes): Move to ACK code-gen templates. 28 | func getSyncedCondition(r *resource) *ackv1alpha1.Condition { 29 | return getConditionOfType(r, ackv1alpha1.ConditionTypeResourceSynced) 30 | } 31 | 32 | // getTerminalCondition returns the Condition in the resource's Conditions 33 | // collection that is of type ConditionTypeTerminal. If no such condition is 34 | // found, returns nil. 35 | // 36 | // TODO(jaypipes): Move to ACK code-gen templates. 37 | func getTerminalCondition(r *resource) *ackv1alpha1.Condition { 38 | return getConditionOfType(r, ackv1alpha1.ConditionTypeTerminal) 39 | } 40 | 41 | // getConditionOfType returns the Condition in the resource's Conditions 42 | // collection of the supplied type. If no such condition is found, returns nil. 43 | // 44 | // TODO(jaypipes): Move to ACK code-gen templates. 45 | func getConditionOfType( 46 | r *resource, 47 | condType ackv1alpha1.ConditionType, 48 | ) *ackv1alpha1.Condition { 49 | for _, condition := range r.ko.Status.Conditions { 50 | if condition.Type == condType { 51 | return condition 52 | } 53 | } 54 | return nil 55 | } 56 | 57 | // setSyncedCondition sets the resource's Condition of type 58 | // ConditionTypeResourceSynced to the supplied status, optional message and 59 | // reason. 60 | // 61 | // TODO(jaypipes): Move to ACK code-gen templates. 62 | func setSyncedCondition( 63 | r *resource, 64 | status corev1.ConditionStatus, 65 | message *string, 66 | reason *string, 67 | ) { 68 | c := getSyncedCondition(r) 69 | if c == nil { 70 | c = &ackv1alpha1.Condition{ 71 | Type: ackv1alpha1.ConditionTypeResourceSynced, 72 | } 73 | r.ko.Status.Conditions = append(r.ko.Status.Conditions, c) 74 | } 75 | now := metav1.Now() 76 | c.LastTransitionTime = &now 77 | c.Status = status 78 | } 79 | 80 | // setTerminalCondition sets the resource's Condition of type 81 | // ConditionTypeTerminal to the supplied status, optional message and reason. 82 | // 83 | // TODO(jaypipes): Move to ACK code-gen templates. 84 | func setTerminalCondition( 85 | r *resource, 86 | status corev1.ConditionStatus, 87 | message *string, 88 | reason *string, 89 | ) { 90 | c := getTerminalCondition(r) 91 | if c == nil { 92 | c = &ackv1alpha1.Condition{ 93 | Type: ackv1alpha1.ConditionTypeTerminal, 94 | } 95 | r.ko.Status.Conditions = append(r.ko.Status.Conditions, c) 96 | } 97 | now := metav1.Now() 98 | c.LastTransitionTime = &now 99 | c.Status = status 100 | c.Message = message 101 | c.Reason = reason 102 | } 103 | -------------------------------------------------------------------------------- /pkg/resource/table/manager_factory.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by ack-generate. DO NOT EDIT. 15 | 16 | package table 17 | 18 | import ( 19 | "fmt" 20 | "sync" 21 | 22 | ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" 23 | ackcfg "github.com/aws-controllers-k8s/runtime/pkg/config" 24 | ackmetrics "github.com/aws-controllers-k8s/runtime/pkg/metrics" 25 | acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" 26 | "github.com/aws/aws-sdk-go-v2/aws" 27 | "github.com/go-logr/logr" 28 | 29 | svcresource "github.com/aws-controllers-k8s/dynamodb-controller/pkg/resource" 30 | ) 31 | 32 | // resourceManagerFactory produces resourceManager objects. It implements the 33 | // `types.AWSResourceManagerFactory` interface. 34 | type resourceManagerFactory struct { 35 | sync.RWMutex 36 | // rmCache contains resource managers for a particular AWS account ID 37 | rmCache map[string]*resourceManager 38 | } 39 | 40 | // ResourcePrototype returns an AWSResource that resource managers produced by 41 | // this factory will handle 42 | func (f *resourceManagerFactory) ResourceDescriptor() acktypes.AWSResourceDescriptor { 43 | return &resourceDescriptor{} 44 | } 45 | 46 | // ManagerFor returns a resource manager object that can manage resources for a 47 | // supplied AWS account 48 | func (f *resourceManagerFactory) ManagerFor( 49 | cfg ackcfg.Config, 50 | clientcfg aws.Config, 51 | log logr.Logger, 52 | metrics *ackmetrics.Metrics, 53 | rr acktypes.Reconciler, 54 | id ackv1alpha1.AWSAccountID, 55 | region ackv1alpha1.AWSRegion, 56 | roleARN ackv1alpha1.AWSResourceName, 57 | ) (acktypes.AWSResourceManager, error) { 58 | // We use the account ID, region, and role ARN to uniquely identify a 59 | // resource manager. This helps us to avoid creating multiple resource 60 | // managers for the same account/region/roleARN combination. 61 | rmId := fmt.Sprintf("%s/%s/%s", id, region, roleARN) 62 | f.RLock() 63 | rm, found := f.rmCache[rmId] 64 | f.RUnlock() 65 | 66 | if found { 67 | return rm, nil 68 | } 69 | 70 | f.Lock() 71 | defer f.Unlock() 72 | 73 | rm, err := newResourceManager(cfg, clientcfg, log, metrics, rr, id, region) 74 | if err != nil { 75 | return nil, err 76 | } 77 | f.rmCache[rmId] = rm 78 | return rm, nil 79 | } 80 | 81 | // IsAdoptable returns true if the resource is able to be adopted 82 | func (f *resourceManagerFactory) IsAdoptable() bool { 83 | return true 84 | } 85 | 86 | // RequeueOnSuccessSeconds returns true if the resource should be requeued after specified seconds 87 | // Default is false which means resource will not be requeued after success. 88 | func (f *resourceManagerFactory) RequeueOnSuccessSeconds() int { 89 | return 0 90 | } 91 | 92 | func newResourceManagerFactory() *resourceManagerFactory { 93 | return &resourceManagerFactory{ 94 | rmCache: map[string]*resourceManager{}, 95 | } 96 | } 97 | 98 | func init() { 99 | svcresource.RegisterManagerFactory(newResourceManagerFactory()) 100 | } 101 | -------------------------------------------------------------------------------- /pkg/resource/backup/manager_factory.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by ack-generate. DO NOT EDIT. 15 | 16 | package backup 17 | 18 | import ( 19 | "fmt" 20 | "sync" 21 | 22 | ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" 23 | ackcfg "github.com/aws-controllers-k8s/runtime/pkg/config" 24 | ackmetrics "github.com/aws-controllers-k8s/runtime/pkg/metrics" 25 | acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" 26 | "github.com/aws/aws-sdk-go-v2/aws" 27 | "github.com/go-logr/logr" 28 | 29 | svcresource "github.com/aws-controllers-k8s/dynamodb-controller/pkg/resource" 30 | ) 31 | 32 | // resourceManagerFactory produces resourceManager objects. It implements the 33 | // `types.AWSResourceManagerFactory` interface. 34 | type resourceManagerFactory struct { 35 | sync.RWMutex 36 | // rmCache contains resource managers for a particular AWS account ID 37 | rmCache map[string]*resourceManager 38 | } 39 | 40 | // ResourcePrototype returns an AWSResource that resource managers produced by 41 | // this factory will handle 42 | func (f *resourceManagerFactory) ResourceDescriptor() acktypes.AWSResourceDescriptor { 43 | return &resourceDescriptor{} 44 | } 45 | 46 | // ManagerFor returns a resource manager object that can manage resources for a 47 | // supplied AWS account 48 | func (f *resourceManagerFactory) ManagerFor( 49 | cfg ackcfg.Config, 50 | clientcfg aws.Config, 51 | log logr.Logger, 52 | metrics *ackmetrics.Metrics, 53 | rr acktypes.Reconciler, 54 | id ackv1alpha1.AWSAccountID, 55 | region ackv1alpha1.AWSRegion, 56 | roleARN ackv1alpha1.AWSResourceName, 57 | ) (acktypes.AWSResourceManager, error) { 58 | // We use the account ID, region, and role ARN to uniquely identify a 59 | // resource manager. This helps us to avoid creating multiple resource 60 | // managers for the same account/region/roleARN combination. 61 | rmId := fmt.Sprintf("%s/%s/%s", id, region, roleARN) 62 | f.RLock() 63 | rm, found := f.rmCache[rmId] 64 | f.RUnlock() 65 | 66 | if found { 67 | return rm, nil 68 | } 69 | 70 | f.Lock() 71 | defer f.Unlock() 72 | 73 | rm, err := newResourceManager(cfg, clientcfg, log, metrics, rr, id, region) 74 | if err != nil { 75 | return nil, err 76 | } 77 | f.rmCache[rmId] = rm 78 | return rm, nil 79 | } 80 | 81 | // IsAdoptable returns true if the resource is able to be adopted 82 | func (f *resourceManagerFactory) IsAdoptable() bool { 83 | return true 84 | } 85 | 86 | // RequeueOnSuccessSeconds returns true if the resource should be requeued after specified seconds 87 | // Default is false which means resource will not be requeued after success. 88 | func (f *resourceManagerFactory) RequeueOnSuccessSeconds() int { 89 | return 0 90 | } 91 | 92 | func newResourceManagerFactory() *resourceManagerFactory { 93 | return &resourceManagerFactory{ 94 | rmCache: map[string]*resourceManager{}, 95 | } 96 | } 97 | 98 | func init() { 99 | svcresource.RegisterManagerFactory(newResourceManagerFactory()) 100 | } 101 | -------------------------------------------------------------------------------- /pkg/resource/global_table/manager_factory.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by ack-generate. DO NOT EDIT. 15 | 16 | package global_table 17 | 18 | import ( 19 | "fmt" 20 | "sync" 21 | 22 | ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" 23 | ackcfg "github.com/aws-controllers-k8s/runtime/pkg/config" 24 | ackmetrics "github.com/aws-controllers-k8s/runtime/pkg/metrics" 25 | acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" 26 | "github.com/aws/aws-sdk-go-v2/aws" 27 | "github.com/go-logr/logr" 28 | 29 | svcresource "github.com/aws-controllers-k8s/dynamodb-controller/pkg/resource" 30 | ) 31 | 32 | // resourceManagerFactory produces resourceManager objects. It implements the 33 | // `types.AWSResourceManagerFactory` interface. 34 | type resourceManagerFactory struct { 35 | sync.RWMutex 36 | // rmCache contains resource managers for a particular AWS account ID 37 | rmCache map[string]*resourceManager 38 | } 39 | 40 | // ResourcePrototype returns an AWSResource that resource managers produced by 41 | // this factory will handle 42 | func (f *resourceManagerFactory) ResourceDescriptor() acktypes.AWSResourceDescriptor { 43 | return &resourceDescriptor{} 44 | } 45 | 46 | // ManagerFor returns a resource manager object that can manage resources for a 47 | // supplied AWS account 48 | func (f *resourceManagerFactory) ManagerFor( 49 | cfg ackcfg.Config, 50 | clientcfg aws.Config, 51 | log logr.Logger, 52 | metrics *ackmetrics.Metrics, 53 | rr acktypes.Reconciler, 54 | id ackv1alpha1.AWSAccountID, 55 | region ackv1alpha1.AWSRegion, 56 | roleARN ackv1alpha1.AWSResourceName, 57 | ) (acktypes.AWSResourceManager, error) { 58 | // We use the account ID, region, and role ARN to uniquely identify a 59 | // resource manager. This helps us to avoid creating multiple resource 60 | // managers for the same account/region/roleARN combination. 61 | rmId := fmt.Sprintf("%s/%s/%s", id, region, roleARN) 62 | f.RLock() 63 | rm, found := f.rmCache[rmId] 64 | f.RUnlock() 65 | 66 | if found { 67 | return rm, nil 68 | } 69 | 70 | f.Lock() 71 | defer f.Unlock() 72 | 73 | rm, err := newResourceManager(cfg, clientcfg, log, metrics, rr, id, region) 74 | if err != nil { 75 | return nil, err 76 | } 77 | f.rmCache[rmId] = rm 78 | return rm, nil 79 | } 80 | 81 | // IsAdoptable returns true if the resource is able to be adopted 82 | func (f *resourceManagerFactory) IsAdoptable() bool { 83 | return true 84 | } 85 | 86 | // RequeueOnSuccessSeconds returns true if the resource should be requeued after specified seconds 87 | // Default is false which means resource will not be requeued after success. 88 | func (f *resourceManagerFactory) RequeueOnSuccessSeconds() int { 89 | return 0 90 | } 91 | 92 | func newResourceManagerFactory() *resourceManagerFactory { 93 | return &resourceManagerFactory{ 94 | rmCache: map[string]*resourceManager{}, 95 | } 96 | } 97 | 98 | func init() { 99 | svcresource.RegisterManagerFactory(newResourceManagerFactory()) 100 | } 101 | -------------------------------------------------------------------------------- /config/controller/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: ack-system 5 | --- 6 | apiVersion: apps/v1 7 | kind: Deployment 8 | metadata: 9 | name: ack-dynamodb-controller 10 | namespace: ack-system 11 | labels: 12 | app.kubernetes.io/name: ack-dynamodb-controller 13 | app.kubernetes.io/part-of: ack-system 14 | spec: 15 | selector: 16 | matchLabels: 17 | app.kubernetes.io/name: ack-dynamodb-controller 18 | replicas: 1 19 | template: 20 | metadata: 21 | labels: 22 | app.kubernetes.io/name: ack-dynamodb-controller 23 | spec: 24 | containers: 25 | - command: 26 | - ./bin/controller 27 | args: 28 | - --aws-region 29 | - "$(AWS_REGION)" 30 | - --aws-endpoint-url 31 | - "$(AWS_ENDPOINT_URL)" 32 | - --enable-development-logging=$(ACK_ENABLE_DEVELOPMENT_LOGGING) 33 | - --log-level 34 | - "$(ACK_LOG_LEVEL)" 35 | - --resource-tags 36 | - "$(ACK_RESOURCE_TAGS)" 37 | - --watch-namespace 38 | - "$(ACK_WATCH_NAMESPACE)" 39 | - --enable-leader-election=$(ENABLE_LEADER_ELECTION) 40 | - --leader-election-namespace 41 | - "$(LEADER_ELECTION_NAMESPACE)" 42 | - --reconcile-default-max-concurrent-syncs 43 | - "$(RECONCILE_DEFAULT_MAX_CONCURRENT_SYNCS)" 44 | - --feature-gates 45 | - "$(FEATURE_GATES)" 46 | - --enable-carm=$(ENABLE_CARM) 47 | image: controller:latest 48 | name: controller 49 | ports: 50 | - name: http 51 | containerPort: 8080 52 | resources: 53 | limits: 54 | cpu: 100m 55 | memory: 300Mi 56 | requests: 57 | cpu: 100m 58 | memory: 200Mi 59 | env: 60 | - name: ACK_SYSTEM_NAMESPACE 61 | valueFrom: 62 | fieldRef: 63 | fieldPath: metadata.namespace 64 | - name: AWS_REGION 65 | value: "" 66 | - name: AWS_ENDPOINT_URL 67 | value: "" 68 | - name: ACK_WATCH_NAMESPACE 69 | value: "" 70 | - name: ACK_ENABLE_DEVELOPMENT_LOGGING 71 | value: "false" 72 | - name: ACK_LOG_LEVEL 73 | value: "info" 74 | - name: ACK_RESOURCE_TAGS 75 | value: "services.k8s.aws/controller-version=%CONTROLLER_SERVICE%-%CONTROLLER_VERSION%,services.k8s.aws/namespace=%K8S_NAMESPACE%" 76 | - name: ENABLE_LEADER_ELECTION 77 | value: "false" 78 | - name: LEADER_ELECTION_NAMESPACE 79 | value: "ack-system" 80 | - name: "RECONCILE_DEFAULT_MAX_CONCURRENT_SYNCS" 81 | value: "1" 82 | - name: "FEATURE_GATES" 83 | value: "" 84 | - name: "ENABLE_CARM" 85 | value: "true" 86 | securityContext: 87 | allowPrivilegeEscalation: false 88 | privileged: false 89 | runAsNonRoot: true 90 | capabilities: 91 | drop: 92 | - ALL 93 | livenessProbe: 94 | httpGet: 95 | path: /healthz 96 | port: 8081 97 | initialDelaySeconds: 15 98 | periodSeconds: 20 99 | readinessProbe: 100 | httpGet: 101 | path: /readyz 102 | port: 8081 103 | initialDelaySeconds: 5 104 | periodSeconds: 10 105 | securityContext: 106 | seccompProfile: 107 | type: RuntimeDefault 108 | terminationGracePeriodSeconds: 10 109 | serviceAccountName: ack-dynamodb-controller 110 | hostIPC: false 111 | hostPID: false 112 | hostNetwork: false 113 | dnsPolicy: ClusterFirst 114 | -------------------------------------------------------------------------------- /helm/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* The name of the application this chart installs */}} 2 | {{- define "ack-dynamodb-controller.app.name" -}} 3 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 4 | {{- end -}} 5 | 6 | {{/* 7 | Create a default fully qualified app name. 8 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 9 | If release name contains chart name it will be used as a full name. 10 | */}} 11 | {{- define "ack-dynamodb-controller.app.fullname" -}} 12 | {{- if .Values.fullnameOverride -}} 13 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} 14 | {{- else -}} 15 | {{- $name := default .Chart.Name .Values.nameOverride -}} 16 | {{- if contains $name .Release.Name -}} 17 | {{- .Release.Name | trunc 63 | trimSuffix "-" -}} 18 | {{- else -}} 19 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 20 | {{- end -}} 21 | {{- end -}} 22 | {{- end -}} 23 | 24 | {{/* The name and version as used by the chart label */}} 25 | {{- define "ack-dynamodb-controller.chart.name-version" -}} 26 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} 27 | {{- end -}} 28 | 29 | {{/* The name of the service account to use */}} 30 | {{- define "ack-dynamodb-controller.service-account.name" -}} 31 | {{ default "default" .Values.serviceAccount.name }} 32 | {{- end -}} 33 | 34 | {{- define "ack-dynamodb-controller.watch-namespace" -}} 35 | {{- if eq .Values.installScope "namespace" -}} 36 | {{ .Values.watchNamespace | default .Release.Namespace }} 37 | {{- end -}} 38 | {{- end -}} 39 | 40 | {{/* The mount path for the shared credentials file */}} 41 | {{- define "ack-dynamodb-controller.aws.credentials.secret_mount_path" -}} 42 | {{- "/var/run/secrets/aws" -}} 43 | {{- end -}} 44 | 45 | {{/* The path the shared credentials file is mounted */}} 46 | {{- define "ack-dynamodb-controller.aws.credentials.path" -}} 47 | {{ $secret_mount_path := include "ack-dynamodb-controller.aws.credentials.secret_mount_path" . }} 48 | {{- printf "%s/%s" $secret_mount_path .Values.aws.credentials.secretKey -}} 49 | {{- end -}} 50 | 51 | {{/* The rules a of ClusterRole or Role */}} 52 | {{- define "ack-dynamodb-controller.rbac-rules" -}} 53 | rules: 54 | - apiGroups: 55 | - "" 56 | resources: 57 | - configmaps 58 | - secrets 59 | verbs: 60 | - get 61 | - list 62 | - patch 63 | - watch 64 | - apiGroups: 65 | - "" 66 | resources: 67 | - namespaces 68 | verbs: 69 | - get 70 | - list 71 | - watch 72 | - apiGroups: 73 | - dynamodb.services.k8s.aws 74 | resources: 75 | - backups 76 | - globaltables 77 | - tables 78 | verbs: 79 | - create 80 | - delete 81 | - get 82 | - list 83 | - patch 84 | - update 85 | - watch 86 | - apiGroups: 87 | - dynamodb.services.k8s.aws 88 | resources: 89 | - backups/status 90 | - globaltables/status 91 | - tables/status 92 | verbs: 93 | - get 94 | - patch 95 | - update 96 | - apiGroups: 97 | - kms.services.k8s.aws 98 | resources: 99 | - keys 100 | - keys/status 101 | verbs: 102 | - get 103 | - list 104 | - apiGroups: 105 | - services.k8s.aws 106 | resources: 107 | - fieldexports 108 | - iamroleselectors 109 | verbs: 110 | - create 111 | - delete 112 | - get 113 | - list 114 | - patch 115 | - update 116 | - watch 117 | - apiGroups: 118 | - services.k8s.aws 119 | resources: 120 | - fieldexports/status 121 | - iamroleselectors/status 122 | verbs: 123 | - get 124 | - patch 125 | - update 126 | {{- end }} 127 | 128 | {{/* Convert k/v map to string like: "key1=value1,key2=value2,..." */}} 129 | {{- define "ack-dynamodb-controller.feature-gates" -}} 130 | {{- $list := list -}} 131 | {{- range $k, $v := .Values.featureGates -}} 132 | {{- $list = append $list (printf "%s=%s" $k ( $v | toString)) -}} 133 | {{- end -}} 134 | {{ join "," $list }} 135 | {{- end -}} 136 | -------------------------------------------------------------------------------- /pkg/resource/table/resource.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by ack-generate. DO NOT EDIT. 15 | 16 | package table 17 | 18 | import ( 19 | "fmt" 20 | 21 | ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" 22 | ackerrors "github.com/aws-controllers-k8s/runtime/pkg/errors" 23 | acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" 24 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 | rtclient "sigs.k8s.io/controller-runtime/pkg/client" 26 | 27 | svcapitypes "github.com/aws-controllers-k8s/dynamodb-controller/apis/v1alpha1" 28 | ) 29 | 30 | // Hack to avoid import errors during build... 31 | var ( 32 | _ = &ackerrors.MissingNameIdentifier 33 | ) 34 | 35 | // resource implements the `aws-controller-k8s/runtime/pkg/types.AWSResource` 36 | // interface 37 | type resource struct { 38 | // The Kubernetes-native CR representing the resource 39 | ko *svcapitypes.Table 40 | } 41 | 42 | // Identifiers returns an AWSResourceIdentifiers object containing various 43 | // identifying information, including the AWS account ID that owns the 44 | // resource, the resource's AWS Resource Name (ARN) 45 | func (r *resource) Identifiers() acktypes.AWSResourceIdentifiers { 46 | return &resourceIdentifiers{r.ko.Status.ACKResourceMetadata} 47 | } 48 | 49 | // IsBeingDeleted returns true if the Kubernetes resource has a non-zero 50 | // deletion timestamp 51 | func (r *resource) IsBeingDeleted() bool { 52 | return !r.ko.DeletionTimestamp.IsZero() 53 | } 54 | 55 | // RuntimeObject returns the Kubernetes apimachinery/runtime representation of 56 | // the AWSResource 57 | func (r *resource) RuntimeObject() rtclient.Object { 58 | return r.ko 59 | } 60 | 61 | // MetaObject returns the Kubernetes apimachinery/apis/meta/v1.Object 62 | // representation of the AWSResource 63 | func (r *resource) MetaObject() metav1.Object { 64 | return r.ko.GetObjectMeta() 65 | } 66 | 67 | // Conditions returns the ACK Conditions collection for the AWSResource 68 | func (r *resource) Conditions() []*ackv1alpha1.Condition { 69 | return r.ko.Status.Conditions 70 | } 71 | 72 | // ReplaceConditions sets the Conditions status field for the resource 73 | func (r *resource) ReplaceConditions(conditions []*ackv1alpha1.Condition) { 74 | r.ko.Status.Conditions = conditions 75 | } 76 | 77 | // SetObjectMeta sets the ObjectMeta field for the resource 78 | func (r *resource) SetObjectMeta(meta metav1.ObjectMeta) { 79 | r.ko.ObjectMeta = meta 80 | } 81 | 82 | // SetStatus will set the Status field for the resource 83 | func (r *resource) SetStatus(desired acktypes.AWSResource) { 84 | r.ko.Status = desired.(*resource).ko.Status 85 | } 86 | 87 | // SetIdentifiers sets the Spec or Status field that is referenced as the unique 88 | // resource identifier 89 | func (r *resource) SetIdentifiers(identifier *ackv1alpha1.AWSIdentifiers) error { 90 | if identifier.NameOrID == "" { 91 | return ackerrors.MissingNameIdentifier 92 | } 93 | r.ko.Spec.TableName = &identifier.NameOrID 94 | 95 | return nil 96 | } 97 | 98 | // PopulateResourceFromAnnotation populates the fields passed from adoption annotation 99 | func (r *resource) PopulateResourceFromAnnotation(fields map[string]string) error { 100 | f0, ok := fields["tableName"] 101 | if !ok { 102 | return ackerrors.NewTerminalError(fmt.Errorf("required field missing: tableName")) 103 | } 104 | r.ko.Spec.TableName = &f0 105 | 106 | return nil 107 | } 108 | 109 | // DeepCopy will return a copy of the resource 110 | func (r *resource) DeepCopy() acktypes.AWSResource { 111 | koCopy := r.ko.DeepCopy() 112 | return &resource{koCopy} 113 | } 114 | -------------------------------------------------------------------------------- /pkg/resource/global_table/resource.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by ack-generate. DO NOT EDIT. 15 | 16 | package global_table 17 | 18 | import ( 19 | "fmt" 20 | 21 | ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" 22 | ackerrors "github.com/aws-controllers-k8s/runtime/pkg/errors" 23 | acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" 24 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 | rtclient "sigs.k8s.io/controller-runtime/pkg/client" 26 | 27 | svcapitypes "github.com/aws-controllers-k8s/dynamodb-controller/apis/v1alpha1" 28 | ) 29 | 30 | // Hack to avoid import errors during build... 31 | var ( 32 | _ = &ackerrors.MissingNameIdentifier 33 | ) 34 | 35 | // resource implements the `aws-controller-k8s/runtime/pkg/types.AWSResource` 36 | // interface 37 | type resource struct { 38 | // The Kubernetes-native CR representing the resource 39 | ko *svcapitypes.GlobalTable 40 | } 41 | 42 | // Identifiers returns an AWSResourceIdentifiers object containing various 43 | // identifying information, including the AWS account ID that owns the 44 | // resource, the resource's AWS Resource Name (ARN) 45 | func (r *resource) Identifiers() acktypes.AWSResourceIdentifiers { 46 | return &resourceIdentifiers{r.ko.Status.ACKResourceMetadata} 47 | } 48 | 49 | // IsBeingDeleted returns true if the Kubernetes resource has a non-zero 50 | // deletion timestamp 51 | func (r *resource) IsBeingDeleted() bool { 52 | return !r.ko.DeletionTimestamp.IsZero() 53 | } 54 | 55 | // RuntimeObject returns the Kubernetes apimachinery/runtime representation of 56 | // the AWSResource 57 | func (r *resource) RuntimeObject() rtclient.Object { 58 | return r.ko 59 | } 60 | 61 | // MetaObject returns the Kubernetes apimachinery/apis/meta/v1.Object 62 | // representation of the AWSResource 63 | func (r *resource) MetaObject() metav1.Object { 64 | return r.ko.GetObjectMeta() 65 | } 66 | 67 | // Conditions returns the ACK Conditions collection for the AWSResource 68 | func (r *resource) Conditions() []*ackv1alpha1.Condition { 69 | return r.ko.Status.Conditions 70 | } 71 | 72 | // ReplaceConditions sets the Conditions status field for the resource 73 | func (r *resource) ReplaceConditions(conditions []*ackv1alpha1.Condition) { 74 | r.ko.Status.Conditions = conditions 75 | } 76 | 77 | // SetObjectMeta sets the ObjectMeta field for the resource 78 | func (r *resource) SetObjectMeta(meta metav1.ObjectMeta) { 79 | r.ko.ObjectMeta = meta 80 | } 81 | 82 | // SetStatus will set the Status field for the resource 83 | func (r *resource) SetStatus(desired acktypes.AWSResource) { 84 | r.ko.Status = desired.(*resource).ko.Status 85 | } 86 | 87 | // SetIdentifiers sets the Spec or Status field that is referenced as the unique 88 | // resource identifier 89 | func (r *resource) SetIdentifiers(identifier *ackv1alpha1.AWSIdentifiers) error { 90 | if identifier.NameOrID == "" { 91 | return ackerrors.MissingNameIdentifier 92 | } 93 | r.ko.Spec.GlobalTableName = &identifier.NameOrID 94 | 95 | return nil 96 | } 97 | 98 | // PopulateResourceFromAnnotation populates the fields passed from adoption annotation 99 | func (r *resource) PopulateResourceFromAnnotation(fields map[string]string) error { 100 | f0, ok := fields["globalTableName"] 101 | if !ok { 102 | return ackerrors.NewTerminalError(fmt.Errorf("required field missing: globalTableName")) 103 | } 104 | r.ko.Spec.GlobalTableName = &f0 105 | 106 | return nil 107 | } 108 | 109 | // DeepCopy will return a copy of the resource 110 | func (r *resource) DeepCopy() acktypes.AWSResource { 111 | koCopy := r.ko.DeepCopy() 112 | return &resource{koCopy} 113 | } 114 | -------------------------------------------------------------------------------- /pkg/resource/table/tags.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by ack-generate. DO NOT EDIT. 15 | 16 | package table 17 | 18 | import ( 19 | "slices" 20 | "strings" 21 | 22 | acktags "github.com/aws-controllers-k8s/runtime/pkg/tags" 23 | 24 | svcapitypes "github.com/aws-controllers-k8s/dynamodb-controller/apis/v1alpha1" 25 | ) 26 | 27 | var ( 28 | _ = svcapitypes.Table{} 29 | _ = acktags.NewTags() 30 | ) 31 | 32 | // convertToOrderedACKTags converts the tags parameter into 'acktags.Tags' shape. 33 | // This method helps in creating the hub(acktags.Tags) for merging 34 | // default controller tags with existing resource tags. It also returns a slice 35 | // of keys maintaining the original key Order when the tags are a list 36 | func convertToOrderedACKTags(tags []*svcapitypes.Tag) (acktags.Tags, []string) { 37 | result := acktags.NewTags() 38 | keyOrder := []string{} 39 | 40 | if len(tags) == 0 { 41 | return result, keyOrder 42 | } 43 | for _, t := range tags { 44 | if t.Key != nil { 45 | keyOrder = append(keyOrder, *t.Key) 46 | if t.Value != nil { 47 | result[*t.Key] = *t.Value 48 | } else { 49 | result[*t.Key] = "" 50 | } 51 | } 52 | } 53 | 54 | return result, keyOrder 55 | } 56 | 57 | // fromACKTags converts the tags parameter into []*svcapitypes.Tag shape. 58 | // This method helps in setting the tags back inside AWSResource after merging 59 | // default controller tags with existing resource tags. When a list, 60 | // it maintains the order from original 61 | func fromACKTags(tags acktags.Tags, keyOrder []string) []*svcapitypes.Tag { 62 | result := []*svcapitypes.Tag{} 63 | 64 | for _, k := range keyOrder { 65 | v, ok := tags[k] 66 | if ok { 67 | tag := svcapitypes.Tag{Key: &k, Value: &v} 68 | result = append(result, &tag) 69 | delete(tags, k) 70 | } 71 | } 72 | for k, v := range tags { 73 | tag := svcapitypes.Tag{Key: &k, Value: &v} 74 | result = append(result, &tag) 75 | } 76 | 77 | return result 78 | } 79 | 80 | // ignoreSystemTags ignores tags that have keys that start with "aws:" 81 | // and systemTags defined on startup via the --resource-tags flag, 82 | // to avoid patching them to the resourceSpec. 83 | // Eg. resources created with cloudformation have tags that cannot be 84 | // removed by an ACK controller 85 | func ignoreSystemTags(tags acktags.Tags, systemTags []string) { 86 | for k := range tags { 87 | if strings.HasPrefix(k, "aws:") || 88 | slices.Contains(systemTags, k) { 89 | delete(tags, k) 90 | } 91 | } 92 | } 93 | 94 | // syncAWSTags ensures AWS-managed tags (prefixed with "aws:") from the latest resource state 95 | // are preserved in the desired state. This prevents the controller from attempting to 96 | // modify AWS-managed tags, which would result in an error. 97 | // 98 | // AWS-managed tags are automatically added by AWS services (e.g., CloudFormation, Service Catalog) 99 | // and cannot be modified or deleted through normal tag operations. Common examples include: 100 | // - aws:cloudformation:stack-name 101 | // - aws:servicecatalog:productArn 102 | // 103 | // Parameters: 104 | // - a: The target Tags map to be updated (typically desired state) 105 | // - b: The source Tags map containing AWS-managed tags (typically latest state) 106 | // 107 | // Example: 108 | // 109 | // latest := Tags{"aws:cloudformation:stack-name": "my-stack", "environment": "prod"} 110 | // desired := Tags{"environment": "dev"} 111 | // SyncAWSTags(desired, latest) 112 | // desired now contains {"aws:cloudformation:stack-name": "my-stack", "environment": "dev"} 113 | func syncAWSTags(a acktags.Tags, b acktags.Tags) { 114 | for k := range b { 115 | if strings.HasPrefix(k, "aws:") { 116 | a[k] = b[k] 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /pkg/resource/backup/resource.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by ack-generate. DO NOT EDIT. 15 | 16 | package backup 17 | 18 | import ( 19 | "fmt" 20 | 21 | ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" 22 | ackerrors "github.com/aws-controllers-k8s/runtime/pkg/errors" 23 | acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" 24 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 | rtclient "sigs.k8s.io/controller-runtime/pkg/client" 26 | 27 | svcapitypes "github.com/aws-controllers-k8s/dynamodb-controller/apis/v1alpha1" 28 | ) 29 | 30 | // Hack to avoid import errors during build... 31 | var ( 32 | _ = &ackerrors.MissingNameIdentifier 33 | ) 34 | 35 | // resource implements the `aws-controller-k8s/runtime/pkg/types.AWSResource` 36 | // interface 37 | type resource struct { 38 | // The Kubernetes-native CR representing the resource 39 | ko *svcapitypes.Backup 40 | } 41 | 42 | // Identifiers returns an AWSResourceIdentifiers object containing various 43 | // identifying information, including the AWS account ID that owns the 44 | // resource, the resource's AWS Resource Name (ARN) 45 | func (r *resource) Identifiers() acktypes.AWSResourceIdentifiers { 46 | return &resourceIdentifiers{r.ko.Status.ACKResourceMetadata} 47 | } 48 | 49 | // IsBeingDeleted returns true if the Kubernetes resource has a non-zero 50 | // deletion timestamp 51 | func (r *resource) IsBeingDeleted() bool { 52 | return !r.ko.DeletionTimestamp.IsZero() 53 | } 54 | 55 | // RuntimeObject returns the Kubernetes apimachinery/runtime representation of 56 | // the AWSResource 57 | func (r *resource) RuntimeObject() rtclient.Object { 58 | return r.ko 59 | } 60 | 61 | // MetaObject returns the Kubernetes apimachinery/apis/meta/v1.Object 62 | // representation of the AWSResource 63 | func (r *resource) MetaObject() metav1.Object { 64 | return r.ko.GetObjectMeta() 65 | } 66 | 67 | // Conditions returns the ACK Conditions collection for the AWSResource 68 | func (r *resource) Conditions() []*ackv1alpha1.Condition { 69 | return r.ko.Status.Conditions 70 | } 71 | 72 | // ReplaceConditions sets the Conditions status field for the resource 73 | func (r *resource) ReplaceConditions(conditions []*ackv1alpha1.Condition) { 74 | r.ko.Status.Conditions = conditions 75 | } 76 | 77 | // SetObjectMeta sets the ObjectMeta field for the resource 78 | func (r *resource) SetObjectMeta(meta metav1.ObjectMeta) { 79 | r.ko.ObjectMeta = meta 80 | } 81 | 82 | // SetStatus will set the Status field for the resource 83 | func (r *resource) SetStatus(desired acktypes.AWSResource) { 84 | r.ko.Status = desired.(*resource).ko.Status 85 | } 86 | 87 | // SetIdentifiers sets the Spec or Status field that is referenced as the unique 88 | // resource identifier 89 | func (r *resource) SetIdentifiers(identifier *ackv1alpha1.AWSIdentifiers) error { 90 | if r.ko.Status.ACKResourceMetadata == nil { 91 | r.ko.Status.ACKResourceMetadata = &ackv1alpha1.ResourceMetadata{} 92 | } 93 | r.ko.Status.ACKResourceMetadata.ARN = identifier.ARN 94 | 95 | return nil 96 | } 97 | 98 | // PopulateResourceFromAnnotation populates the fields passed from adoption annotation 99 | func (r *resource) PopulateResourceFromAnnotation(fields map[string]string) error { 100 | resourceARN, ok := fields["arn"] 101 | if !ok { 102 | return ackerrors.NewTerminalError(fmt.Errorf("required field missing: arn")) 103 | } 104 | 105 | if r.ko.Status.ACKResourceMetadata == nil { 106 | r.ko.Status.ACKResourceMetadata = &ackv1alpha1.ResourceMetadata{} 107 | } 108 | arn := ackv1alpha1.AWSResourceName(resourceARN) 109 | r.ko.Status.ACKResourceMetadata.ARN = &arn 110 | 111 | return nil 112 | } 113 | 114 | // DeepCopy will return a copy of the resource 115 | func (r *resource) DeepCopy() acktypes.AWSResource { 116 | koCopy := r.ko.DeepCopy() 117 | return &resource{koCopy} 118 | } 119 | -------------------------------------------------------------------------------- /test/e2e/tests/test_backup.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | # not use this file except in compliance with the License. A copy of the 5 | # License is located at 6 | # 7 | # http://aws.amazon.com/apache2.0/ 8 | # 9 | # or in the "license" file accompanying this file. This file is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | # express or implied. See the License for the specific language governing 12 | # permissions and limitations under the License. 13 | 14 | import boto3 15 | import pytest 16 | import time 17 | import logging 18 | from typing import Dict, Tuple 19 | 20 | from acktest.resources import random_suffix_name 21 | from acktest.k8s import resource as k8s 22 | from acktest.aws.identity import get_region 23 | from e2e import ( 24 | service_marker, 25 | CRD_GROUP, 26 | CRD_VERSION, 27 | load_dynamodb_resource, 28 | wait_for_cr_status, 29 | ) 30 | from e2e.replacement_values import REPLACEMENT_VALUES 31 | from e2e import condition 32 | 33 | RESOURCE_PLURAL = "backups" 34 | 35 | DELETE_WAIT_AFTER_SECONDS = 10 36 | 37 | @pytest.fixture(scope="module") 38 | def dynamodb_table(): 39 | resource_name = random_suffix_name("table-forbackup", 32) 40 | 41 | replacements = REPLACEMENT_VALUES.copy() 42 | replacements["TABLE_NAME"] = resource_name 43 | 44 | # load resource 45 | resource_data = load_dynamodb_resource( 46 | "table_local_secondary_indexes", 47 | additional_replacements=replacements, 48 | ) 49 | 50 | table_reference = k8s.CustomResourceReference( 51 | CRD_GROUP, CRD_VERSION, "tables", 52 | resource_name, namespace="default", 53 | ) 54 | 55 | # Create table 56 | k8s.create_custom_resource(table_reference, resource_data) 57 | table_resource = k8s.wait_resource_consumed_by_controller(table_reference) 58 | 59 | assert table_resource is not None 60 | assert k8s.get_resource_exists(table_reference) 61 | 62 | wait_for_cr_status( 63 | table_reference, 64 | "tableStatus", 65 | "ACTIVE", 66 | 90, 67 | 3, 68 | ) 69 | 70 | yield (table_reference, table_resource) 71 | try: 72 | _, deleted = k8s.delete_custom_resource(table_reference, wait_periods=3, period_length=10) 73 | assert deleted 74 | except: 75 | pass 76 | 77 | @service_marker 78 | @pytest.mark.canary 79 | class TestBackup: 80 | def get_backup(self, dynamodb_client, backup_arn: str) -> dict: 81 | try: 82 | resp = dynamodb_client.describe_backup( 83 | BackupArn=backup_arn, 84 | ) 85 | return resp["BackupDescription"] 86 | 87 | except Exception as e: 88 | logging.debug(e) 89 | return None 90 | 91 | def backup_exists(self, dynamodb_client, backup_arn: str) -> bool: 92 | return self.get_backup(dynamodb_client, backup_arn) is not None 93 | 94 | def test_smoke(self, dynamodb_client, dynamodb_table): 95 | (_, table_resource) = dynamodb_table 96 | resource_name = random_suffix_name("backup", 32) 97 | table_name = table_resource["spec"]["tableName"] 98 | 99 | replacements = REPLACEMENT_VALUES.copy() 100 | replacements["TABLE_NAME"] = table_name 101 | replacements["BACKUP_NAME"] = resource_name 102 | 103 | # Load Backup CR 104 | resource_data = load_dynamodb_resource( 105 | "backup", 106 | additional_replacements=replacements, 107 | ) 108 | logging.debug(resource_data) 109 | 110 | # Create k8s resource 111 | ref = k8s.CustomResourceReference( 112 | CRD_GROUP, CRD_VERSION, RESOURCE_PLURAL, 113 | resource_name, namespace="default", 114 | ) 115 | k8s.create_custom_resource(ref, resource_data) 116 | cr = k8s.wait_resource_consumed_by_controller(ref) 117 | 118 | assert cr is not None 119 | assert k8s.get_resource_exists(ref) 120 | 121 | wait_for_cr_status( 122 | ref, 123 | "backupStatus", 124 | "AVAILABLE", 125 | 20, 126 | 5, 127 | ) 128 | condition.assert_synced(ref) 129 | 130 | backupArn = k8s.get_resource_arn(cr) 131 | # Check DynamoDB Backup exists 132 | exists = self.backup_exists(dynamodb_client, backupArn) 133 | assert exists 134 | 135 | # Delete k8s resource 136 | _, deleted = k8s.delete_custom_resource(ref) 137 | assert deleted is True 138 | 139 | time.sleep(DELETE_WAIT_AFTER_SECONDS) 140 | 141 | # Check DynamoDB Backup doesn't exists 142 | exists = self.backup_exists(dynamodb_client, backupArn) 143 | assert not exists 144 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/aws-controllers-k8s/dynamodb-controller 2 | 3 | go 1.24.0 4 | 5 | toolchain go1.24.1 6 | 7 | require ( 8 | github.com/aws-controllers-k8s/kms-controller v1.0.21 9 | github.com/aws-controllers-k8s/runtime v0.56.0 10 | github.com/aws/aws-sdk-go v1.49.0 11 | github.com/aws/aws-sdk-go-v2 v1.36.0 12 | github.com/aws/aws-sdk-go-v2/service/dynamodb v1.39.8 13 | github.com/aws/smithy-go v1.22.2 14 | github.com/go-logr/logr v1.4.2 15 | github.com/micahhausler/aws-iam-policy v0.4.2 16 | github.com/spf13/pflag v1.0.5 17 | github.com/stretchr/testify v1.9.0 18 | k8s.io/api v0.32.1 19 | k8s.io/apimachinery v0.32.1 20 | k8s.io/client-go v0.32.1 21 | sigs.k8s.io/controller-runtime v0.20.4 22 | ) 23 | 24 | // Temporary fix for github.com/micahhausler/aws-iam-policy. Awaiting for a-hilaly to send 25 | // a PR to micahhausler/aws-iam-policy to build Equal() method for PolicyDocument struct. 26 | replace github.com/micahhausler/aws-iam-policy => github.com/a-hilaly/aws-iam-policy v0.0.0-20231121054900-2c56e839ca53 27 | 28 | require ( 29 | github.com/aws/aws-sdk-go-v2/config v1.28.6 // indirect 30 | github.com/aws/aws-sdk-go-v2/credentials v1.17.47 // indirect 31 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.21 // indirect 32 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.31 // indirect 33 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.31 // indirect 34 | github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect 35 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 // indirect 36 | github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.12 // indirect 37 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.6 // indirect 38 | github.com/aws/aws-sdk-go-v2/service/sso v1.24.7 // indirect 39 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.6 // indirect 40 | github.com/aws/aws-sdk-go-v2/service/sts v1.33.2 // indirect 41 | github.com/beorn7/perks v1.0.1 // indirect 42 | github.com/cenkalti/backoff/v4 v4.3.0 // indirect 43 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 44 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 45 | github.com/emicklei/go-restful/v3 v3.11.0 // indirect 46 | github.com/evanphx/json-patch/v5 v5.9.11 // indirect 47 | github.com/fsnotify/fsnotify v1.7.0 // indirect 48 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 49 | github.com/go-logr/zapr v1.3.0 // indirect 50 | github.com/go-openapi/jsonpointer v0.21.0 // indirect 51 | github.com/go-openapi/jsonreference v0.20.2 // indirect 52 | github.com/go-openapi/swag v0.23.0 // indirect 53 | github.com/gogo/protobuf v1.3.2 // indirect 54 | github.com/golang/protobuf v1.5.4 // indirect 55 | github.com/google/btree v1.1.3 // indirect 56 | github.com/google/gnostic-models v0.6.8 // indirect 57 | github.com/google/go-cmp v0.6.0 // indirect 58 | github.com/google/gofuzz v1.2.0 // indirect 59 | github.com/google/uuid v1.6.0 // indirect 60 | github.com/itchyny/gojq v0.12.6 // indirect 61 | github.com/itchyny/timefmt-go v0.1.3 // indirect 62 | github.com/jaypipes/envutil v1.0.0 // indirect 63 | github.com/josharian/intern v1.0.0 // indirect 64 | github.com/json-iterator/go v1.1.12 // indirect 65 | github.com/mailru/easyjson v0.7.7 // indirect 66 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 67 | github.com/modern-go/reflect2 v1.0.2 // indirect 68 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 69 | github.com/pkg/errors v0.9.1 // indirect 70 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 71 | github.com/prometheus/client_golang v1.19.1 // indirect 72 | github.com/prometheus/client_model v0.6.1 // indirect 73 | github.com/prometheus/common v0.55.0 // indirect 74 | github.com/prometheus/procfs v0.15.1 // indirect 75 | github.com/samber/lo v1.37.0 // indirect 76 | github.com/x448/float16 v0.8.4 // indirect 77 | go.uber.org/multierr v1.11.0 // indirect 78 | go.uber.org/zap v1.27.0 // indirect 79 | golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect 80 | golang.org/x/net v0.38.0 // indirect 81 | golang.org/x/oauth2 v0.27.0 // indirect 82 | golang.org/x/sync v0.12.0 // indirect 83 | golang.org/x/sys v0.31.0 // indirect 84 | golang.org/x/term v0.30.0 // indirect 85 | golang.org/x/text v0.23.0 // indirect 86 | golang.org/x/time v0.7.0 // indirect 87 | gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect 88 | google.golang.org/protobuf v1.35.1 // indirect 89 | gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect 90 | gopkg.in/inf.v0 v0.9.1 // indirect 91 | gopkg.in/yaml.v3 v3.0.1 // indirect 92 | k8s.io/apiextensions-apiserver v0.32.1 // indirect 93 | k8s.io/klog/v2 v2.130.1 // indirect 94 | k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect 95 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect 96 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect 97 | sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect 98 | sigs.k8s.io/yaml v1.4.0 // indirect 99 | ) 100 | -------------------------------------------------------------------------------- /test/e2e/tests/test_global_table.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | # not use this file except in compliance with the License. A copy of the 5 | # License is located at 6 | # 7 | # http://aws.amazon.com/apache2.0/ 8 | # 9 | # or in the "license" file accompanying this file. This file is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | # express or implied. See the License for the specific language governing 12 | # permissions and limitations under the License. 13 | 14 | 15 | 16 | import boto3 17 | import pytest 18 | import json 19 | import time 20 | import logging 21 | from typing import Dict, Tuple 22 | 23 | from acktest.resources import random_suffix_name 24 | from acktest.k8s import resource as k8s 25 | from acktest.aws.identity import get_region 26 | from e2e import ( 27 | service_marker, 28 | CRD_GROUP, 29 | CRD_VERSION, 30 | load_dynamodb_resource, 31 | wait_for_cr_status, 32 | ) 33 | from e2e import condition 34 | from e2e.replacement_values import REPLACEMENT_VALUES 35 | 36 | RESOURCE_PLURAL = "globaltables" 37 | 38 | CREATE_WAIT_AFTER_SECONDS = 10 39 | DELETE_WAIT_AFTER_SECONDS = 30 40 | 41 | @pytest.fixture(scope="module") 42 | def dynamodb_table(): 43 | resource_name = random_suffix_name("table", 32) 44 | 45 | replacements = REPLACEMENT_VALUES.copy() 46 | replacements["TABLE_NAME"] = resource_name 47 | 48 | # load resource 49 | resource_data = load_dynamodb_resource( 50 | "table_local_secondary_indexes", 51 | additional_replacements=replacements, 52 | ) 53 | 54 | table_reference = k8s.CustomResourceReference( 55 | CRD_GROUP, CRD_VERSION, "tables", 56 | resource_name, namespace="default", 57 | ) 58 | 59 | # Create table 60 | k8s.create_custom_resource(table_reference, resource_data) 61 | table_resource = k8s.wait_resource_consumed_by_controller(table_reference) 62 | 63 | assert table_resource is not None 64 | assert k8s.get_resource_exists(table_reference) 65 | 66 | wait_for_cr_status( 67 | table_reference, 68 | "tableStatus", 69 | "ACTIVE", 70 | 10, 71 | 5, 72 | ) 73 | 74 | yield (table_reference, table_resource) 75 | try: 76 | _, deleted = k8s.delete_custom_resource(table_reference, wait_periods=3, period_length=10) 77 | assert deleted 78 | except: 79 | pass 80 | 81 | @service_marker 82 | @pytest.mark.canary 83 | class TestGlobalTable: 84 | 85 | def get_global_table(self, dynamodb_client, global_table_name: str) -> dict: 86 | try: 87 | resp = dynamodb_client.describe_global_table( 88 | GlobalTableName=global_table_name, 89 | ) 90 | return resp["GlobalTableDescription"] 91 | 92 | except Exception as e: 93 | logging.debug(e) 94 | return None 95 | 96 | def global_table_exists(self, dynamodb_client, global_table_name: str) -> bool: 97 | return self.get_global_table(dynamodb_client, global_table_name) is not None 98 | 99 | def test_smoke(self, dynamodb_client, dynamodb_table): 100 | (_, table_resource) = dynamodb_table 101 | 102 | # Global Tables must have the same name as dynamodb Tables 103 | global_table_name = table_resource["spec"]["tableName"] 104 | 105 | replacements = REPLACEMENT_VALUES.copy() 106 | replacements["REGION_NAME"] = get_region() 107 | replacements["TABLE_NAME"] = global_table_name 108 | replacements["GLOBAL_TABLE_NAME"] = global_table_name 109 | 110 | # Load GLobal Table CR 111 | resource_data = load_dynamodb_resource( 112 | "global_table", 113 | additional_replacements=replacements, 114 | ) 115 | logging.debug(resource_data) 116 | 117 | # Create k8s resource 118 | ref = k8s.CustomResourceReference( 119 | CRD_GROUP, CRD_VERSION, RESOURCE_PLURAL, 120 | global_table_name, namespace="default", 121 | ) 122 | k8s.create_custom_resource(ref, resource_data) 123 | cr = k8s.wait_resource_consumed_by_controller(ref) 124 | 125 | assert cr is not None 126 | assert k8s.get_resource_exists(ref) 127 | 128 | wait_for_cr_status( 129 | ref, 130 | "globalTableStatus", 131 | "ACTIVE", 132 | 10, 133 | 5, 134 | ) 135 | condition.assert_synced(ref) 136 | 137 | # Check DynamoDB Global Table exists 138 | exists = self.global_table_exists(dynamodb_client, global_table_name) 139 | assert exists 140 | 141 | _, deleted = k8s.delete_custom_resource(ref) 142 | assert deleted is True 143 | 144 | time.sleep(DELETE_WAIT_AFTER_SECONDS) 145 | 146 | exists = self.global_table_exists(dynamodb_client, global_table_name) 147 | assert not exists 148 | -------------------------------------------------------------------------------- /apis/v1alpha1/backup.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by ack-generate. DO NOT EDIT. 15 | 16 | package v1alpha1 17 | 18 | import ( 19 | ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | ) 22 | 23 | // BackupSpec defines the desired state of Backup. 24 | type BackupSpec struct { 25 | 26 | // Specified name for the backup. 27 | // 28 | // Regex Pattern: `^[a-zA-Z0-9_.-]+$` 29 | // +kubebuilder:validation:Required 30 | BackupName *string `json:"backupName"` 31 | // The name of the table. You can also provide the Amazon Resource Name (ARN) 32 | // of the table in this parameter. 33 | // +kubebuilder:validation:Required 34 | TableName *string `json:"tableName"` 35 | } 36 | 37 | // BackupStatus defines the observed state of Backup 38 | type BackupStatus struct { 39 | // All CRs managed by ACK have a common `Status.ACKResourceMetadata` member 40 | // that is used to contain resource sync state, account ownership, 41 | // constructed ARN for the resource 42 | // +kubebuilder:validation:Optional 43 | ACKResourceMetadata *ackv1alpha1.ResourceMetadata `json:"ackResourceMetadata"` 44 | // All CRs managed by ACK have a common `Status.Conditions` member that 45 | // contains a collection of `ackv1alpha1.Condition` objects that describe 46 | // the various terminal states of the CR and its backend AWS service API 47 | // resource 48 | // +kubebuilder:validation:Optional 49 | Conditions []*ackv1alpha1.Condition `json:"conditions"` 50 | // Time at which the backup was created. This is the request time of the backup. 51 | // +kubebuilder:validation:Optional 52 | BackupCreationDateTime *metav1.Time `json:"backupCreationDateTime,omitempty"` 53 | // Time at which the automatic on-demand backup created by DynamoDB will expire. 54 | // This SYSTEM on-demand backup expires automatically 35 days after its creation. 55 | // +kubebuilder:validation:Optional 56 | BackupExpiryDateTime *metav1.Time `json:"backupExpiryDateTime,omitempty"` 57 | // Size of the backup in bytes. DynamoDB updates this value approximately every 58 | // six hours. Recent changes might not be reflected in this value. 59 | // +kubebuilder:validation:Optional 60 | BackupSizeBytes *int64 `json:"backupSizeBytes,omitempty"` 61 | // Backup can be in one of the following states: CREATING, ACTIVE, DELETED. 62 | // +kubebuilder:validation:Optional 63 | BackupStatus *string `json:"backupStatus,omitempty"` 64 | // BackupType: 65 | // 66 | // * USER - You create and manage these using the on-demand backup feature. 67 | // 68 | // * SYSTEM - If you delete a table with point-in-time recovery enabled, 69 | // a SYSTEM backup is automatically created and is retained for 35 days (at 70 | // no additional cost). System backups allow you to restore the deleted table 71 | // to the state it was in just before the point of deletion. 72 | // 73 | // * AWS_BACKUP - On-demand backup created by you from Backup service. 74 | // +kubebuilder:validation:Optional 75 | BackupType *string `json:"backupType,omitempty"` 76 | } 77 | 78 | // Backup is the Schema for the Backups API 79 | // +kubebuilder:object:root=true 80 | // +kubebuilder:subresource:status 81 | // +kubebuilder:printcolumn:name="ARN",type=string,priority=1,JSONPath=`.status.ackResourceMetadata.arn` 82 | // +kubebuilder:printcolumn:name="SIZE",type=string,priority=0,JSONPath=`.status.backupSize` 83 | // +kubebuilder:printcolumn:name="STATUS",type=string,priority=0,JSONPath=`.status.backupStatus` 84 | // +kubebuilder:printcolumn:name="TABLENAME",type=string,priority=0,JSONPath=`.spec.tableName` 85 | // +kubebuilder:printcolumn:name="TYPE",type=string,priority=0,JSONPath=`.status.backupType` 86 | // +kubebuilder:printcolumn:name="Synced",type="string",priority=0,JSONPath=".status.conditions[?(@.type==\"ACK.ResourceSynced\")].status" 87 | // +kubebuilder:printcolumn:name="Age",type="date",priority=0,JSONPath=".metadata.creationTimestamp" 88 | type Backup struct { 89 | metav1.TypeMeta `json:",inline"` 90 | metav1.ObjectMeta `json:"metadata,omitempty"` 91 | Spec BackupSpec `json:"spec,omitempty"` 92 | Status BackupStatus `json:"status,omitempty"` 93 | } 94 | 95 | // BackupList contains a list of Backup 96 | // +kubebuilder:object:root=true 97 | type BackupList struct { 98 | metav1.TypeMeta `json:",inline"` 99 | metav1.ListMeta `json:"metadata,omitempty"` 100 | Items []Backup `json:"items"` 101 | } 102 | 103 | func init() { 104 | SchemeBuilder.Register(&Backup{}, &BackupList{}) 105 | } 106 | -------------------------------------------------------------------------------- /test/e2e/condition.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | # not use this file except in compliance with the License. A copy of the 5 | # License is located at 6 | # 7 | # http://aws.amazon.com/apache2.0/ 8 | # 9 | # or in the "license" file accompanying this file. This file is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | # express or implied. See the License for the specific language governing 12 | # permissions and limitations under the License. 13 | 14 | """Utility functions to help processing Kubernetes resource conditions""" 15 | 16 | import pytest 17 | 18 | from acktest.k8s import resource 19 | 20 | CONDITION_TYPE_ADOPTED = "ACK.Adopted" 21 | CONDITION_TYPE_RESOURCE_SYNCED = "ACK.ResourceSynced" 22 | CONDITION_TYPE_TERMINAL = "ACK.Terminal" 23 | CONDITION_TYPE_RECOVERABLE = "ACK.Recoverable" 24 | CONDITION_TYPE_ADVISORY = "ACK.Advisory" 25 | CONDITION_TYPE_LATE_INITIALIZED = "ACK.LateInitialized" 26 | CONDITION_TYPE_REFERENCES_RESOLVED = "ACK.ReferencesResolved" 27 | 28 | 29 | def assert_type_status( 30 | ref: resource.CustomResourceReference, 31 | cond_type_match: str = CONDITION_TYPE_RESOURCE_SYNCED, 32 | cond_status_match: bool = True, 33 | ): 34 | """Asserts that the supplied resource has a condition of type 35 | ACK.ResourceSynced and that the Status of this condition is True. 36 | 37 | Usage: 38 | from acktest.k8s import resource 39 | from acktest.k8s import condition 40 | 41 | ref = resource.CustomResourceReference( 42 | CRD_GROUP, CRD_VERSION, RESOURCE_PLURAL, 43 | db_cluster_id, namespace="default", 44 | ) 45 | resource.create_custom_resource(ref, resource_data) 46 | resource.wait_resource_consumed_by_controller(ref) 47 | condition.assert_type_status( 48 | ref, 49 | condition.CONDITION_TYPE_RESOURCE_SYNCED, 50 | False) 51 | 52 | Raises: 53 | pytest.fail when condition of the specified type is not found or is not 54 | in the supplied status. 55 | """ 56 | cond = resource.get_resource_condition(ref, cond_type_match) 57 | if cond is None: 58 | msg = (f"Failed to find {cond_type_match} condition in " 59 | f"resource {ref}") 60 | pytest.fail(msg) 61 | 62 | cond_status = cond.get('status', None) 63 | if str(cond_status) != str(cond_status_match): 64 | msg = (f"Expected {cond_type_match} condition to " 65 | f"have status {cond_status_match} but found {cond_status}") 66 | pytest.fail(msg) 67 | 68 | 69 | def assert_synced_status( 70 | ref: resource.CustomResourceReference, 71 | cond_status_match: bool, 72 | ): 73 | """Asserts that the supplied resource has a condition of type 74 | ACK.ResourceSynced and that the Status of this condition is True. 75 | 76 | Usage: 77 | from acktest.k8s import resource 78 | from acktest.k8s import condition 79 | 80 | ref = resource.CustomResourceReference( 81 | CRD_GROUP, CRD_VERSION, RESOURCE_PLURAL, 82 | db_cluster_id, namespace="default", 83 | ) 84 | resource.create_custom_resource(ref, resource_data) 85 | resource.wait_resource_consumed_by_controller(ref) 86 | condition.assert_synced_status(ref, False) 87 | 88 | Raises: 89 | pytest.fail when ACK.ResourceSynced condition is not found or is not in 90 | a True status. 91 | """ 92 | assert_type_status(ref, CONDITION_TYPE_RESOURCE_SYNCED, cond_status_match) 93 | 94 | 95 | def assert_synced(ref: resource.CustomResourceReference): 96 | """Asserts that the supplied resource has a condition of type 97 | ACK.ResourceSynced and that the Status of this condition is True. 98 | 99 | Usage: 100 | from acktest.k8s import resource 101 | from acktest.k8s import condition 102 | 103 | ref = resource.CustomResourceReference( 104 | CRD_GROUP, CRD_VERSION, RESOURCE_PLURAL, 105 | db_cluster_id, namespace="default", 106 | ) 107 | resource.create_custom_resource(ref, resource_data) 108 | resource.wait_resource_consumed_by_controller(ref) 109 | condition.assert_synced(ref) 110 | 111 | Raises: 112 | pytest.fail when ACK.ResourceSynced condition is not found or is not in 113 | a True status. 114 | """ 115 | return assert_synced_status(ref, True) 116 | 117 | 118 | def assert_not_synced(ref: resource.CustomResourceReference): 119 | """Asserts that the supplied resource has a condition of type 120 | ACK.ResourceSynced and that the Status of this condition is False. 121 | 122 | Usage: 123 | from acktest.k8s import resource 124 | from acktest.k8s import condition 125 | 126 | ref = resource.CustomResourceReference( 127 | CRD_GROUP, CRD_VERSION, RESOURCE_PLURAL, 128 | db_cluster_id, namespace="default", 129 | ) 130 | resource.create_custom_resource(ref, resource_data) 131 | resource.wait_resource_consumed_by_controller(ref) 132 | condition.assert_not_synced(ref) 133 | 134 | Raises: 135 | pytest.fail when ACK.ResourceSynced condition is not found or is not in 136 | a False status. 137 | """ 138 | return assert_synced_status(ref, False) 139 | 140 | -------------------------------------------------------------------------------- /generator.yaml: -------------------------------------------------------------------------------- 1 | ignore: 2 | shape_names: 3 | - OnDemandThroughput 4 | - WarmThroughput 5 | - GlobalSecondaryIndexWarmThroughputDescription 6 | - OnDemandThroughputOverride 7 | - TableWarmThroughputDescription 8 | field_paths: 9 | # Replica of Spec.BillingMode 10 | - TableDescription.BillingModeSummary 11 | # Replica of Spec.SSESpecification 12 | - TableDescription.SSEDescription 13 | - TableDescription.TableClassSummary 14 | - CreateTableInput.WarmThroughput 15 | operations: 16 | UpdateGlobalTable: 17 | operation_type: 18 | - Update 19 | - Delete 20 | resource_name: GlobalTable 21 | DescribeBackup: 22 | output_wrapper_field_path: BackupDescription.BackupDetails 23 | primary_identifier_field_name: BackupArn 24 | resources: 25 | Table: 26 | fields: 27 | TableReplicas: 28 | custom_field: 29 | list_of: CreateReplicationGroupMemberAction 30 | compare: 31 | is_ignored: true 32 | ResourcePolicy: 33 | from: 34 | operation: PutResourcePolicy 35 | path: Policy 36 | compare: 37 | is_ignored: true 38 | GlobalSecondaryIndexesDescriptions: 39 | custom_field: 40 | list_of: GlobalSecondaryIndexDescription 41 | is_read_only: true 42 | TimeToLive: 43 | from: 44 | operation: UpdateTimeToLive 45 | path: TimeToLiveSpecification 46 | ContinuousBackups: 47 | is_required: false 48 | from: 49 | operation: UpdateContinuousBackups 50 | path: PointInTimeRecoverySpecification 51 | AttributeDefinitions: 52 | compare: 53 | is_ignored: true 54 | # non modifiable field 55 | KeySchema: 56 | is_immutable: true 57 | compare: 58 | is_ignored: true 59 | GlobalSecondaryIndexes: 60 | compare: 61 | is_ignored: true 62 | Tags: 63 | compare: 64 | is_ignored: true 65 | # non modifiable field 66 | LocalSecondaryIndexes: 67 | is_immutable: true 68 | compare: 69 | is_ignored: true 70 | SSESpecification: 71 | compare: 72 | is_ignored: true 73 | SSESpecification.KMSMasterKeyID: 74 | references: 75 | service_name: kms 76 | resource: Key 77 | path: Status.ACKResourceMetadata.ARN 78 | ContributorInsights: 79 | from: 80 | operation: UpdateContributorInsights 81 | path: ContributorInsightsAction 82 | compare: 83 | is_ignored: true 84 | exceptions: 85 | errors: 86 | 404: 87 | code: ResourceNotFoundException 88 | terminal_codes: 89 | - InvalidParameter 90 | - ValidationException 91 | update_operation: 92 | custom_method_name: customUpdateTable 93 | hooks: 94 | delta_pre_compare: 95 | code: customPreCompare(delta, a, b) 96 | sdk_create_post_set_output: 97 | template_path: hooks/table/sdk_create_post_set_output.go.tpl 98 | sdk_read_one_post_set_output: 99 | template_path: hooks/table/sdk_read_one_post_set_output.go.tpl 100 | sdk_update_pre_build_request: 101 | template_path: hooks/table/sdk_update_pre_build_request.go.tpl 102 | sdk_delete_pre_build_request: 103 | template_path: hooks/table/sdk_delete_pre_build_request.go.tpl 104 | synced: 105 | when: 106 | - path: Status.TableStatus 107 | in: 108 | - ACTIVE 109 | - ARCHIVED 110 | print: 111 | add_age_column: true 112 | add_synced_column: true 113 | additional_columns: 114 | - name: ARN 115 | json_path: .status.ackResourceMetadata.arn 116 | type: string 117 | priority: 1 118 | - name: CLASS 119 | json_path: .spec.tableClass 120 | type: string 121 | priority: 0 122 | - name: STATUS 123 | json_path: .status.tableStatus 124 | type: string 125 | GlobalTable: 126 | exceptions: 127 | errors: 128 | 404: 129 | code: GlobalTableNotFoundException 130 | hooks: 131 | sdk_delete_post_build_request: 132 | code: customSetDeleteInput(r, input) 133 | tags: 134 | ignore: true 135 | synced: 136 | when: 137 | - path: Status.GlobalTableStatus 138 | in: 139 | - ACTIVE 140 | Backup: 141 | exceptions: 142 | errors: 143 | 404: 144 | code: BackupNotFoundException 145 | hooks: 146 | sdk_read_one_post_set_output: 147 | template_path: hooks/backup/sdk_read_one_post_set_output.go.tpl 148 | tags: 149 | ignore: true 150 | synced: 151 | when: 152 | - path: Status.BackupStatus 153 | in: 154 | - AVAILABLE 155 | - DELETED 156 | print: 157 | add_age_column: true 158 | add_synced_column: true 159 | additional_columns: 160 | - name: ARN 161 | json_path: .status.ackResourceMetadata.arn 162 | type: string 163 | priority: 1 164 | - name: TABLENAME 165 | json_path: .spec.tableName 166 | type: string 167 | priority: 0 168 | - name: TYPE 169 | json_path: .status.backupType 170 | type: string 171 | - name: SIZE 172 | json_path: .status.backupSize 173 | type: string 174 | - name: STATUS 175 | json_path: .status.backupStatus 176 | type: string 177 | -------------------------------------------------------------------------------- /apis/v1alpha1/generator.yaml: -------------------------------------------------------------------------------- 1 | ignore: 2 | shape_names: 3 | - OnDemandThroughput 4 | - WarmThroughput 5 | - GlobalSecondaryIndexWarmThroughputDescription 6 | - OnDemandThroughputOverride 7 | - TableWarmThroughputDescription 8 | field_paths: 9 | # Replica of Spec.BillingMode 10 | - TableDescription.BillingModeSummary 11 | # Replica of Spec.SSESpecification 12 | - TableDescription.SSEDescription 13 | - TableDescription.TableClassSummary 14 | - CreateTableInput.WarmThroughput 15 | operations: 16 | UpdateGlobalTable: 17 | operation_type: 18 | - Update 19 | - Delete 20 | resource_name: GlobalTable 21 | DescribeBackup: 22 | output_wrapper_field_path: BackupDescription.BackupDetails 23 | primary_identifier_field_name: BackupArn 24 | resources: 25 | Table: 26 | fields: 27 | TableReplicas: 28 | custom_field: 29 | list_of: CreateReplicationGroupMemberAction 30 | compare: 31 | is_ignored: true 32 | ResourcePolicy: 33 | from: 34 | operation: PutResourcePolicy 35 | path: Policy 36 | compare: 37 | is_ignored: true 38 | GlobalSecondaryIndexesDescriptions: 39 | custom_field: 40 | list_of: GlobalSecondaryIndexDescription 41 | is_read_only: true 42 | TimeToLive: 43 | from: 44 | operation: UpdateTimeToLive 45 | path: TimeToLiveSpecification 46 | ContinuousBackups: 47 | is_required: false 48 | from: 49 | operation: UpdateContinuousBackups 50 | path: PointInTimeRecoverySpecification 51 | AttributeDefinitions: 52 | compare: 53 | is_ignored: true 54 | # non modifiable field 55 | KeySchema: 56 | is_immutable: true 57 | compare: 58 | is_ignored: true 59 | GlobalSecondaryIndexes: 60 | compare: 61 | is_ignored: true 62 | Tags: 63 | compare: 64 | is_ignored: true 65 | # non modifiable field 66 | LocalSecondaryIndexes: 67 | is_immutable: true 68 | compare: 69 | is_ignored: true 70 | SSESpecification: 71 | compare: 72 | is_ignored: true 73 | SSESpecification.KMSMasterKeyID: 74 | references: 75 | service_name: kms 76 | resource: Key 77 | path: Status.ACKResourceMetadata.ARN 78 | ContributorInsights: 79 | from: 80 | operation: UpdateContributorInsights 81 | path: ContributorInsightsAction 82 | compare: 83 | is_ignored: true 84 | exceptions: 85 | errors: 86 | 404: 87 | code: ResourceNotFoundException 88 | terminal_codes: 89 | - InvalidParameter 90 | - ValidationException 91 | update_operation: 92 | custom_method_name: customUpdateTable 93 | hooks: 94 | delta_pre_compare: 95 | code: customPreCompare(delta, a, b) 96 | sdk_create_post_set_output: 97 | template_path: hooks/table/sdk_create_post_set_output.go.tpl 98 | sdk_read_one_post_set_output: 99 | template_path: hooks/table/sdk_read_one_post_set_output.go.tpl 100 | sdk_update_pre_build_request: 101 | template_path: hooks/table/sdk_update_pre_build_request.go.tpl 102 | sdk_delete_pre_build_request: 103 | template_path: hooks/table/sdk_delete_pre_build_request.go.tpl 104 | synced: 105 | when: 106 | - path: Status.TableStatus 107 | in: 108 | - ACTIVE 109 | - ARCHIVED 110 | print: 111 | add_age_column: true 112 | add_synced_column: true 113 | additional_columns: 114 | - name: ARN 115 | json_path: .status.ackResourceMetadata.arn 116 | type: string 117 | priority: 1 118 | - name: CLASS 119 | json_path: .spec.tableClass 120 | type: string 121 | priority: 0 122 | - name: STATUS 123 | json_path: .status.tableStatus 124 | type: string 125 | GlobalTable: 126 | exceptions: 127 | errors: 128 | 404: 129 | code: GlobalTableNotFoundException 130 | hooks: 131 | sdk_delete_post_build_request: 132 | code: customSetDeleteInput(r, input) 133 | tags: 134 | ignore: true 135 | synced: 136 | when: 137 | - path: Status.GlobalTableStatus 138 | in: 139 | - ACTIVE 140 | Backup: 141 | exceptions: 142 | errors: 143 | 404: 144 | code: BackupNotFoundException 145 | hooks: 146 | sdk_read_one_post_set_output: 147 | template_path: hooks/backup/sdk_read_one_post_set_output.go.tpl 148 | tags: 149 | ignore: true 150 | synced: 151 | when: 152 | - path: Status.BackupStatus 153 | in: 154 | - AVAILABLE 155 | - DELETED 156 | print: 157 | add_age_column: true 158 | add_synced_column: true 159 | additional_columns: 160 | - name: ARN 161 | json_path: .status.ackResourceMetadata.arn 162 | type: string 163 | priority: 1 164 | - name: TABLENAME 165 | json_path: .spec.tableName 166 | type: string 167 | priority: 0 168 | - name: TYPE 169 | json_path: .status.backupType 170 | type: string 171 | - name: SIZE 172 | json_path: .status.backupSize 173 | type: string 174 | - name: STATUS 175 | json_path: .status.backupStatus 176 | type: string 177 | -------------------------------------------------------------------------------- /pkg/resource/table/hooks_tags.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package table 15 | 16 | import ( 17 | "context" 18 | 19 | ackrtlog "github.com/aws-controllers-k8s/runtime/pkg/runtime/log" 20 | ackutil "github.com/aws-controllers-k8s/runtime/pkg/util" 21 | svcsdk "github.com/aws/aws-sdk-go-v2/service/dynamodb" 22 | svcsdktypes "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" 23 | 24 | "github.com/aws-controllers-k8s/dynamodb-controller/apis/v1alpha1" 25 | ) 26 | 27 | // syncTableTags updates a dynamodb table tags. 28 | // 29 | // TODO(hilalymh): move this function to a common utility file. This function can be reused 30 | // to tag GlobalTable resources. 31 | func (rm *resourceManager) syncTableTags( 32 | ctx context.Context, 33 | desired *resource, 34 | latest *resource, 35 | ) (err error) { 36 | rlog := ackrtlog.FromContext(ctx) 37 | exit := rlog.Trace("rm.syncTableTags") 38 | defer exit(err) 39 | 40 | added, removed := computeTagsDelta(desired.ko.Spec.Tags, latest.ko.Spec.Tags) 41 | 42 | // There are no API calls to update an existing tag. To update a tag we will have to first 43 | // delete it and then recreate it with the new value. 44 | 45 | if len(removed) > 0 { 46 | _, err = rm.sdkapi.UntagResource( 47 | ctx, 48 | &svcsdk.UntagResourceInput{ 49 | ResourceArn: (*string)(latest.ko.Status.ACKResourceMetadata.ARN), 50 | TagKeys: removed, 51 | }, 52 | ) 53 | rm.metrics.RecordAPICall("GET", "UntagResource", err) 54 | if err != nil { 55 | return err 56 | } 57 | } 58 | 59 | if len(added) > 0 { 60 | _, err = rm.sdkapi.TagResource( 61 | ctx, 62 | &svcsdk.TagResourceInput{ 63 | ResourceArn: (*string)(latest.ko.Status.ACKResourceMetadata.ARN), 64 | Tags: sdkTagsFromResourceTags(added), 65 | }, 66 | ) 67 | rm.metrics.RecordAPICall("GET", "UntagResource", err) 68 | if err != nil { 69 | return err 70 | } 71 | } 72 | return nil 73 | } 74 | 75 | // equalTags returns true if two Tag arrays are equal regardless of the order 76 | // of their elements. 77 | func equalTags( 78 | a []*v1alpha1.Tag, 79 | b []*v1alpha1.Tag, 80 | ) bool { 81 | added, removed := computeTagsDelta(a, b) 82 | return len(added) == 0 && len(removed) == 0 83 | } 84 | 85 | // resourceTagsFromSDKTags transforms a *svcsdk.Tag array to a *v1alpha1.Tag array. 86 | func resourceTagsFromSDKTags(svcTags []svcsdktypes.Tag) []*v1alpha1.Tag { 87 | tags := make([]*v1alpha1.Tag, len(svcTags)) 88 | for i := range svcTags { 89 | tags[i] = &v1alpha1.Tag{ 90 | Key: svcTags[i].Key, 91 | Value: svcTags[i].Value, 92 | } 93 | } 94 | return tags 95 | } 96 | 97 | // svcTagsFromResourceTags transforms a *v1alpha1.Tag array to a *svcsdk.Tag array. 98 | func sdkTagsFromResourceTags(rTags []*v1alpha1.Tag) []svcsdktypes.Tag { 99 | tags := make([]svcsdktypes.Tag, len(rTags)) 100 | for i := range rTags { 101 | tags[i] = svcsdktypes.Tag{ 102 | Key: rTags[i].Key, 103 | Value: rTags[i].Value, 104 | } 105 | } 106 | return tags 107 | } 108 | 109 | // computeTagsDelta compares two Tag arrays and return three different list 110 | // containing the added, updated and removed tags. 111 | // The removed tags only contains the Key of tags 112 | func computeTagsDelta( 113 | a []*v1alpha1.Tag, 114 | b []*v1alpha1.Tag, 115 | ) (added []*v1alpha1.Tag, removed []string) { 116 | var visitedIndexes []string 117 | mainLoop: 118 | for _, aElement := range b { 119 | visitedIndexes = append(visitedIndexes, *aElement.Key) 120 | for _, bElement := range a { 121 | if equalStrings(aElement.Key, bElement.Key) { 122 | if !equalStrings(aElement.Value, bElement.Value) { 123 | added = append(added, bElement) 124 | } 125 | continue mainLoop 126 | } 127 | } 128 | removed = append(removed, *aElement.Key) 129 | } 130 | for _, bElement := range a { 131 | if !ackutil.InStrings(*bElement.Key, visitedIndexes) { 132 | added = append(added, bElement) 133 | } 134 | } 135 | return added, removed 136 | } 137 | 138 | // getResourceTagsPagesWithContext queries the list of tags of a given resource. 139 | func (rm *resourceManager) getResourceTagsPagesWithContext(ctx context.Context, resourceARN string) ([]*v1alpha1.Tag, error) { 140 | var err error 141 | rlog := ackrtlog.FromContext(ctx) 142 | exit := rlog.Trace("rm.getResourceTagsPagesWithContext") 143 | defer exit(err) 144 | 145 | tags := []*v1alpha1.Tag{} 146 | 147 | var token *string = nil 148 | for { 149 | var listTagsOfResourceOutput *svcsdk.ListTagsOfResourceOutput 150 | listTagsOfResourceOutput, err = rm.sdkapi.ListTagsOfResource( 151 | ctx, 152 | &svcsdk.ListTagsOfResourceInput{ 153 | NextToken: token, 154 | ResourceArn: &resourceARN, 155 | }, 156 | ) 157 | rm.metrics.RecordAPICall("GET", "ListTagsOfResource", err) 158 | if err != nil { 159 | return nil, err 160 | } 161 | tags = append(tags, resourceTagsFromSDKTags(listTagsOfResourceOutput.Tags)...) 162 | if listTagsOfResourceOutput.NextToken == nil { 163 | break 164 | } 165 | token = listTagsOfResourceOutput.NextToken 166 | } 167 | return tags, nil 168 | } 169 | -------------------------------------------------------------------------------- /pkg/resource/table/descriptor.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by ack-generate. DO NOT EDIT. 15 | 16 | package table 17 | 18 | import ( 19 | ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" 20 | ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare" 21 | acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" 22 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 | "k8s.io/apimachinery/pkg/runtime/schema" 24 | rtclient "sigs.k8s.io/controller-runtime/pkg/client" 25 | k8sctrlutil "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 26 | 27 | svcapitypes "github.com/aws-controllers-k8s/dynamodb-controller/apis/v1alpha1" 28 | ) 29 | 30 | const ( 31 | FinalizerString = "finalizers.dynamodb.services.k8s.aws/Table" 32 | ) 33 | 34 | var ( 35 | GroupVersionResource = svcapitypes.GroupVersion.WithResource("tables") 36 | GroupKind = metav1.GroupKind{ 37 | Group: "dynamodb.services.k8s.aws", 38 | Kind: "Table", 39 | } 40 | ) 41 | 42 | // resourceDescriptor implements the 43 | // `aws-service-operator-k8s/pkg/types.AWSResourceDescriptor` interface 44 | type resourceDescriptor struct { 45 | } 46 | 47 | // GroupVersionKind returns a Kubernetes schema.GroupVersionKind struct that 48 | // describes the API Group, Version and Kind of CRs described by the descriptor 49 | func (d *resourceDescriptor) GroupVersionKind() schema.GroupVersionKind { 50 | return svcapitypes.GroupVersion.WithKind(GroupKind.Kind) 51 | } 52 | 53 | // EmptyRuntimeObject returns an empty object prototype that may be used in 54 | // apimachinery and k8s client operations 55 | func (d *resourceDescriptor) EmptyRuntimeObject() rtclient.Object { 56 | return &svcapitypes.Table{} 57 | } 58 | 59 | // ResourceFromRuntimeObject returns an AWSResource that has been initialized 60 | // with the supplied runtime.Object 61 | func (d *resourceDescriptor) ResourceFromRuntimeObject( 62 | obj rtclient.Object, 63 | ) acktypes.AWSResource { 64 | return &resource{ 65 | ko: obj.(*svcapitypes.Table), 66 | } 67 | } 68 | 69 | // Delta returns an `ackcompare.Delta` object containing the difference between 70 | // one `AWSResource` and another. 71 | func (d *resourceDescriptor) Delta(a, b acktypes.AWSResource) *ackcompare.Delta { 72 | return newResourceDelta(a.(*resource), b.(*resource)) 73 | } 74 | 75 | // IsManaged returns true if the supplied AWSResource is under the management 76 | // of an ACK service controller. What this means in practice is that the 77 | // underlying custom resource (CR) in the AWSResource has had a 78 | // resource-specific finalizer associated with it. 79 | func (d *resourceDescriptor) IsManaged( 80 | res acktypes.AWSResource, 81 | ) bool { 82 | obj := res.RuntimeObject() 83 | if obj == nil { 84 | // Should not happen. If it does, there is a bug in the code 85 | panic("nil RuntimeMetaObject in AWSResource") 86 | } 87 | // Remove use of custom code once 88 | // https://github.com/kubernetes-sigs/controller-runtime/issues/994 is 89 | // fixed. This should be able to be: 90 | // 91 | // return k8sctrlutil.ContainsFinalizer(obj, FinalizerString) 92 | return containsFinalizer(obj, FinalizerString) 93 | } 94 | 95 | // Remove once https://github.com/kubernetes-sigs/controller-runtime/issues/994 96 | // is fixed. 97 | func containsFinalizer(obj rtclient.Object, finalizer string) bool { 98 | f := obj.GetFinalizers() 99 | for _, e := range f { 100 | if e == finalizer { 101 | return true 102 | } 103 | } 104 | return false 105 | } 106 | 107 | // MarkManaged places the supplied resource under the management of ACK. What 108 | // this typically means is that the resource manager will decorate the 109 | // underlying custom resource (CR) with a finalizer that indicates ACK is 110 | // managing the resource and the underlying CR may not be deleted until ACK is 111 | // finished cleaning up any backend AWS service resources associated with the 112 | // CR. 113 | func (d *resourceDescriptor) MarkManaged( 114 | res acktypes.AWSResource, 115 | ) { 116 | obj := res.RuntimeObject() 117 | if obj == nil { 118 | // Should not happen. If it does, there is a bug in the code 119 | panic("nil RuntimeMetaObject in AWSResource") 120 | } 121 | k8sctrlutil.AddFinalizer(obj, FinalizerString) 122 | } 123 | 124 | // MarkUnmanaged removes the supplied resource from management by ACK. What 125 | // this typically means is that the resource manager will remove a finalizer 126 | // underlying custom resource (CR) that indicates ACK is managing the resource. 127 | // This will allow the Kubernetes API server to delete the underlying CR. 128 | func (d *resourceDescriptor) MarkUnmanaged( 129 | res acktypes.AWSResource, 130 | ) { 131 | obj := res.RuntimeObject() 132 | if obj == nil { 133 | // Should not happen. If it does, there is a bug in the code 134 | panic("nil RuntimeMetaObject in AWSResource") 135 | } 136 | k8sctrlutil.RemoveFinalizer(obj, FinalizerString) 137 | } 138 | 139 | // MarkAdopted places descriptors on the custom resource that indicate the 140 | // resource was not created from within ACK. 141 | func (d *resourceDescriptor) MarkAdopted( 142 | res acktypes.AWSResource, 143 | ) { 144 | obj := res.RuntimeObject() 145 | if obj == nil { 146 | // Should not happen. If it does, there is a bug in the code 147 | panic("nil RuntimeObject in AWSResource") 148 | } 149 | curr := obj.GetAnnotations() 150 | if curr == nil { 151 | curr = make(map[string]string) 152 | } 153 | curr[ackv1alpha1.AnnotationAdopted] = "true" 154 | obj.SetAnnotations(curr) 155 | } 156 | -------------------------------------------------------------------------------- /pkg/resource/backup/descriptor.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by ack-generate. DO NOT EDIT. 15 | 16 | package backup 17 | 18 | import ( 19 | ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" 20 | ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare" 21 | acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" 22 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 | "k8s.io/apimachinery/pkg/runtime/schema" 24 | rtclient "sigs.k8s.io/controller-runtime/pkg/client" 25 | k8sctrlutil "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 26 | 27 | svcapitypes "github.com/aws-controllers-k8s/dynamodb-controller/apis/v1alpha1" 28 | ) 29 | 30 | const ( 31 | FinalizerString = "finalizers.dynamodb.services.k8s.aws/Backup" 32 | ) 33 | 34 | var ( 35 | GroupVersionResource = svcapitypes.GroupVersion.WithResource("backups") 36 | GroupKind = metav1.GroupKind{ 37 | Group: "dynamodb.services.k8s.aws", 38 | Kind: "Backup", 39 | } 40 | ) 41 | 42 | // resourceDescriptor implements the 43 | // `aws-service-operator-k8s/pkg/types.AWSResourceDescriptor` interface 44 | type resourceDescriptor struct { 45 | } 46 | 47 | // GroupVersionKind returns a Kubernetes schema.GroupVersionKind struct that 48 | // describes the API Group, Version and Kind of CRs described by the descriptor 49 | func (d *resourceDescriptor) GroupVersionKind() schema.GroupVersionKind { 50 | return svcapitypes.GroupVersion.WithKind(GroupKind.Kind) 51 | } 52 | 53 | // EmptyRuntimeObject returns an empty object prototype that may be used in 54 | // apimachinery and k8s client operations 55 | func (d *resourceDescriptor) EmptyRuntimeObject() rtclient.Object { 56 | return &svcapitypes.Backup{} 57 | } 58 | 59 | // ResourceFromRuntimeObject returns an AWSResource that has been initialized 60 | // with the supplied runtime.Object 61 | func (d *resourceDescriptor) ResourceFromRuntimeObject( 62 | obj rtclient.Object, 63 | ) acktypes.AWSResource { 64 | return &resource{ 65 | ko: obj.(*svcapitypes.Backup), 66 | } 67 | } 68 | 69 | // Delta returns an `ackcompare.Delta` object containing the difference between 70 | // one `AWSResource` and another. 71 | func (d *resourceDescriptor) Delta(a, b acktypes.AWSResource) *ackcompare.Delta { 72 | return newResourceDelta(a.(*resource), b.(*resource)) 73 | } 74 | 75 | // IsManaged returns true if the supplied AWSResource is under the management 76 | // of an ACK service controller. What this means in practice is that the 77 | // underlying custom resource (CR) in the AWSResource has had a 78 | // resource-specific finalizer associated with it. 79 | func (d *resourceDescriptor) IsManaged( 80 | res acktypes.AWSResource, 81 | ) bool { 82 | obj := res.RuntimeObject() 83 | if obj == nil { 84 | // Should not happen. If it does, there is a bug in the code 85 | panic("nil RuntimeMetaObject in AWSResource") 86 | } 87 | // Remove use of custom code once 88 | // https://github.com/kubernetes-sigs/controller-runtime/issues/994 is 89 | // fixed. This should be able to be: 90 | // 91 | // return k8sctrlutil.ContainsFinalizer(obj, FinalizerString) 92 | return containsFinalizer(obj, FinalizerString) 93 | } 94 | 95 | // Remove once https://github.com/kubernetes-sigs/controller-runtime/issues/994 96 | // is fixed. 97 | func containsFinalizer(obj rtclient.Object, finalizer string) bool { 98 | f := obj.GetFinalizers() 99 | for _, e := range f { 100 | if e == finalizer { 101 | return true 102 | } 103 | } 104 | return false 105 | } 106 | 107 | // MarkManaged places the supplied resource under the management of ACK. What 108 | // this typically means is that the resource manager will decorate the 109 | // underlying custom resource (CR) with a finalizer that indicates ACK is 110 | // managing the resource and the underlying CR may not be deleted until ACK is 111 | // finished cleaning up any backend AWS service resources associated with the 112 | // CR. 113 | func (d *resourceDescriptor) MarkManaged( 114 | res acktypes.AWSResource, 115 | ) { 116 | obj := res.RuntimeObject() 117 | if obj == nil { 118 | // Should not happen. If it does, there is a bug in the code 119 | panic("nil RuntimeMetaObject in AWSResource") 120 | } 121 | k8sctrlutil.AddFinalizer(obj, FinalizerString) 122 | } 123 | 124 | // MarkUnmanaged removes the supplied resource from management by ACK. What 125 | // this typically means is that the resource manager will remove a finalizer 126 | // underlying custom resource (CR) that indicates ACK is managing the resource. 127 | // This will allow the Kubernetes API server to delete the underlying CR. 128 | func (d *resourceDescriptor) MarkUnmanaged( 129 | res acktypes.AWSResource, 130 | ) { 131 | obj := res.RuntimeObject() 132 | if obj == nil { 133 | // Should not happen. If it does, there is a bug in the code 134 | panic("nil RuntimeMetaObject in AWSResource") 135 | } 136 | k8sctrlutil.RemoveFinalizer(obj, FinalizerString) 137 | } 138 | 139 | // MarkAdopted places descriptors on the custom resource that indicate the 140 | // resource was not created from within ACK. 141 | func (d *resourceDescriptor) MarkAdopted( 142 | res acktypes.AWSResource, 143 | ) { 144 | obj := res.RuntimeObject() 145 | if obj == nil { 146 | // Should not happen. If it does, there is a bug in the code 147 | panic("nil RuntimeObject in AWSResource") 148 | } 149 | curr := obj.GetAnnotations() 150 | if curr == nil { 151 | curr = make(map[string]string) 152 | } 153 | curr[ackv1alpha1.AnnotationAdopted] = "true" 154 | obj.SetAnnotations(curr) 155 | } 156 | -------------------------------------------------------------------------------- /pkg/resource/global_table/descriptor.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by ack-generate. DO NOT EDIT. 15 | 16 | package global_table 17 | 18 | import ( 19 | ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" 20 | ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare" 21 | acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" 22 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 | "k8s.io/apimachinery/pkg/runtime/schema" 24 | rtclient "sigs.k8s.io/controller-runtime/pkg/client" 25 | k8sctrlutil "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 26 | 27 | svcapitypes "github.com/aws-controllers-k8s/dynamodb-controller/apis/v1alpha1" 28 | ) 29 | 30 | const ( 31 | FinalizerString = "finalizers.dynamodb.services.k8s.aws/GlobalTable" 32 | ) 33 | 34 | var ( 35 | GroupVersionResource = svcapitypes.GroupVersion.WithResource("globaltables") 36 | GroupKind = metav1.GroupKind{ 37 | Group: "dynamodb.services.k8s.aws", 38 | Kind: "GlobalTable", 39 | } 40 | ) 41 | 42 | // resourceDescriptor implements the 43 | // `aws-service-operator-k8s/pkg/types.AWSResourceDescriptor` interface 44 | type resourceDescriptor struct { 45 | } 46 | 47 | // GroupVersionKind returns a Kubernetes schema.GroupVersionKind struct that 48 | // describes the API Group, Version and Kind of CRs described by the descriptor 49 | func (d *resourceDescriptor) GroupVersionKind() schema.GroupVersionKind { 50 | return svcapitypes.GroupVersion.WithKind(GroupKind.Kind) 51 | } 52 | 53 | // EmptyRuntimeObject returns an empty object prototype that may be used in 54 | // apimachinery and k8s client operations 55 | func (d *resourceDescriptor) EmptyRuntimeObject() rtclient.Object { 56 | return &svcapitypes.GlobalTable{} 57 | } 58 | 59 | // ResourceFromRuntimeObject returns an AWSResource that has been initialized 60 | // with the supplied runtime.Object 61 | func (d *resourceDescriptor) ResourceFromRuntimeObject( 62 | obj rtclient.Object, 63 | ) acktypes.AWSResource { 64 | return &resource{ 65 | ko: obj.(*svcapitypes.GlobalTable), 66 | } 67 | } 68 | 69 | // Delta returns an `ackcompare.Delta` object containing the difference between 70 | // one `AWSResource` and another. 71 | func (d *resourceDescriptor) Delta(a, b acktypes.AWSResource) *ackcompare.Delta { 72 | return newResourceDelta(a.(*resource), b.(*resource)) 73 | } 74 | 75 | // IsManaged returns true if the supplied AWSResource is under the management 76 | // of an ACK service controller. What this means in practice is that the 77 | // underlying custom resource (CR) in the AWSResource has had a 78 | // resource-specific finalizer associated with it. 79 | func (d *resourceDescriptor) IsManaged( 80 | res acktypes.AWSResource, 81 | ) bool { 82 | obj := res.RuntimeObject() 83 | if obj == nil { 84 | // Should not happen. If it does, there is a bug in the code 85 | panic("nil RuntimeMetaObject in AWSResource") 86 | } 87 | // Remove use of custom code once 88 | // https://github.com/kubernetes-sigs/controller-runtime/issues/994 is 89 | // fixed. This should be able to be: 90 | // 91 | // return k8sctrlutil.ContainsFinalizer(obj, FinalizerString) 92 | return containsFinalizer(obj, FinalizerString) 93 | } 94 | 95 | // Remove once https://github.com/kubernetes-sigs/controller-runtime/issues/994 96 | // is fixed. 97 | func containsFinalizer(obj rtclient.Object, finalizer string) bool { 98 | f := obj.GetFinalizers() 99 | for _, e := range f { 100 | if e == finalizer { 101 | return true 102 | } 103 | } 104 | return false 105 | } 106 | 107 | // MarkManaged places the supplied resource under the management of ACK. What 108 | // this typically means is that the resource manager will decorate the 109 | // underlying custom resource (CR) with a finalizer that indicates ACK is 110 | // managing the resource and the underlying CR may not be deleted until ACK is 111 | // finished cleaning up any backend AWS service resources associated with the 112 | // CR. 113 | func (d *resourceDescriptor) MarkManaged( 114 | res acktypes.AWSResource, 115 | ) { 116 | obj := res.RuntimeObject() 117 | if obj == nil { 118 | // Should not happen. If it does, there is a bug in the code 119 | panic("nil RuntimeMetaObject in AWSResource") 120 | } 121 | k8sctrlutil.AddFinalizer(obj, FinalizerString) 122 | } 123 | 124 | // MarkUnmanaged removes the supplied resource from management by ACK. What 125 | // this typically means is that the resource manager will remove a finalizer 126 | // underlying custom resource (CR) that indicates ACK is managing the resource. 127 | // This will allow the Kubernetes API server to delete the underlying CR. 128 | func (d *resourceDescriptor) MarkUnmanaged( 129 | res acktypes.AWSResource, 130 | ) { 131 | obj := res.RuntimeObject() 132 | if obj == nil { 133 | // Should not happen. If it does, there is a bug in the code 134 | panic("nil RuntimeMetaObject in AWSResource") 135 | } 136 | k8sctrlutil.RemoveFinalizer(obj, FinalizerString) 137 | } 138 | 139 | // MarkAdopted places descriptors on the custom resource that indicate the 140 | // resource was not created from within ACK. 141 | func (d *resourceDescriptor) MarkAdopted( 142 | res acktypes.AWSResource, 143 | ) { 144 | obj := res.RuntimeObject() 145 | if obj == nil { 146 | // Should not happen. If it does, there is a bug in the code 147 | panic("nil RuntimeObject in AWSResource") 148 | } 149 | curr := obj.GetAnnotations() 150 | if curr == nil { 151 | curr = make(map[string]string) 152 | } 153 | curr[ackv1alpha1.AnnotationAdopted] = "true" 154 | obj.SetAnnotations(curr) 155 | } 156 | -------------------------------------------------------------------------------- /helm/crds/services.k8s.aws_fieldexports.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.19.0 7 | name: fieldexports.services.k8s.aws 8 | spec: 9 | group: services.k8s.aws 10 | names: 11 | kind: FieldExport 12 | listKind: FieldExportList 13 | plural: fieldexports 14 | singular: fieldexport 15 | scope: Namespaced 16 | versions: 17 | - name: v1alpha1 18 | schema: 19 | openAPIV3Schema: 20 | description: FieldExport is the schema for the FieldExport API. 21 | properties: 22 | apiVersion: 23 | description: |- 24 | APIVersion defines the versioned schema of this representation of an object. 25 | Servers should convert recognized schemas to the latest internal value, and 26 | may reject unrecognized values. 27 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources 28 | type: string 29 | kind: 30 | description: |- 31 | Kind is a string value representing the REST resource this object represents. 32 | Servers may infer this from the endpoint the client submits requests to. 33 | Cannot be updated. 34 | In CamelCase. 35 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds 36 | type: string 37 | metadata: 38 | type: object 39 | spec: 40 | description: FieldExportSpec defines the desired state of the FieldExport. 41 | properties: 42 | from: 43 | description: |- 44 | ResourceFieldSelector provides the values necessary to identify an individual 45 | field on an individual K8s resource. 46 | properties: 47 | path: 48 | type: string 49 | resource: 50 | description: |- 51 | NamespacedResource provides all the values necessary to identify an ACK 52 | resource of a given type (within the same namespace as the custom resource 53 | containing this type). 54 | properties: 55 | group: 56 | type: string 57 | kind: 58 | type: string 59 | name: 60 | type: string 61 | required: 62 | - group 63 | - kind 64 | - name 65 | type: object 66 | required: 67 | - path 68 | - resource 69 | type: object 70 | to: 71 | description: |- 72 | FieldExportTarget provides the values necessary to identify the 73 | output path for a field export. 74 | properties: 75 | key: 76 | description: Key overrides the default value (`.`) 77 | for the FieldExport target 78 | type: string 79 | kind: 80 | description: |- 81 | FieldExportOutputType represents all types that can be produced by a field 82 | export operation 83 | enum: 84 | - configmap 85 | - secret 86 | type: string 87 | name: 88 | type: string 89 | namespace: 90 | description: Namespace is marked as optional, so we cannot compose 91 | `NamespacedName` 92 | type: string 93 | required: 94 | - kind 95 | - name 96 | type: object 97 | required: 98 | - from 99 | - to 100 | type: object 101 | status: 102 | description: FieldExportStatus defines the observed status of the FieldExport. 103 | properties: 104 | conditions: 105 | description: |- 106 | A collection of `ackv1alpha1.Condition` objects that describe the various 107 | recoverable states of the field CR 108 | items: 109 | description: |- 110 | Condition is the common struct used by all CRDs managed by ACK service 111 | controllers to indicate terminal states of the CR and its backend AWS 112 | service API resource 113 | properties: 114 | lastTransitionTime: 115 | description: Last time the condition transitioned from one status 116 | to another. 117 | format: date-time 118 | type: string 119 | message: 120 | description: A human readable message indicating details about 121 | the transition. 122 | type: string 123 | reason: 124 | description: The reason for the condition's last transition. 125 | type: string 126 | status: 127 | description: Status of the condition, one of True, False, Unknown. 128 | type: string 129 | type: 130 | description: Type is the type of the Condition 131 | type: string 132 | required: 133 | - status 134 | - type 135 | type: object 136 | type: array 137 | required: 138 | - conditions 139 | type: object 140 | type: object 141 | served: true 142 | storage: true 143 | subresources: 144 | status: {} 145 | -------------------------------------------------------------------------------- /config/crd/common/bases/services.k8s.aws_fieldexports.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.19.0 7 | name: fieldexports.services.k8s.aws 8 | spec: 9 | group: services.k8s.aws 10 | names: 11 | kind: FieldExport 12 | listKind: FieldExportList 13 | plural: fieldexports 14 | singular: fieldexport 15 | scope: Namespaced 16 | versions: 17 | - name: v1alpha1 18 | schema: 19 | openAPIV3Schema: 20 | description: FieldExport is the schema for the FieldExport API. 21 | properties: 22 | apiVersion: 23 | description: |- 24 | APIVersion defines the versioned schema of this representation of an object. 25 | Servers should convert recognized schemas to the latest internal value, and 26 | may reject unrecognized values. 27 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources 28 | type: string 29 | kind: 30 | description: |- 31 | Kind is a string value representing the REST resource this object represents. 32 | Servers may infer this from the endpoint the client submits requests to. 33 | Cannot be updated. 34 | In CamelCase. 35 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds 36 | type: string 37 | metadata: 38 | type: object 39 | spec: 40 | description: FieldExportSpec defines the desired state of the FieldExport. 41 | properties: 42 | from: 43 | description: |- 44 | ResourceFieldSelector provides the values necessary to identify an individual 45 | field on an individual K8s resource. 46 | properties: 47 | path: 48 | type: string 49 | resource: 50 | description: |- 51 | NamespacedResource provides all the values necessary to identify an ACK 52 | resource of a given type (within the same namespace as the custom resource 53 | containing this type). 54 | properties: 55 | group: 56 | type: string 57 | kind: 58 | type: string 59 | name: 60 | type: string 61 | required: 62 | - group 63 | - kind 64 | - name 65 | type: object 66 | required: 67 | - path 68 | - resource 69 | type: object 70 | to: 71 | description: |- 72 | FieldExportTarget provides the values necessary to identify the 73 | output path for a field export. 74 | properties: 75 | key: 76 | description: Key overrides the default value (`.`) 77 | for the FieldExport target 78 | type: string 79 | kind: 80 | description: |- 81 | FieldExportOutputType represents all types that can be produced by a field 82 | export operation 83 | enum: 84 | - configmap 85 | - secret 86 | type: string 87 | name: 88 | type: string 89 | namespace: 90 | description: Namespace is marked as optional, so we cannot compose 91 | `NamespacedName` 92 | type: string 93 | required: 94 | - kind 95 | - name 96 | type: object 97 | required: 98 | - from 99 | - to 100 | type: object 101 | status: 102 | description: FieldExportStatus defines the observed status of the FieldExport. 103 | properties: 104 | conditions: 105 | description: |- 106 | A collection of `ackv1alpha1.Condition` objects that describe the various 107 | recoverable states of the field CR 108 | items: 109 | description: |- 110 | Condition is the common struct used by all CRDs managed by ACK service 111 | controllers to indicate terminal states of the CR and its backend AWS 112 | service API resource 113 | properties: 114 | lastTransitionTime: 115 | description: Last time the condition transitioned from one status 116 | to another. 117 | format: date-time 118 | type: string 119 | message: 120 | description: A human readable message indicating details about 121 | the transition. 122 | type: string 123 | reason: 124 | description: The reason for the condition's last transition. 125 | type: string 126 | status: 127 | description: Status of the condition, one of True, False, Unknown. 128 | type: string 129 | type: 130 | description: Type is the type of the Condition 131 | type: string 132 | required: 133 | - status 134 | - type 135 | type: object 136 | type: array 137 | required: 138 | - conditions 139 | type: object 140 | type: object 141 | served: true 142 | storage: true 143 | subresources: 144 | status: {} 145 | -------------------------------------------------------------------------------- /pkg/resource/table/hooks_resource_policy.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package table 15 | 16 | import ( 17 | "context" 18 | "encoding/json" 19 | "errors" 20 | "reflect" 21 | 22 | ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare" 23 | ackerr "github.com/aws-controllers-k8s/runtime/pkg/errors" 24 | ackrtlog "github.com/aws-controllers-k8s/runtime/pkg/runtime/log" 25 | svcsdk "github.com/aws/aws-sdk-go-v2/service/dynamodb" 26 | svcsdktypes "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" 27 | awsiampolicy "github.com/micahhausler/aws-iam-policy/policy" 28 | ) 29 | 30 | // syncResourcePolicy updates a DynamoDB table's resource-based policy. 31 | func (rm *resourceManager) syncResourcePolicy( 32 | ctx context.Context, 33 | desired *resource, 34 | latest *resource, 35 | ) (err error) { 36 | rlog := ackrtlog.FromContext(ctx) 37 | exit := rlog.Trace("rm.syncResourcePolicy") 38 | defer func(err error) { exit(err) }(err) 39 | 40 | if desired.ko.Spec.ResourcePolicy == nil { 41 | return rm.deleteResourcePolicy(ctx, latest) 42 | } 43 | 44 | return rm.putResourcePolicy(ctx, desired) 45 | } 46 | 47 | // putResourcePolicy attaches or updates a resource-based policy to a DynamoDB table. 48 | func (rm *resourceManager) putResourcePolicy( 49 | ctx context.Context, 50 | r *resource, 51 | ) (err error) { 52 | rlog := ackrtlog.FromContext(ctx) 53 | exit := rlog.Trace("rm.putResourcePolicy") 54 | defer func(err error) { exit(err) }(err) 55 | 56 | if r.ko.Spec.ResourcePolicy == nil { 57 | return nil 58 | } 59 | 60 | tableARN := (*string)(r.ko.Status.ACKResourceMetadata.ARN) 61 | if tableARN == nil || *tableARN == "" { 62 | return errors.New("table ARN is required to put resource policy") 63 | } 64 | 65 | _, err = rm.sdkapi.PutResourcePolicy( 66 | ctx, 67 | &svcsdk.PutResourcePolicyInput{ 68 | ResourceArn: tableARN, 69 | Policy: r.ko.Spec.ResourcePolicy, 70 | }, 71 | ) 72 | rm.metrics.RecordAPICall("UPDATE", "PutResourcePolicy", err) 73 | return err 74 | } 75 | 76 | // deleteResourcePolicy removes a resource-based policy from a DynamoDB table. 77 | func (rm *resourceManager) deleteResourcePolicy( 78 | ctx context.Context, 79 | r *resource, 80 | ) (err error) { 81 | rlog := ackrtlog.FromContext(ctx) 82 | exit := rlog.Trace("rm.deleteResourcePolicy") 83 | defer func(err error) { exit(err) }(err) 84 | 85 | tableARN := (*string)(r.ko.Status.ACKResourceMetadata.ARN) 86 | if tableARN == nil || *tableARN == "" { 87 | return errors.New("table ARN is required to delete resource policy") 88 | } 89 | 90 | _, err = rm.sdkapi.DeleteResourcePolicy( 91 | ctx, 92 | &svcsdk.DeleteResourcePolicyInput{ 93 | ResourceArn: tableARN, 94 | }, 95 | ) 96 | rm.metrics.RecordAPICall("DELETE", "DeleteResourcePolicy", err) 97 | if err != nil { 98 | var policyNotFoundErr *svcsdktypes.PolicyNotFoundException 99 | if errors.As(err, &policyNotFoundErr) { 100 | // Policy already doesn't exist, this is a success case 101 | return nil 102 | } 103 | } 104 | 105 | return err 106 | } 107 | 108 | // getResourcePolicyWithContext retrieves the resource-based policy of a DynamoDB table. 109 | func (rm *resourceManager) getResourcePolicyWithContext( 110 | ctx context.Context, 111 | tableARN *string, 112 | ) (*string, error) { 113 | var err error 114 | rlog := ackrtlog.FromContext(ctx) 115 | exit := rlog.Trace("rm.getResourcePolicyWithContext") 116 | defer func(err error) { exit(err) }(err) 117 | 118 | if tableARN == nil || *tableARN == "" { 119 | return nil, errors.New("table ARN is required to get resource policy") 120 | } 121 | 122 | res, err := rm.sdkapi.GetResourcePolicy( 123 | ctx, 124 | &svcsdk.GetResourcePolicyInput{ 125 | ResourceArn: tableARN, 126 | }, 127 | ) 128 | rm.metrics.RecordAPICall("GET", "GetResourcePolicy", nil) 129 | if err != nil { 130 | if awsErr, ok := ackerr.AWSError(err); ok && awsErr.ErrorCode() == "PolicyNotFoundException" { 131 | return nil, nil 132 | } 133 | return nil, err 134 | } 135 | 136 | return res.Policy, nil 137 | } 138 | 139 | // compareResourcePolicyDocument is a custom comparison function for 140 | // ResourcePolicy documents. The reason why we need a custom function for 141 | // this field is to handle the variability in shapes of JSON objects representing 142 | // IAM policies, especially when it comes to statements, actions, and other fields. 143 | func compareResourcePolicyDocument( 144 | delta *ackcompare.Delta, 145 | a *resource, 146 | b *resource, 147 | ) { 148 | // Handle cases where one policy is nil and the other is not. 149 | // This means one resource has a policy and the other doesn't - they're different. 150 | if ackcompare.HasNilDifference(a.ko.Spec.ResourcePolicy, b.ko.Spec.ResourcePolicy) { 151 | delta.Add("Spec.ResourcePolicy", a.ko.Spec.ResourcePolicy, b.ko.Spec.ResourcePolicy) 152 | return 153 | } 154 | 155 | // If both policies are nil, there's no difference - both resources have no policy. 156 | if a.ko.Spec.ResourcePolicy == nil && b.ko.Spec.ResourcePolicy == nil { 157 | return 158 | } 159 | 160 | // At this point, both policies are non-nil. We need to compare their JSON content. 161 | // To handle the variability in shapes of JSON objects representing IAM policies, 162 | // especially when it comes to statements, actions, and other fields, we need 163 | // a custom json.Unmarshaller approach crafted to our specific needs. Luckily, 164 | // it happens that @micahhausler built a library dedicated to this very special 165 | // need: github.com/micahhausler/aws-iam-policy. 166 | // 167 | // Copied from IAM Controller: https://github.com/aws-controllers-k8s/iam-controller/blob/main/pkg/resource/role/hooks.go#L398-L432 168 | // Based on review feedback: https://github.com/aws-controllers-k8s/dynamodb-controller/pull/154#discussion_r2443876840 169 | var policyDocumentA awsiampolicy.Policy 170 | _ = json.Unmarshal([]byte(*a.ko.Spec.ResourcePolicy), &policyDocumentA) 171 | var policyDocumentB awsiampolicy.Policy 172 | _ = json.Unmarshal([]byte(*b.ko.Spec.ResourcePolicy), &policyDocumentB) 173 | 174 | if !reflect.DeepEqual(policyDocumentA, policyDocumentB) { 175 | delta.Add("Spec.ResourcePolicy", a.ko.Spec.ResourcePolicy, b.ko.Spec.ResourcePolicy) 176 | } 177 | } 178 | --------------------------------------------------------------------------------