├── test └── e2e │ ├── bootstrap.yaml │ ├── .gitignore │ ├── requirements.txt │ ├── resources │ ├── bucket.yaml │ ├── bucket_accelerate.yaml │ ├── bucket_versioning.yaml │ ├── bucket_request_payment.yaml │ ├── bucket_deletion_policy.yaml │ ├── bucket_ownership_controls.yaml │ ├── bucket_public_access_block.yaml │ ├── bucket_adopt_or_create_not_exist.yaml │ ├── bucket_tagging.yaml │ ├── bucket_website.yaml │ ├── bucket_cors.yaml │ ├── bucket_logging.yaml │ ├── bucket_adopt.yaml │ ├── bucket_encryption.yaml │ ├── bucket_adoption_stack.yaml │ ├── bucket_notification.yaml │ ├── bucket_lifecycle.yaml │ ├── bucket_adopt_or_create.yaml │ ├── bucket_policy.yaml │ └── bucket_replication.yaml │ ├── service_cleanup.py │ ├── replacement_values.py │ ├── bootstrap_resources.py │ ├── conftest.py │ ├── __init__.py │ ├── service_bootstrap.py │ └── tests │ ├── test_bucket_deletion_policy.py │ ├── test_bucket_adoption_policy.py │ └── test_bucket.py ├── config ├── iam │ ├── recommended-policy-arn │ └── recommended-inline-policy ├── 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 ├── overlays │ └── namespaced │ │ ├── role.json │ │ ├── role-binding.json │ │ └── kustomization.yaml ├── crd │ ├── kustomization.yaml │ └── common │ │ ├── kustomization.yaml │ │ └── bases │ │ ├── services.k8s.aws_iamroleselectors.yaml │ │ └── services.k8s.aws_fieldexports.yaml ├── controller │ ├── kustomization.yaml │ ├── service.yaml │ └── deployment.yaml └── default │ └── kustomization.yaml ├── NOTICE ├── OWNERS ├── .gitignore ├── templates └── hooks │ └── bucket │ ├── sdk_create_post_set_output.go.tpl │ ├── sdk_read_many_post_set_output.go.tpl │ └── sdk_create_post_build_request.go.tpl ├── apis └── v1alpha1 │ ├── doc.go │ ├── ack-generate-metadata.yaml │ ├── groupversion_info.go │ ├── generator.yaml │ └── bucket.go ├── .github └── workflows │ ├── postsubmit.yaml │ └── create-release.yml ├── metadata.yaml ├── CODE_OF_CONDUCT.md ├── OWNERS_ALIASES ├── helm ├── Chart.yaml ├── templates │ ├── role-reader.yaml │ ├── service-account.yaml │ ├── NOTES.txt │ ├── 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 │ └── deployment.yaml ├── crds │ ├── services.k8s.aws_iamroleselectors.yaml │ └── services.k8s.aws_fieldexports.yaml ├── values.yaml └── values.schema.json ├── Makefile ├── pkg ├── version │ └── version.go └── resource │ ├── bucket │ ├── identifiers.go │ ├── references.go │ ├── manager_factory.go │ ├── resource.go │ ├── tags.go │ ├── descriptor.go │ ├── acl_custom_test.go │ └── acl_custom.go │ └── registry.go ├── README.md ├── olm └── olmconfig.yaml ├── GOVERNANCE.md ├── go.local.mod ├── CONTRIBUTING.md ├── generator.yaml ├── go.mod ├── cmd └── controller │ └── main.go └── LICENSE /test/e2e/bootstrap.yaml: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /test/e2e/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | *.py[cod] 3 | **/bootstrap.pkl -------------------------------------------------------------------------------- /config/iam/recommended-policy-arn: -------------------------------------------------------------------------------- 1 | arn:aws:iam::aws:policy/AmazonS3FullAccess -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | -------------------------------------------------------------------------------- /OWNERS: -------------------------------------------------------------------------------- 1 | # See the OWNERS docs at https://go.k8s.io/owners 2 | 3 | approvers: 4 | - core-ack-team -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.swp 3 | *~ 4 | .idea 5 | /docs/site 6 | bin 7 | build 8 | go.local.sum 9 | -------------------------------------------------------------------------------- /test/e2e/requirements.txt: -------------------------------------------------------------------------------- 1 | acktest @ git+https://github.com/aws-controllers-k8s/test-infra.git@a49300708a1a5586fec36fd28a2494d9cb2288ab 2 | -------------------------------------------------------------------------------- /templates/hooks/bucket/sdk_create_post_set_output.go.tpl: -------------------------------------------------------------------------------- 1 | if err := rm.createPutFields(ctx, desired); err != nil { 2 | return nil, err 3 | } -------------------------------------------------------------------------------- /config/rbac/service-account.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: ack-s3-controller 6 | namespace: ack-system 7 | -------------------------------------------------------------------------------- /config/overlays/namespaced/role.json: -------------------------------------------------------------------------------- 1 | [{"op": "replace", "path": "/kind", "value": "Role"}, 2 | {"op": "add", "path": "/metadata/namespace", "value": "ack-system"}] -------------------------------------------------------------------------------- /test/e2e/resources/bucket.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: s3.services.k8s.aws/v1alpha1 2 | kind: Bucket 3 | metadata: 4 | name: $BUCKET_NAME 5 | spec: 6 | name: $BUCKET_NAME -------------------------------------------------------------------------------- /config/crd/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - common 5 | - bases/s3.services.k8s.aws_buckets.yaml 6 | -------------------------------------------------------------------------------- /apis/v1alpha1/doc.go: -------------------------------------------------------------------------------- 1 | // +k8s:deepcopy-gen=package 2 | // Package v1alpha1 is the v1alpha1 version of the s3.services.k8s.aws API. 3 | // +groupName=s3.services.k8s.aws 4 | package v1alpha1 5 | -------------------------------------------------------------------------------- /test/e2e/resources/bucket_accelerate.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: s3.services.k8s.aws/v1alpha1 2 | kind: Bucket 3 | metadata: 4 | name: $BUCKET_NAME 5 | spec: 6 | name: $BUCKET_NAME 7 | accelerate: 8 | status: Enabled -------------------------------------------------------------------------------- /test/e2e/resources/bucket_versioning.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: s3.services.k8s.aws/v1alpha1 2 | kind: Bucket 3 | metadata: 4 | name: $BUCKET_NAME 5 | spec: 6 | name: $BUCKET_NAME 7 | versioning: 8 | status: Enabled -------------------------------------------------------------------------------- /test/e2e/resources/bucket_request_payment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: s3.services.k8s.aws/v1alpha1 2 | kind: Bucket 3 | metadata: 4 | name: $BUCKET_NAME 5 | spec: 6 | name: $BUCKET_NAME 7 | requestPayment: 8 | payer: Requester -------------------------------------------------------------------------------- /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/bucket_deletion_policy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: s3.services.k8s.aws/v1alpha1 2 | kind: Bucket 3 | metadata: 4 | name: $BUCKET_NAME 5 | annotations: 6 | services.k8s.aws/deletion-policy: $DELETION_POLICY 7 | spec: 8 | name: $BUCKET_NAME -------------------------------------------------------------------------------- /test/e2e/resources/bucket_ownership_controls.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: s3.services.k8s.aws/v1alpha1 2 | kind: Bucket 3 | metadata: 4 | name: $BUCKET_NAME 5 | spec: 6 | name: $BUCKET_NAME 7 | ownershipControls: 8 | rules: 9 | - objectOwnership: ObjectWriter -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /test/e2e/resources/bucket_public_access_block.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: s3.services.k8s.aws/v1alpha1 2 | kind: Bucket 3 | metadata: 4 | name: $BUCKET_NAME 5 | spec: 6 | name: $BUCKET_NAME 7 | publicAccessBlock: 8 | blockPublicACLs: true 9 | blockPublicPolicy: true -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/e2e/resources/bucket_adopt_or_create_not_exist.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: s3.services.k8s.aws/v1alpha1 2 | kind: Bucket 3 | metadata: 4 | name: $RANDOM_BUCKET_NAME 5 | annotations: 6 | services.k8s.aws/adoption-policy: $ADOPTION_POLICY 7 | spec: 8 | name: $RANDOM_BUCKET_NAME 9 | -------------------------------------------------------------------------------- /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/s3-controller 9 | newTag: 1.2.0 10 | -------------------------------------------------------------------------------- /metadata.yaml: -------------------------------------------------------------------------------- 1 | service: 2 | full_name: "Amazon Simple Storage Service" 3 | short_name: "S3" 4 | link: "https://aws.amazon.com/s3/" 5 | documentation: "https://docs.aws.amazon.com/AmazonS3/latest/userguide/Welcome.html" 6 | api_versions: 7 | - api_version: v1alpha1 8 | status: available 9 | -------------------------------------------------------------------------------- /test/e2e/resources/bucket_tagging.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: s3.services.k8s.aws/v1alpha1 2 | kind: Bucket 3 | metadata: 4 | name: $BUCKET_NAME 5 | spec: 6 | name: $BUCKET_NAME 7 | tagging: 8 | tagSet: 9 | - key: "first" 10 | value: "1" 11 | - key: "second" 12 | value: "2" -------------------------------------------------------------------------------- /test/e2e/resources/bucket_website.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: s3.services.k8s.aws/v1alpha1 2 | kind: Bucket 3 | metadata: 4 | name: $BUCKET_NAME 5 | spec: 6 | name: $BUCKET_NAME 7 | website: 8 | indexDocument: 9 | suffix: index.html 10 | errorDocument: 11 | key: error.html 12 | -------------------------------------------------------------------------------- /templates/hooks/bucket/sdk_read_many_post_set_output.go.tpl: -------------------------------------------------------------------------------- 1 | if err := rm.addPutFieldsToSpec(ctx, r, ko); err != nil { 2 | return nil, err 3 | } 4 | 5 | // Set bucket ARN in the output 6 | bucketARN := ackv1alpha1.AWSResourceName(bucketARN(*ko.Spec.Name)) 7 | ko.Status.ACKResourceMetadata.ARN = &bucketARN -------------------------------------------------------------------------------- /test/e2e/resources/bucket_cors.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: s3.services.k8s.aws/v1alpha1 2 | kind: Bucket 3 | metadata: 4 | name: $BUCKET_NAME 5 | spec: 6 | name: $BUCKET_NAME 7 | cors: 8 | corsRules: 9 | - allowedMethods: 10 | - GET 11 | - PUT 12 | allowedOrigins: 13 | - localhost -------------------------------------------------------------------------------- /test/e2e/resources/bucket_logging.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: s3.services.k8s.aws/v1alpha1 2 | kind: Bucket 3 | metadata: 4 | name: $BUCKET_NAME 5 | spec: 6 | name: $BUCKET_NAME 7 | acl: log-delivery-write 8 | logging: 9 | loggingEnabled: 10 | targetBucket: $BUCKET_NAME 11 | targetPrefix: "logging-" -------------------------------------------------------------------------------- /.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 -------------------------------------------------------------------------------- /test/e2e/resources/bucket_adopt.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: s3.services.k8s.aws/v1alpha1 2 | kind: Bucket 3 | metadata: 4 | name: $RANDOM_BUCKET_NAME 5 | annotations: 6 | services.k8s.aws/adoption-policy: $ADOPTION_POLICY 7 | services.k8s.aws/adoption-fields: "$ADOPTION_FIELDS" 8 | services.k8s.aws/deletion-policy: retain 9 | -------------------------------------------------------------------------------- /test/e2e/resources/bucket_encryption.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: s3.services.k8s.aws/v1alpha1 2 | kind: Bucket 3 | metadata: 4 | name: $BUCKET_NAME 5 | spec: 6 | name: $BUCKET_NAME 7 | encryption: 8 | rules: 9 | - bucketKeyEnabled: false 10 | applyServerSideEncryptionByDefault: 11 | sseAlgorithm: AES256 -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/e2e/resources/bucket_adoption_stack.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: s3.services.k8s.aws/v1alpha1 2 | kind: Bucket 3 | metadata: 4 | name: $STACK_BUCKET_NAME 5 | annotations: 6 | services.k8s.aws/adoption-policy: $ADOPTION_POLICY 7 | services.k8s.aws/adoption-fields: "$ADOPTION_FIELDS" 8 | services.k8s.aws/deletion-policy: retain 9 | -------------------------------------------------------------------------------- /config/rbac/role-reader.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | creationTimestamp: null 6 | name: ack-s3-reader 7 | namespace: default 8 | rules: 9 | - apiGroups: 10 | - s3.services.k8s.aws 11 | resources: 12 | - buckets 13 | verbs: 14 | - get 15 | - list 16 | - watch 17 | -------------------------------------------------------------------------------- /config/controller/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: ack-s3-metrics-service 5 | namespace: ack-system 6 | spec: 7 | selector: 8 | app.kubernetes.io/name: ack-s3-controller 9 | ports: 10 | - name: metricsport 11 | port: 8080 12 | targetPort: http 13 | protocol: TCP 14 | type: ClusterIP 15 | -------------------------------------------------------------------------------- /test/e2e/resources/bucket_notification.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: s3.services.k8s.aws/v1alpha1 2 | kind: Bucket 3 | metadata: 4 | name: $BUCKET_NAME 5 | spec: 6 | name: $BUCKET_NAME 7 | notification: 8 | topicConfigurations: 9 | - id: "Publish new objects to SNS" 10 | topicARN: "$NOTIFICATION_TOPIC_ARN" 11 | events: 12 | - "s3:ObjectCreated:Put" -------------------------------------------------------------------------------- /config/rbac/cluster-role-binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: ack-s3-controller-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: ack-s3-controller 9 | subjects: 10 | - kind: ServiceAccount 11 | name: ack-s3-controller 12 | namespace: ack-system 13 | -------------------------------------------------------------------------------- /test/e2e/resources/bucket_lifecycle.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: s3.services.k8s.aws/v1alpha1 2 | kind: Bucket 3 | metadata: 4 | name: $BUCKET_NAME 5 | spec: 6 | name: $BUCKET_NAME 7 | lifecycle: 8 | rules: 9 | - id: "Move to Glacier after sixty days" 10 | prefix: "/logs/" 11 | status: "Enabled" 12 | transitions: 13 | - days: 60 14 | storageClass: "GLACIER" -------------------------------------------------------------------------------- /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: s3-leader-election-rolebinding 7 | roleRef: 8 | apiGroup: rbac.authorization.k8s.io 9 | kind: Role 10 | name: s3-leader-election-role 11 | subjects: 12 | - kind: ServiceAccount 13 | name: ack-s3-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-s3-controller 10 | - path: role-binding.json 11 | target: 12 | group: rbac.authorization.k8s.io 13 | version: v1 14 | kind: ClusterRoleBinding 15 | name: ack-s3-controller-rolebinding -------------------------------------------------------------------------------- /templates/hooks/bucket/sdk_create_post_build_request.go.tpl: -------------------------------------------------------------------------------- 1 | 2 | if rm.awsRegion != "us-east-1" { 3 | // Set default region if not specified 4 | if input.CreateBucketConfiguration == nil || 5 | input.CreateBucketConfiguration.LocationConstraint == "" { 6 | input.CreateBucketConfiguration = &svcsdktypes.CreateBucketConfiguration{ 7 | LocationConstraint: svcsdktypes.BucketLocationConstraint(rm.awsRegion), 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/e2e/resources/bucket_adopt_or_create.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: s3.services.k8s.aws/v1alpha1 2 | kind: Bucket 3 | metadata: 4 | name: $RANDOM_BUCKET_NAME 5 | annotations: 6 | services.k8s.aws/adoption-policy: $ADOPTION_POLICY 7 | services.k8s.aws/adoption-fields: "$ADOPTION_FIELDS" 8 | services.k8s.aws/deletion-policy: retain 9 | spec: 10 | name: $BUCKET_NAME 11 | tagging: 12 | tagSet: 13 | - key: tag_key 14 | value: tag_value 15 | -------------------------------------------------------------------------------- /config/rbac/leader-election-role.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | name: s3-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 | -------------------------------------------------------------------------------- /test/e2e/resources/bucket_policy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: s3.services.k8s.aws/v1alpha1 2 | kind: Bucket 3 | metadata: 4 | name: $BUCKET_NAME 5 | spec: 6 | name: $BUCKET_NAME 7 | policy: > 8 | { 9 | "Version": "2012-10-17", 10 | "Id": "BlockAllObjects", 11 | "Statement": [ 12 | { 13 | "Effect": "Deny", 14 | "Principal": "*", 15 | "Action": "s3:PutObject", 16 | "Resource": "arn:aws:s3:::$BUCKET_NAME/*" 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /apis/v1alpha1/ack-generate-metadata.yaml: -------------------------------------------------------------------------------- 1 | ack_generate_info: 2 | build_date: "2025-11-29T03:43:15Z" 3 | build_hash: 23c7074fa310ad1ccb38946775397c203b49f024 4 | go_version: go1.25.4 5 | version: v0.56.0 6 | api_directory_checksum: 2108338a86d704419192e545c0bfb433bab8c836 7 | api_version: v1alpha1 8 | aws_sdk_go_version: v1.32.6 9 | generator_config_info: 10 | file_checksum: 00a96da29786586c7a3a59666d5546468e616b48 11 | original_file_name: generator.yaml 12 | last_modification: 13 | reason: API generation 14 | -------------------------------------------------------------------------------- /config/rbac/role-writer.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | creationTimestamp: null 6 | name: ack-s3-writer 7 | namespace: default 8 | rules: 9 | - apiGroups: 10 | - s3.services.k8s.aws 11 | resources: 12 | - buckets 13 | verbs: 14 | - create 15 | - delete 16 | - get 17 | - list 18 | - patch 19 | - update 20 | - watch 21 | - apiGroups: 22 | - s3.services.k8s.aws 23 | resources: 24 | - buckets 25 | verbs: 26 | - get 27 | - patch 28 | - update 29 | -------------------------------------------------------------------------------- /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/bucket_replication.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: s3.services.k8s.aws/v1alpha1 2 | kind: Bucket 3 | metadata: 4 | name: $BUCKET_NAME 5 | spec: 6 | name: $BUCKET_NAME 7 | versioning: 8 | status: "Enabled" 9 | replication: 10 | role: "$REPLICATION_ROLE_ARN" 11 | rules: 12 | - id: "Replicate logs into another bucket" 13 | status: "Enabled" 14 | priority: 1 15 | deleteMarkerReplication: 16 | status: "Disabled" 17 | filter: 18 | prefix: "logs/" 19 | destination: 20 | bucket: "arn:aws:s3:::$REPLICATION_BUCKET_NAME" -------------------------------------------------------------------------------- /config/iam/recommended-inline-policy: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Sid": "S3AllPermission", 6 | "Effect": "Allow", 7 | "Action": [ 8 | "s3:*", 9 | "s3-object-lambda:*" 10 | ], 11 | "Resource": "*" 12 | }, 13 | { 14 | "Sid": "S3ReplicationPassRole", 15 | "Condition": { 16 | "StringEquals": { 17 | "iam:PassedToService": "s3.amazonaws.com" 18 | } 19 | }, 20 | "Action": "iam:PassRole", 21 | "Resource": "*", 22 | "Effect": "Allow" 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /helm/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | name: s3-chart 3 | description: A Helm chart for the ACK service controller for Amazon Simple Storage Service (S3) 4 | version: 1.2.0 5 | appVersion: 1.2.0 6 | home: https://github.com/aws-controllers-k8s/s3-controller 7 | icon: https://raw.githubusercontent.com/aws/eks-charts/master/docs/logo/aws.png 8 | sources: 9 | - https://github.com/aws-controllers-k8s/s3-controller 10 | maintainers: 11 | - name: ACK Admins 12 | url: https://github.com/orgs/aws-controllers-k8s/teams/ack-admin 13 | - name: S3 Admins 14 | url: https://github.com/orgs/aws-controllers-k8s/teams/s3-maintainer 15 | keywords: 16 | - aws 17 | - kubernetes 18 | - s3 19 | -------------------------------------------------------------------------------- /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 local-test 15 | 16 | all: test 17 | 18 | test: ## Run code tests 19 | go test -v ./... 20 | 21 | local-test: ## Run code tests using go.local.mod file 22 | go test -modfile=go.local.mod -v ./... 23 | 24 | help: ## Show this help. 25 | @grep -F -h "##" $(MAKEFILE_LIST) | grep -F -v grep | sed -e 's/\\$$//' \ 26 | | awk -F'[:#]' '{print $$1 = sprintf("%-30s", $$1), $$4}' 27 | -------------------------------------------------------------------------------- /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-s3-controller.app.fullname" . }}-reader 7 | namespace: {{ .Release.Namespace }} 8 | labels: 9 | app.kubernetes.io/name: {{ include "ack-s3-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-s3-controller.app.name" . }} 14 | helm.sh/chart: {{ include "ack-s3-controller.chart.name-version" . }} 15 | rules: 16 | - apiGroups: 17 | - s3.services.k8s.aws 18 | resources: 19 | - buckets 20 | verbs: 21 | - get 22 | - list 23 | - watch 24 | -------------------------------------------------------------------------------- /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-s3-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-s3-controller.app.name" . }} 11 | helm.sh/chart: {{ include "ack-s3-controller.chart.name-version" . }} 12 | name: {{ include "ack-s3-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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /helm/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | {{ .Chart.Name }} has been installed. 2 | This chart deploys "public.ecr.aws/aws-controllers-k8s/s3-controller:1.2.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 Simple Storage Service (S3) 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ACK service controller for Amazon Simple Storage Service (S3) 2 | 3 | This repository contains source code for the AWS Controllers for Kubernetes 4 | (ACK) service controller for Amazon Simple Storage Service (S3). 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 | -------------------------------------------------------------------------------- /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-s3-controller.app.fullname" . }}-writer 7 | namespace: {{ .Release.Namespace }} 8 | labels: 9 | app.kubernetes.io/name: {{ include "ack-s3-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-s3-controller.app.name" . }} 14 | helm.sh/chart: {{ include "ack-s3-controller.chart.name-version" . }} 15 | rules: 16 | - apiGroups: 17 | - s3.services.k8s.aws 18 | resources: 19 | - buckets 20 | verbs: 21 | - create 22 | - delete 23 | - get 24 | - list 25 | - patch 26 | - update 27 | - watch 28 | - apiGroups: 29 | - s3.services.k8s.aws 30 | resources: 31 | - buckets 32 | verbs: 33 | - get 34 | - patch 35 | - update 36 | -------------------------------------------------------------------------------- /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 | """Cleans up the resources created by the S3 bootstrapping process. 14 | """ 15 | 16 | import logging 17 | from pathlib import Path 18 | 19 | from acktest.bootstrapping import Resources 20 | from e2e import bootstrap_directory 21 | 22 | def service_cleanup(): 23 | logging.getLogger().setLevel(logging.INFO) 24 | 25 | resources = Resources.deserialize(bootstrap_directory) 26 | resources.cleanup() 27 | 28 | if __name__ == "__main__": 29 | service_cleanup() -------------------------------------------------------------------------------- /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-s3-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-s3-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-s3-controller.app.name" . }} 17 | helm.sh/chart: {{ include "ack-s3-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-s3-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-s3-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-s3-controller.app.name" . }} 17 | helm.sh/chart: {{ include "ack-s3-controller.chart.name-version" . }} 18 | roleRef: 19 | apiGroup: rbac.authorization.k8s.io 20 | kind: Role 21 | name: {{ include "ack-s3-controller.app.fullname" . }}-leaderelection 22 | subjects: 23 | - kind: ServiceAccount 24 | name: {{ include "ack-s3-controller.service-account.name" . }} 25 | namespace: {{ .Release.Namespace }}{{- end }} 26 | -------------------------------------------------------------------------------- /config/rbac/cluster-role-controller.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: ack-s3-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 | - s3.services.k8s.aws 27 | resources: 28 | - buckets 29 | verbs: 30 | - create 31 | - delete 32 | - get 33 | - list 34 | - patch 35 | - update 36 | - watch 37 | - apiGroups: 38 | - s3.services.k8s.aws 39 | resources: 40 | - buckets/status 41 | verbs: 42 | - get 43 | - patch 44 | - update 45 | - apiGroups: 46 | - services.k8s.aws 47 | resources: 48 | - fieldexports 49 | - iamroleselectors 50 | verbs: 51 | - create 52 | - delete 53 | - get 54 | - list 55 | - patch 56 | - update 57 | - watch 58 | - apiGroups: 59 | - services.k8s.aws 60 | resources: 61 | - fieldexports/status 62 | - iamroleselectors/status 63 | verbs: 64 | - get 65 | - patch 66 | - update 67 | -------------------------------------------------------------------------------- /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-s3-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-s3-controller.app.name" . }} 13 | helm.sh/chart: {{ include "ack-s3-controller.chart.name-version" . }} 14 | spec: 15 | selector: 16 | app.kubernetes.io/name: {{ include "ack-s3-controller.app.name" . }} 17 | app.kubernetes.io/instance: {{ .Release.Name }} 18 | app.kubernetes.io/managed-by: Helm 19 | k8s-app: {{ include "ack-s3-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 | -------------------------------------------------------------------------------- /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 | """Stores the values used by each of the integration tests for replacing the 14 | S3-specific test variables. 15 | """ 16 | 17 | from e2e.bootstrap_resources import get_bootstrap_resources 18 | 19 | REPLACEMENT_VALUES = { 20 | "REPLICATION_ROLE_ARN": get_bootstrap_resources().ReplicationRole.arn, 21 | "ADOPTION_BUCKET_NAME": get_bootstrap_resources().AdoptionBucket.name, 22 | "REPLICATION_BUCKET_NAME": get_bootstrap_resources().ReplicationBucket.name, 23 | "NOTIFICATION_TOPIC_ARN": get_bootstrap_resources().NotificationTopic.arn, 24 | "STACK_BUCKET_NAME": get_bootstrap_resources().StackBucket.template["Resources"]["MyS3Bucket"]["Properties"]["BucketName"], 25 | } 26 | -------------------------------------------------------------------------------- /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: "s3.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 | -------------------------------------------------------------------------------- /helm/templates/caches-role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: {{ include "ack-s3-controller.app.fullname" . }}-namespaces-cache 5 | labels: 6 | app.kubernetes.io/name: {{ include "ack-s3-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-s3-controller.app.name" . }} 11 | helm.sh/chart: {{ include "ack-s3-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-s3-controller.app.fullname" . }}-configmaps-cache 26 | namespace: {{ .Release.Namespace }} 27 | labels: 28 | app.kubernetes.io/name: {{ include "ack-s3-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-s3-controller.app.name" . }} 33 | helm.sh/chart: {{ include "ack-s3-controller.chart.name-version" . }} 34 | rules: 35 | - apiGroups: 36 | - "" 37 | resources: 38 | - configmaps 39 | verbs: 40 | - get 41 | - list 42 | - watch -------------------------------------------------------------------------------- /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 | from acktest.bootstrapping import Resources 20 | from acktest.bootstrapping.iam import Role 21 | from acktest.bootstrapping.s3 import Bucket 22 | from acktest.bootstrapping.sns import Topic 23 | from acktest.bootstrapping.cloudformation import Stack 24 | from e2e import bootstrap_directory 25 | 26 | @dataclass 27 | class BootstrapResources(Resources): 28 | ReplicationBucket: Bucket 29 | AdoptionBucket: Bucket 30 | ReplicationRole: Role 31 | NotificationTopic: Topic 32 | StackBucket: Stack 33 | 34 | _bootstrap_resources = None 35 | 36 | def get_bootstrap_resources(bootstrap_file_name: str = "bootstrap.pkl") -> BootstrapResources: 37 | global _bootstrap_resources 38 | if _bootstrap_resources is None: 39 | _bootstrap_resources = BootstrapResources.deserialize(bootstrap_directory, bootstrap_file_name=bootstrap_file_name) 40 | return _bootstrap_resources 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 | 20 | 21 | def pytest_addoption(parser): 22 | parser.addoption("--runslow", action="store_true", default=False, help="run slow tests") 23 | 24 | 25 | def pytest_configure(config): 26 | config.addinivalue_line( 27 | "markers", "service(arg): mark test associated with a given service" 28 | ) 29 | config.addinivalue_line( 30 | "markers", "slow: mark test as slow to run" 31 | ) 32 | 33 | def pytest_collection_modifyitems(config, items): 34 | if config.getoption("--runslow"): 35 | return 36 | skip_slow = pytest.mark.skip(reason="need --runslow option to run") 37 | for item in items: 38 | if "slow" in item.keywords: 39 | item.add_marker(skip_slow) 40 | 41 | # Provide a k8s client to interact with the integration test cluster 42 | @pytest.fixture(scope='class') 43 | def k8s_client(): 44 | return k8s._get_k8s_api_client() 45 | 46 | @pytest.fixture(scope='module') 47 | def s3_client(): 48 | return boto3.client('s3') 49 | 50 | @pytest.fixture(scope='module') 51 | def s3_resource(): 52 | return boto3.resource('s3') -------------------------------------------------------------------------------- /helm/templates/cluster-role-controller.yaml: -------------------------------------------------------------------------------- 1 | {{ $labels := .Values.role.labels }} 2 | {{ $appVersion := .Chart.AppVersion | quote }} 3 | {{ $rbacRules := include "ack-s3-controller.rbac-rules" . }} 4 | {{ $fullname := include "ack-s3-controller.app.fullname" . }} 5 | {{ $chartVersion := include "ack-s3-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-s3-controller.app.fullname" . }} 11 | labels: 12 | app.kubernetes.io/name: {{ include "ack-s3-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-s3-controller.app.name" . }} 17 | helm.sh/chart: {{ include "ack-s3-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-s3-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-s3-controller.app.fullname" . }}-namespaces-cache 5 | labels: 6 | app.kubernetes.io/name: {{ include "ack-s3-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-s3-controller.app.name" . }} 11 | helm.sh/chart: {{ include "ack-s3-controller.chart.name-version" . }} 12 | roleRef: 13 | kind: ClusterRole 14 | apiGroup: rbac.authorization.k8s.io 15 | name: {{ include "ack-s3-controller.app.fullname" . }}-namespaces-cache 16 | subjects: 17 | - kind: ServiceAccount 18 | name: {{ include "ack-s3-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-s3-controller.app.fullname" . }}-configmaps-cache 25 | namespace: {{ .Release.Namespace }} 26 | labels: 27 | app.kubernetes.io/name: {{ include "ack-s3-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-s3-controller.app.name" . }} 32 | helm.sh/chart: {{ include "ack-s3-controller.chart.name-version" . }} 33 | roleRef: 34 | kind: Role 35 | apiGroup: rbac.authorization.k8s.io 36 | name: {{ include "ack-s3-controller.app.fullname" . }}-configmaps-cache 37 | subjects: 38 | - kind: ServiceAccount 39 | name: {{ include "ack-s3-controller.service-account.name" . }} 40 | namespace: {{ .Release.Namespace }} 41 | -------------------------------------------------------------------------------- /olm/olmconfig.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | annotations: 3 | capabilityLevel: Basic Install 4 | shortDescription: AWS S3 controller is a service controller for managing S3 resources 5 | in Kubernetes 6 | displayName: AWS Controllers for Kubernetes - Amazon S3 7 | description: |- 8 | Manage Amazon Simple Storage Service (S3) resources in AWS from within your Kubernetes cluster. 9 | 10 | 11 | **About Amazon S3** 12 | 13 | 14 | Amazon Simple Storage Service (Amazon S3) is an object storage service that offers industry-leading scalability, data availability, security, and performance. This means customers of all sizes and industries can use it to store and protect any amount of data for a range of use cases, such as data lakes, websites, mobile applications, backup and restore, archive, enterprise applications, IoT devices, and big data analytics. Amazon S3 provides easy-to-use management features so you can organize your data and configure finely-tuned access controls to meet your specific business, organizational, and compliance requirements. Amazon S3 is designed for 99.999999999% (11 9s) of durability, and stores data for millions of applications for companies all around the world. 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: Bucket 30 | spec: '{}' 31 | maintainers: 32 | - name: "s3 maintainer team" 33 | email: "ack-maintainers@amazon.com" 34 | links: 35 | - name: Amazon S3 Developer Resources 36 | url: https://aws.amazon.com/s3/developer-resources/ 37 | -------------------------------------------------------------------------------- /pkg/resource/bucket/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 bucket 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 | -------------------------------------------------------------------------------- /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 pytest 15 | from typing import Dict, Any 16 | from pathlib import Path 17 | 18 | from acktest.k8s.resource import load_and_create_resource 19 | from acktest.resources import load_resource_file 20 | 21 | SERVICE_NAME = "s3" 22 | CRD_GROUP = "s3.services.k8s.aws" 23 | CRD_VERSION = "v1alpha1" 24 | 25 | # PyTest marker for the current service 26 | service_marker = pytest.mark.service(arg=SERVICE_NAME) 27 | 28 | bootstrap_directory = Path(__file__).parent 29 | resource_directory = Path(__file__).parent / "resources" 30 | def load_s3_resource(resource_name: str, additional_replacements: Dict[str, Any] = {}): 31 | """ Overrides the default `load_resource_file` to access the specific resources 32 | directory for the current service. 33 | """ 34 | return load_resource_file(resource_directory, resource_name, additional_replacements=additional_replacements) 35 | 36 | def create_s3_resource( 37 | resource_plural, resource_name, spec_file, replacements, namespace="default" 38 | ): 39 | """ 40 | Wrapper around k8s.load_and_create_resource to create an S3 resource 41 | """ 42 | 43 | reference, spec, resource = load_and_create_resource( 44 | resource_directory, 45 | CRD_GROUP, 46 | CRD_VERSION, 47 | resource_plural, 48 | resource_name, 49 | spec_file, 50 | replacements, 51 | namespace, 52 | ) 53 | 54 | return reference, spec, resource 55 | -------------------------------------------------------------------------------- /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-s3-controller.app.fullname" . }}-rolebinding 6 | labels: 7 | app.kubernetes.io/name: {{ include "ack-s3-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-s3-controller.app.name" . }} 12 | helm.sh/chart: {{ include "ack-s3-controller.chart.name-version" . }} 13 | roleRef: 14 | kind: ClusterRole 15 | apiGroup: rbac.authorization.k8s.io 16 | name: {{ include "ack-s3-controller.app.fullname" . }} 17 | subjects: 18 | - kind: ServiceAccount 19 | name: {{ include "ack-s3-controller.service-account.name" . }} 20 | namespace: {{ .Release.Namespace }} 21 | {{ else if eq .Values.installScope "namespace" }} 22 | {{ $wn := include "ack-s3-controller.watch-namespace" . }} 23 | {{ $namespaces := split "," $wn }} 24 | {{ $fullname := include "ack-s3-controller.app.fullname" . }} 25 | {{ $releaseNamespace := .Release.Namespace }} 26 | {{ $serviceAccountName := include "ack-s3-controller.service-account.name" . }} 27 | {{ $chartVersion := include "ack-s3-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/bucket/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 bucket 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/s3-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.Bucket) error { 56 | return nil 57 | } 58 | -------------------------------------------------------------------------------- /go.local.mod: -------------------------------------------------------------------------------- 1 | module github.com/aws-controllers-k8s/s3-controller 2 | 3 | go 1.17 4 | 5 | replace github.com/aws-controllers-k8s/runtime => ../runtime 6 | 7 | require ( 8 | github.com/aws-controllers-k8s/runtime v0.0.0 9 | github.com/aws/aws-sdk-go v1.42.0 10 | github.com/go-logr/logr v1.2.0 11 | github.com/spf13/pflag v1.0.5 12 | github.com/stretchr/testify v1.7.0 13 | k8s.io/api v0.23.0 14 | k8s.io/apimachinery v0.23.0 15 | k8s.io/client-go v0.23.0 16 | sigs.k8s.io/controller-runtime v0.11.0 17 | ) 18 | 19 | require ( 20 | github.com/beorn7/perks v1.0.1 // indirect 21 | github.com/cespare/xxhash/v2 v2.1.1 // indirect 22 | github.com/davecgh/go-spew v1.1.1 // indirect 23 | github.com/evanphx/json-patch v4.12.0+incompatible // indirect 24 | github.com/fsnotify/fsnotify v1.5.1 // indirect 25 | github.com/go-logr/zapr v1.2.0 // indirect 26 | github.com/gogo/protobuf v1.3.2 // indirect 27 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 28 | github.com/golang/protobuf v1.5.2 // indirect 29 | github.com/google/go-cmp v0.5.5 // indirect 30 | github.com/google/gofuzz v1.1.0 // indirect 31 | github.com/google/uuid v1.1.2 // indirect 32 | github.com/googleapis/gnostic v0.5.5 // indirect 33 | github.com/imdario/mergo v0.3.12 // indirect 34 | github.com/itchyny/gojq v0.12.6 // indirect 35 | github.com/itchyny/timefmt-go v0.1.3 // indirect 36 | github.com/jaypipes/envutil v1.0.0 // indirect 37 | github.com/jmespath/go-jmespath v0.4.0 // indirect 38 | github.com/json-iterator/go v1.1.12 // indirect 39 | github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect 40 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 41 | github.com/modern-go/reflect2 v1.0.2 // indirect 42 | github.com/pkg/errors v0.9.1 // indirect 43 | github.com/pmezard/go-difflib v1.0.0 // indirect 44 | github.com/prometheus/client_golang v1.11.0 // indirect 45 | github.com/prometheus/client_model v0.2.0 // indirect 46 | github.com/prometheus/common v0.28.0 // indirect 47 | github.com/prometheus/procfs v0.6.0 // indirect 48 | github.com/stretchr/objx v0.2.0 // indirect 49 | go.uber.org/atomic v1.7.0 // indirect 50 | go.uber.org/multierr v1.6.0 // indirect 51 | go.uber.org/zap v1.19.1 // indirect 52 | golang.org/x/net v0.0.0-20210825183410-e898025ed96a // indirect 53 | golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f // indirect 54 | golang.org/x/sys v0.0.0-20211124211545-fe61309f8881 // indirect 55 | golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect 56 | golang.org/x/text v0.3.7 // indirect 57 | golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect 58 | gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect 59 | google.golang.org/appengine v1.6.7 // indirect 60 | google.golang.org/protobuf v1.27.1 // indirect 61 | gopkg.in/inf.v0 v0.9.1 // indirect 62 | gopkg.in/yaml.v2 v2.4.0 // indirect 63 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect 64 | k8s.io/apiextensions-apiserver v0.23.0 // indirect 65 | k8s.io/component-base v0.23.0 // indirect 66 | k8s.io/klog/v2 v2.30.0 // indirect 67 | k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect 68 | k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b // indirect 69 | sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect 70 | sigs.k8s.io/structured-merge-diff/v4 v4.2.0 // indirect 71 | sigs.k8s.io/yaml v1.3.0 // indirect 72 | ) 73 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /pkg/resource/bucket/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 bucket 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/s3-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 | -------------------------------------------------------------------------------- /helm/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* The name of the application this chart installs */}} 2 | {{- define "ack-s3-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-s3-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-s3-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-s3-controller.service-account.name" -}} 31 | {{ default "default" .Values.serviceAccount.name }} 32 | {{- end -}} 33 | 34 | {{- define "ack-s3-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-s3-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-s3-controller.aws.credentials.path" -}} 47 | {{ $secret_mount_path := include "ack-s3-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-s3-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 | - s3.services.k8s.aws 74 | resources: 75 | - buckets 76 | verbs: 77 | - create 78 | - delete 79 | - get 80 | - list 81 | - patch 82 | - update 83 | - watch 84 | - apiGroups: 85 | - s3.services.k8s.aws 86 | resources: 87 | - buckets/status 88 | verbs: 89 | - get 90 | - patch 91 | - update 92 | - apiGroups: 93 | - services.k8s.aws 94 | resources: 95 | - fieldexports 96 | - iamroleselectors 97 | verbs: 98 | - create 99 | - delete 100 | - get 101 | - list 102 | - patch 103 | - update 104 | - watch 105 | - apiGroups: 106 | - services.k8s.aws 107 | resources: 108 | - fieldexports/status 109 | - iamroleselectors/status 110 | verbs: 111 | - get 112 | - patch 113 | - update 114 | {{- end }} 115 | 116 | {{/* Convert k/v map to string like: "key1=value1,key2=value2,..." */}} 117 | {{- define "ack-s3-controller.feature-gates" -}} 118 | {{- $list := list -}} 119 | {{- range $k, $v := .Values.featureGates -}} 120 | {{- $list = append $list (printf "%s=%s" $k ( $v | toString)) -}} 121 | {{- end -}} 122 | {{ join "," $list }} 123 | {{- end -}} 124 | -------------------------------------------------------------------------------- /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-s3-controller 10 | namespace: ack-system 11 | labels: 12 | app.kubernetes.io/name: ack-s3-controller 13 | app.kubernetes.io/part-of: ack-system 14 | spec: 15 | selector: 16 | matchLabels: 17 | app.kubernetes.io/name: ack-s3-controller 18 | replicas: 1 19 | template: 20 | metadata: 21 | labels: 22 | app.kubernetes.io/name: ack-s3-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-s3-controller 110 | hostIPC: false 111 | hostPID: false 112 | hostNetwork: false 113 | dnsPolicy: ClusterFirst 114 | -------------------------------------------------------------------------------- /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 | """Bootstraps the resources required to run the S3 integration tests. 14 | """ 15 | 16 | import logging 17 | import json 18 | from pathlib import Path 19 | 20 | from acktest.resources import random_suffix_name 21 | from acktest.bootstrapping import Resources, BootstrapFailureException 22 | from acktest.bootstrapping.iam import Role, UserPolicies 23 | from acktest.bootstrapping.s3 import Bucket 24 | from acktest.bootstrapping.sns import Topic 25 | from acktest.bootstrapping.cloudformation import Stack 26 | from e2e import bootstrap_directory 27 | from e2e.bootstrap_resources import BootstrapResources 28 | 29 | 30 | def service_bootstrap() -> Resources: 31 | logging.getLogger().setLevel(logging.INFO) 32 | 33 | replication_policy = json.dumps({ 34 | "Version": "2012-10-17", 35 | "Statement": [ 36 | { 37 | "Effect": "Allow", 38 | "Action": [ 39 | "s3:ReplicateObject", 40 | "s3:GetObjectVersionTagging", 41 | "s3:ReplicateTags", 42 | "s3:GetObjectVersionAcl", 43 | "s3:ListBucket", 44 | "s3:GetReplicationConfiguration", 45 | "s3:ReplicateDelete", 46 | "s3:GetObjectVersion" 47 | ], 48 | "Resource": "*" 49 | } 50 | ] 51 | }) 52 | 53 | notification_policy = json.dumps({ 54 | "Version": "2008-10-17", 55 | "Statement": [ 56 | { 57 | "Effect": "Allow", 58 | "Principal": { 59 | "Service": "s3.amazonaws.com" 60 | }, 61 | "Action": "SNS:Publish", 62 | "Resource": "*" 63 | } 64 | ] 65 | }) 66 | 67 | stack_bucket_name = random_suffix_name("stack-bucket", 24) 68 | template = { 69 | "AWSTemplateFormatVersion": "2010-09-09", 70 | "Resources": { 71 | "MyS3Bucket": { 72 | "Type": "AWS::S3::Bucket", 73 | "Properties": { 74 | "BucketName": stack_bucket_name, 75 | "VersioningConfiguration": { 76 | "Status": "Enabled" 77 | } 78 | } 79 | } 80 | } 81 | } 82 | 83 | resources = BootstrapResources( 84 | ReplicationBucket=Bucket("ack-s3-replication", enable_versioning=True), 85 | AdoptionBucket=Bucket("ack-s3-annotation-adoption", enable_versioning=True), 86 | ReplicationRole=Role("ack-s3-replication-role", "s3.amazonaws.com", 87 | user_policies=UserPolicies("ack-s3-replication-policy", [replication_policy]) 88 | ), 89 | NotificationTopic=Topic("ack-s3-notification", policy=notification_policy), 90 | StackBucket=Stack(name_prefix=stack_bucket_name, template=template) 91 | ) 92 | 93 | try: 94 | resources.bootstrap() 95 | except BootstrapFailureException as ex: 96 | exit(254) 97 | 98 | return resources 99 | 100 | if __name__ == "__main__": 101 | config = service_bootstrap() 102 | # Write config to current directory by default 103 | config.serialize(bootstrap_directory) -------------------------------------------------------------------------------- /pkg/resource/bucket/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 bucket 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/s3-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.Bucket 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.Name = &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 | primaryKey, ok := fields["name"] 101 | if !ok { 102 | return ackerrors.NewTerminalError(fmt.Errorf("required field missing: name")) 103 | } 104 | r.ko.Spec.Name = &primaryKey 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/bucket/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 bucket 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/s3-controller/apis/v1alpha1" 25 | ) 26 | 27 | var ( 28 | _ = svcapitypes.Bucket{} 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 | -------------------------------------------------------------------------------- /generator.yaml: -------------------------------------------------------------------------------- 1 | ignore: 2 | resource_names: 3 | - Object 4 | - MultipartUpload 5 | - Session 6 | shape_names: 7 | # These shapes are structs with no members... 8 | - SSES3 9 | - SimplePrefix 10 | field_paths: 11 | # We cannot support MFA, so if it is set we cannot unset 12 | - "VersioningConfiguration.MFADelete" 13 | # This subfield struct has no members... 14 | - "NotificationConfiguration.EventBridgeConfiguration" 15 | - CreateBucketInput.CreateBucketConfiguration.Location 16 | - CreateBucketInput.CreateBucketConfiguration.Bucket 17 | - LoggingEnabled.TargetObjectKeyFormat 18 | resources: 19 | Bucket: 20 | fields: 21 | Name: 22 | is_primary_key: true 23 | is_required: true 24 | is_immutable: true 25 | from: 26 | operation: CreateBucket 27 | path: Bucket 28 | Accelerate: 29 | from: 30 | operation: PutBucketAccelerateConfiguration 31 | path: AccelerateConfiguration 32 | Analytics: 33 | custom_field: 34 | list_of: AnalyticsConfiguration 35 | CORS: 36 | from: 37 | operation: PutBucketCors 38 | path: CORSConfiguration 39 | Encryption: 40 | from: 41 | operation: PutBucketEncryption 42 | path: ServerSideEncryptionConfiguration 43 | IntelligentTiering: 44 | custom_field: 45 | list_of: IntelligentTieringConfiguration 46 | Inventory: 47 | custom_field: 48 | list_of: InventoryConfiguration 49 | Lifecycle: 50 | from: 51 | operation: PutBucketLifecycleConfiguration 52 | path: LifecycleConfiguration 53 | Logging: 54 | from: 55 | operation: PutBucketLogging 56 | path: BucketLoggingStatus 57 | Logging.LoggingEnabled.TargetGrants.Grantee.URI: 58 | # Forcing CRD field to "uRI" to avoid breaking change following 59 | # fix in aws-controller-k8s/pkg dependency for URI -> uri. 60 | go_tag: json:"uRI,omitempty" 61 | Metrics: 62 | custom_field: 63 | list_of: MetricsConfiguration 64 | Notification: 65 | from: 66 | operation: PutBucketNotificationConfiguration 67 | path: NotificationConfiguration 68 | OwnershipControls: 69 | from: 70 | operation: PutBucketOwnershipControls 71 | path: OwnershipControls 72 | Policy: 73 | from: 74 | operation: PutBucketPolicy 75 | path: Policy 76 | PublicAccessBlock: 77 | from: 78 | operation: PutPublicAccessBlock 79 | path: PublicAccessBlockConfiguration 80 | Replication: 81 | from: 82 | operation: PutBucketReplication 83 | path: ReplicationConfiguration 84 | RequestPayment: 85 | from: 86 | operation: PutBucketRequestPayment 87 | path: RequestPaymentConfiguration 88 | Tagging: 89 | from: 90 | operation: PutBucketTagging 91 | path: Tagging 92 | Versioning: 93 | from: 94 | operation: PutBucketVersioning 95 | path: VersioningConfiguration 96 | Website: 97 | from: 98 | operation: PutBucketWebsite 99 | path: WebsiteConfiguration 100 | exceptions: 101 | errors: 102 | 404: 103 | code: NoSuchBucket 104 | terminal_codes: 105 | - PermanentRedirect 106 | - InvalidLocationConstraint 107 | - MalformedXML 108 | - IllegalLocationConstraintException 109 | hooks: 110 | delta_pre_compare: 111 | code: customPreCompare(a, b) 112 | sdk_create_post_set_output: 113 | template_path: hooks/bucket/sdk_create_post_set_output.go.tpl 114 | sdk_read_many_post_set_output: 115 | template_path: hooks/bucket/sdk_read_many_post_set_output.go.tpl 116 | sdk_create_post_build_request: 117 | template_path: hooks/bucket/sdk_create_post_build_request.go.tpl 118 | update_operation: 119 | custom_method_name: customUpdateBucket 120 | renames: 121 | operations: 122 | CreateBucket: 123 | input_fields: 124 | Bucket: Name 125 | DeleteBucket: 126 | input_fields: 127 | Bucket: Name 128 | list_operation: 129 | match_fields: 130 | - Name 131 | tags: 132 | path: Tagging.TagSet 133 | -------------------------------------------------------------------------------- /apis/v1alpha1/generator.yaml: -------------------------------------------------------------------------------- 1 | ignore: 2 | resource_names: 3 | - Object 4 | - MultipartUpload 5 | - Session 6 | shape_names: 7 | # These shapes are structs with no members... 8 | - SSES3 9 | - SimplePrefix 10 | field_paths: 11 | # We cannot support MFA, so if it is set we cannot unset 12 | - "VersioningConfiguration.MFADelete" 13 | # This subfield struct has no members... 14 | - "NotificationConfiguration.EventBridgeConfiguration" 15 | - CreateBucketInput.CreateBucketConfiguration.Location 16 | - CreateBucketInput.CreateBucketConfiguration.Bucket 17 | - LoggingEnabled.TargetObjectKeyFormat 18 | resources: 19 | Bucket: 20 | fields: 21 | Name: 22 | is_primary_key: true 23 | is_required: true 24 | is_immutable: true 25 | from: 26 | operation: CreateBucket 27 | path: Bucket 28 | Accelerate: 29 | from: 30 | operation: PutBucketAccelerateConfiguration 31 | path: AccelerateConfiguration 32 | Analytics: 33 | custom_field: 34 | list_of: AnalyticsConfiguration 35 | CORS: 36 | from: 37 | operation: PutBucketCors 38 | path: CORSConfiguration 39 | Encryption: 40 | from: 41 | operation: PutBucketEncryption 42 | path: ServerSideEncryptionConfiguration 43 | IntelligentTiering: 44 | custom_field: 45 | list_of: IntelligentTieringConfiguration 46 | Inventory: 47 | custom_field: 48 | list_of: InventoryConfiguration 49 | Lifecycle: 50 | from: 51 | operation: PutBucketLifecycleConfiguration 52 | path: LifecycleConfiguration 53 | Logging: 54 | from: 55 | operation: PutBucketLogging 56 | path: BucketLoggingStatus 57 | Logging.LoggingEnabled.TargetGrants.Grantee.URI: 58 | # Forcing CRD field to "uRI" to avoid breaking change following 59 | # fix in aws-controller-k8s/pkg dependency for URI -> uri. 60 | go_tag: json:"uRI,omitempty" 61 | Metrics: 62 | custom_field: 63 | list_of: MetricsConfiguration 64 | Notification: 65 | from: 66 | operation: PutBucketNotificationConfiguration 67 | path: NotificationConfiguration 68 | OwnershipControls: 69 | from: 70 | operation: PutBucketOwnershipControls 71 | path: OwnershipControls 72 | Policy: 73 | from: 74 | operation: PutBucketPolicy 75 | path: Policy 76 | PublicAccessBlock: 77 | from: 78 | operation: PutPublicAccessBlock 79 | path: PublicAccessBlockConfiguration 80 | Replication: 81 | from: 82 | operation: PutBucketReplication 83 | path: ReplicationConfiguration 84 | RequestPayment: 85 | from: 86 | operation: PutBucketRequestPayment 87 | path: RequestPaymentConfiguration 88 | Tagging: 89 | from: 90 | operation: PutBucketTagging 91 | path: Tagging 92 | Versioning: 93 | from: 94 | operation: PutBucketVersioning 95 | path: VersioningConfiguration 96 | Website: 97 | from: 98 | operation: PutBucketWebsite 99 | path: WebsiteConfiguration 100 | exceptions: 101 | errors: 102 | 404: 103 | code: NoSuchBucket 104 | terminal_codes: 105 | - PermanentRedirect 106 | - InvalidLocationConstraint 107 | - MalformedXML 108 | - IllegalLocationConstraintException 109 | hooks: 110 | delta_pre_compare: 111 | code: customPreCompare(a, b) 112 | sdk_create_post_set_output: 113 | template_path: hooks/bucket/sdk_create_post_set_output.go.tpl 114 | sdk_read_many_post_set_output: 115 | template_path: hooks/bucket/sdk_read_many_post_set_output.go.tpl 116 | sdk_create_post_build_request: 117 | template_path: hooks/bucket/sdk_create_post_build_request.go.tpl 118 | update_operation: 119 | custom_method_name: customUpdateBucket 120 | renames: 121 | operations: 122 | CreateBucket: 123 | input_fields: 124 | Bucket: Name 125 | DeleteBucket: 126 | input_fields: 127 | Bucket: Name 128 | list_operation: 129 | match_fields: 130 | - Name 131 | tags: 132 | path: Tagging.TagSet 133 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/aws-controllers-k8s/s3-controller 2 | 3 | go 1.24.0 4 | 5 | toolchain go1.24.1 6 | 7 | require ( 8 | github.com/aws-controllers-k8s/runtime v0.56.0 9 | github.com/aws/aws-sdk-go v1.49.0 10 | github.com/aws/aws-sdk-go-v2 v1.34.0 11 | github.com/aws/aws-sdk-go-v2/service/s3 v1.74.1 12 | github.com/aws/smithy-go v1.22.2 13 | github.com/go-logr/logr v1.4.2 14 | github.com/pkg/errors v0.9.1 15 | github.com/spf13/pflag v1.0.5 16 | github.com/stretchr/testify v1.9.0 17 | k8s.io/api v0.32.1 18 | k8s.io/apimachinery v0.32.1 19 | k8s.io/client-go v0.32.1 20 | sigs.k8s.io/controller-runtime v0.20.4 21 | ) 22 | 23 | require ( 24 | github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.8 // indirect 25 | github.com/aws/aws-sdk-go-v2/config v1.28.6 // indirect 26 | github.com/aws/aws-sdk-go-v2/credentials v1.17.47 // indirect 27 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.21 // indirect 28 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.29 // indirect 29 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.29 // indirect 30 | github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect 31 | github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.29 // indirect 32 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 // indirect 33 | github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.5.3 // indirect 34 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.10 // indirect 35 | github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.10 // indirect 36 | github.com/aws/aws-sdk-go-v2/service/sso v1.24.7 // indirect 37 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.6 // indirect 38 | github.com/aws/aws-sdk-go-v2/service/sts v1.33.2 // indirect 39 | github.com/beorn7/perks v1.0.1 // indirect 40 | github.com/cenkalti/backoff/v4 v4.3.0 // indirect 41 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 42 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 43 | github.com/emicklei/go-restful/v3 v3.11.0 // indirect 44 | github.com/evanphx/json-patch/v5 v5.9.11 // indirect 45 | github.com/fsnotify/fsnotify v1.7.0 // indirect 46 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 47 | github.com/go-logr/zapr v1.3.0 // indirect 48 | github.com/go-openapi/jsonpointer v0.21.0 // indirect 49 | github.com/go-openapi/jsonreference v0.20.2 // indirect 50 | github.com/go-openapi/swag v0.23.0 // indirect 51 | github.com/gogo/protobuf v1.3.2 // indirect 52 | github.com/golang/protobuf v1.5.4 // indirect 53 | github.com/google/btree v1.1.3 // indirect 54 | github.com/google/gnostic-models v0.6.8 // indirect 55 | github.com/google/go-cmp v0.6.0 // indirect 56 | github.com/google/gofuzz v1.2.0 // indirect 57 | github.com/google/uuid v1.6.0 // indirect 58 | github.com/itchyny/gojq v0.12.6 // indirect 59 | github.com/itchyny/timefmt-go v0.1.3 // indirect 60 | github.com/jaypipes/envutil v1.0.0 // indirect 61 | github.com/josharian/intern v1.0.0 // indirect 62 | github.com/json-iterator/go v1.1.12 // indirect 63 | github.com/mailru/easyjson v0.7.7 // indirect 64 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 65 | github.com/modern-go/reflect2 v1.0.2 // indirect 66 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 67 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 68 | github.com/prometheus/client_golang v1.19.1 // indirect 69 | github.com/prometheus/client_model v0.6.1 // indirect 70 | github.com/prometheus/common v0.55.0 // indirect 71 | github.com/prometheus/procfs v0.15.1 // indirect 72 | github.com/samber/lo v1.37.0 // indirect 73 | github.com/x448/float16 v0.8.4 // indirect 74 | go.uber.org/multierr v1.11.0 // indirect 75 | go.uber.org/zap v1.27.0 // indirect 76 | golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect 77 | golang.org/x/net v0.38.0 // indirect 78 | golang.org/x/oauth2 v0.27.0 // indirect 79 | golang.org/x/sync v0.12.0 // indirect 80 | golang.org/x/sys v0.31.0 // indirect 81 | golang.org/x/term v0.30.0 // indirect 82 | golang.org/x/text v0.23.0 // indirect 83 | golang.org/x/time v0.7.0 // indirect 84 | gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect 85 | google.golang.org/protobuf v1.35.1 // indirect 86 | gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect 87 | gopkg.in/inf.v0 v0.9.1 // indirect 88 | gopkg.in/yaml.v3 v3.0.1 // indirect 89 | k8s.io/apiextensions-apiserver v0.32.1 // indirect 90 | k8s.io/klog/v2 v2.130.1 // indirect 91 | k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect 92 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect 93 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect 94 | sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect 95 | sigs.k8s.io/yaml v1.4.0 // indirect 96 | ) 97 | -------------------------------------------------------------------------------- /test/e2e/tests/test_bucket_deletion_policy.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 | """Integration tests for the deletion policy annotation on Bucket. 15 | """ 16 | 17 | from enum import Enum 18 | import pytest 19 | import logging 20 | import itertools 21 | from typing import TYPE_CHECKING, Generator, List, NamedTuple, Tuple 22 | 23 | from acktest.resources import random_suffix_name 24 | from acktest.k8s import resource as k8s 25 | from acktest import adoption as adoption 26 | from acktest import tags as tags 27 | from e2e import SERVICE_NAME 28 | from e2e.tests.test_bucket import Bucket, bucket_exists, create_bucket, delete_bucket 29 | 30 | DELETION_POLICY_RESOURCE_ANNOTATION_KEY = "services.k8s.aws/deletion-policy" 31 | DELETION_POLICY_NAMESPACE_ANNOTATION_KEY = ( 32 | f"{SERVICE_NAME}.services.k8s.aws/deletion-policy" 33 | ) 34 | 35 | class DeletionPolicy(str, Enum): 36 | NONE = "" 37 | DELETE = "delete" 38 | RETAIN = "retain" 39 | 40 | 41 | # DeletionPolicyAnnotationTuple represents a tuple of namespace and resource 42 | # deletion policy annotations. These are used when testing all combinations of 43 | # each annotation. 44 | class DeletionPolicyAnnotationTuple(NamedTuple): 45 | namespace: DeletionPolicy 46 | resource: DeletionPolicy 47 | 48 | 49 | # Create a matrix of combinations for deletion policy annotations that can be 50 | # used as a parameter for tests 51 | DELETION_POLICY_ANNOTATION_COMBINATIONS: List[DeletionPolicyAnnotationTuple] = [ 52 | DeletionPolicyAnnotationTuple(r[0], r[1]) 53 | for r in itertools.product([p for p in DeletionPolicy], [p for p in DeletionPolicy]) 54 | ] 55 | 56 | # Parameter types are not support by pytest. This adds support for type checking 57 | # the param type. 58 | if TYPE_CHECKING: 59 | 60 | class DeletionPolicyFixtureRequest: 61 | param: DeletionPolicyAnnotationTuple 62 | 63 | else: 64 | from typing import Any 65 | 66 | DeletionPolicyFixtureRequest = Any 67 | 68 | 69 | def create_deletion_policy_namespace(deletion_policy: DeletionPolicy) -> str: 70 | namespace_name = random_suffix_name("s3-deletion-policy", 24) 71 | annotations = {} 72 | if deletion_policy != DeletionPolicy.NONE: 73 | annotations[DELETION_POLICY_NAMESPACE_ANNOTATION_KEY] = deletion_policy.value 74 | 75 | logging.info(f"Creating namespace {namespace_name}") 76 | try: 77 | k8s.create_k8s_namespace(namespace_name, annotations) 78 | except Exception as ex: 79 | return pytest.fail("Failed to create namespace") 80 | 81 | return namespace_name 82 | 83 | 84 | @pytest.fixture(scope="function") 85 | def deletion_policy_namespace_bucket( 86 | request: DeletionPolicyFixtureRequest, s3_client 87 | ) -> Generator[Tuple[Bucket, DeletionPolicyAnnotationTuple], None, None]: 88 | bucket_namespace = create_deletion_policy_namespace(request.param.namespace) 89 | 90 | bucket = None 91 | try: 92 | if request.param.resource == DeletionPolicy.NONE: 93 | bucket = create_bucket("bucket", namespace=bucket_namespace) 94 | else: 95 | bucket = create_bucket( 96 | "bucket_deletion_policy", 97 | namespace=bucket_namespace, 98 | additional_replacements={ 99 | "DELETION_POLICY": request.param.resource.value 100 | }, 101 | ) 102 | 103 | assert k8s.get_resource_exists(bucket.ref) 104 | 105 | exists = bucket_exists(s3_client, bucket) 106 | assert exists 107 | except: 108 | if bucket is not None: 109 | delete_bucket(bucket) 110 | return pytest.fail("Bucket failed to create") 111 | 112 | yield (bucket, request.param) 113 | 114 | delete_bucket(bucket) 115 | 116 | exists = bucket_exists(s3_client, bucket) 117 | if exists: 118 | s3_client.delete_bucket(Bucket=bucket.resource_name) 119 | 120 | k8s.delete_k8s_namespace(bucket_namespace) 121 | 122 | 123 | class TestDeletionPolicyBucket: 124 | @pytest.mark.parametrize( 125 | "deletion_policy_namespace_bucket", 126 | DELETION_POLICY_ANNOTATION_COMBINATIONS, 127 | indirect=True, 128 | ) 129 | def test_deletion_policy( 130 | self, s3_client, deletion_policy_namespace_bucket 131 | ): 132 | (bucket, deletion_policy_annotations) = deletion_policy_namespace_bucket 133 | 134 | delete_bucket(bucket) 135 | 136 | exists = bucket_exists(s3_client, bucket) 137 | 138 | # Assert in order of precedence (resource > namespace) 139 | if deletion_policy_annotations.resource == DeletionPolicy.DELETE: 140 | assert not exists 141 | elif deletion_policy_annotations.resource == DeletionPolicy.RETAIN: 142 | assert exists 143 | elif deletion_policy_annotations.namespace == DeletionPolicy.DELETE: 144 | assert not exists 145 | elif deletion_policy_annotations.namespace == DeletionPolicy.RETAIN: 146 | assert exists 147 | else: # Neither has an annotation 148 | assert not exists 149 | -------------------------------------------------------------------------------- /pkg/resource/bucket/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 bucket 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/s3-controller/apis/v1alpha1" 28 | ) 29 | 30 | const ( 31 | FinalizerString = "finalizers.s3.services.k8s.aws/Bucket" 32 | ) 33 | 34 | var ( 35 | GroupVersionResource = svcapitypes.GroupVersion.WithResource("buckets") 36 | GroupKind = metav1.GroupKind{ 37 | Group: "s3.services.k8s.aws", 38 | Kind: "Bucket", 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.Bucket{} 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.Bucket), 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 | -------------------------------------------------------------------------------- /cmd/controller/main.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 main 17 | 18 | import ( 19 | "context" 20 | "os" 21 | 22 | ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" 23 | ackcfg "github.com/aws-controllers-k8s/runtime/pkg/config" 24 | ackrt "github.com/aws-controllers-k8s/runtime/pkg/runtime" 25 | acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" 26 | ackrtutil "github.com/aws-controllers-k8s/runtime/pkg/util" 27 | ackrtwebhook "github.com/aws-controllers-k8s/runtime/pkg/webhook" 28 | flag "github.com/spf13/pflag" 29 | "k8s.io/apimachinery/pkg/runtime" 30 | "k8s.io/apimachinery/pkg/runtime/schema" 31 | clientgoscheme "k8s.io/client-go/kubernetes/scheme" 32 | ctrlrt "sigs.k8s.io/controller-runtime" 33 | ctrlrtcache "sigs.k8s.io/controller-runtime/pkg/cache" 34 | ctrlrthealthz "sigs.k8s.io/controller-runtime/pkg/healthz" 35 | ctrlrtmetrics "sigs.k8s.io/controller-runtime/pkg/metrics" 36 | metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" 37 | ctrlrtwebhook "sigs.k8s.io/controller-runtime/pkg/webhook" 38 | 39 | svctypes "github.com/aws-controllers-k8s/s3-controller/apis/v1alpha1" 40 | svcresource "github.com/aws-controllers-k8s/s3-controller/pkg/resource" 41 | 42 | _ "github.com/aws-controllers-k8s/s3-controller/pkg/resource/bucket" 43 | 44 | "github.com/aws-controllers-k8s/s3-controller/pkg/version" 45 | ) 46 | 47 | var ( 48 | awsServiceAPIGroup = "s3.services.k8s.aws" 49 | awsServiceAlias = "s3" 50 | scheme = runtime.NewScheme() 51 | setupLog = ctrlrt.Log.WithName("setup") 52 | ) 53 | 54 | func init() { 55 | _ = clientgoscheme.AddToScheme(scheme) 56 | 57 | _ = svctypes.AddToScheme(scheme) 58 | _ = ackv1alpha1.AddToScheme(scheme) 59 | } 60 | 61 | func main() { 62 | var ackCfg ackcfg.Config 63 | ackCfg.BindFlags() 64 | flag.Parse() 65 | ackCfg.SetupLogger() 66 | 67 | managerFactories := svcresource.GetManagerFactories() 68 | resourceGVKs := make([]schema.GroupVersionKind, 0, len(managerFactories)) 69 | for _, mf := range managerFactories { 70 | resourceGVKs = append(resourceGVKs, mf.ResourceDescriptor().GroupVersionKind()) 71 | } 72 | 73 | ctx := context.Background() 74 | if err := ackCfg.Validate(ctx, ackcfg.WithGVKs(resourceGVKs)); err != nil { 75 | setupLog.Error( 76 | err, "Unable to create controller manager", 77 | "aws.service", awsServiceAlias, 78 | ) 79 | os.Exit(1) 80 | } 81 | 82 | host, port, err := ackrtutil.GetHostPort(ackCfg.WebhookServerAddr) 83 | if err != nil { 84 | setupLog.Error( 85 | err, "Unable to parse webhook server address.", 86 | "aws.service", awsServiceAlias, 87 | ) 88 | os.Exit(1) 89 | } 90 | 91 | watchNamespaces := make(map[string]ctrlrtcache.Config, 0) 92 | namespaces, err := ackCfg.GetWatchNamespaces() 93 | if err != nil { 94 | setupLog.Error( 95 | err, "Unable to parse watch namespaces.", 96 | "aws.service", ackCfg.WatchNamespace, 97 | ) 98 | os.Exit(1) 99 | } 100 | 101 | for _, namespace := range namespaces { 102 | watchNamespaces[namespace] = ctrlrtcache.Config{} 103 | } 104 | watchSelectors, err := ackCfg.ParseWatchSelectors() 105 | if err != nil { 106 | setupLog.Error( 107 | err, "Unable to parse watch selectors.", 108 | "aws.service", awsServiceAlias, 109 | ) 110 | os.Exit(1) 111 | } 112 | mgr, err := ctrlrt.NewManager(ctrlrt.GetConfigOrDie(), ctrlrt.Options{ 113 | Scheme: scheme, 114 | Cache: ctrlrtcache.Options{ 115 | Scheme: scheme, 116 | DefaultNamespaces: watchNamespaces, 117 | DefaultLabelSelector: watchSelectors, 118 | }, 119 | WebhookServer: &ctrlrtwebhook.DefaultServer{ 120 | Options: ctrlrtwebhook.Options{ 121 | Port: port, 122 | Host: host, 123 | }, 124 | }, 125 | Metrics: metricsserver.Options{BindAddress: ackCfg.MetricsAddr}, 126 | LeaderElection: ackCfg.EnableLeaderElection, 127 | LeaderElectionID: "ack-" + awsServiceAPIGroup, 128 | LeaderElectionNamespace: ackCfg.LeaderElectionNamespace, 129 | HealthProbeBindAddress: ackCfg.HealthzAddr, 130 | LivenessEndpointName: "/healthz", 131 | ReadinessEndpointName: "/readyz", 132 | }) 133 | if err != nil { 134 | setupLog.Error( 135 | err, "unable to create controller manager", 136 | "aws.service", awsServiceAlias, 137 | ) 138 | os.Exit(1) 139 | } 140 | 141 | stopChan := ctrlrt.SetupSignalHandler() 142 | 143 | setupLog.Info( 144 | "initializing service controller", 145 | "aws.service", awsServiceAlias, 146 | ) 147 | sc := ackrt.NewServiceController( 148 | awsServiceAlias, awsServiceAPIGroup, 149 | acktypes.VersionInfo{ 150 | version.GitCommit, 151 | version.GitVersion, 152 | version.BuildDate, 153 | }, 154 | ).WithLogger( 155 | ctrlrt.Log, 156 | ).WithResourceManagerFactories( 157 | svcresource.GetManagerFactories(), 158 | ).WithPrometheusRegistry( 159 | ctrlrtmetrics.Registry, 160 | ) 161 | 162 | if ackCfg.EnableWebhookServer { 163 | webhooks := ackrtwebhook.GetWebhooks() 164 | for _, webhook := range webhooks { 165 | if err := webhook.Setup(mgr); err != nil { 166 | setupLog.Error( 167 | err, "unable to register webhook "+webhook.UID(), 168 | "aws.service", awsServiceAlias, 169 | ) 170 | } 171 | } 172 | } 173 | 174 | if err = sc.BindControllerManager(mgr, ackCfg); err != nil { 175 | setupLog.Error( 176 | err, "unable bind to controller manager to service controller", 177 | "aws.service", awsServiceAlias, 178 | ) 179 | os.Exit(1) 180 | } 181 | 182 | if err = mgr.AddHealthzCheck("health", ctrlrthealthz.Ping); err != nil { 183 | setupLog.Error( 184 | err, "unable to set up health check", 185 | "aws.service", awsServiceAlias, 186 | ) 187 | os.Exit(1) 188 | } 189 | if err = mgr.AddReadyzCheck("check", ctrlrthealthz.Ping); err != nil { 190 | setupLog.Error( 191 | err, "unable to set up ready check", 192 | "aws.service", awsServiceAlias, 193 | ) 194 | os.Exit(1) 195 | } 196 | 197 | setupLog.Info( 198 | "starting manager", 199 | "aws.service", awsServiceAlias, 200 | ) 201 | if err := mgr.Start(stopChan); err != nil { 202 | setupLog.Error( 203 | err, "unable to start controller manager", 204 | "aws.service", awsServiceAlias, 205 | ) 206 | os.Exit(1) 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /pkg/resource/bucket/acl_custom_test.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 bucket_test 15 | 16 | import ( 17 | "fmt" 18 | "testing" 19 | 20 | bucket "github.com/aws-controllers-k8s/s3-controller/pkg/resource/bucket" 21 | svcsdk "github.com/aws/aws-sdk-go-v2/service/s3" 22 | svcsdktypes "github.com/aws/aws-sdk-go-v2/service/s3/types" 23 | "github.com/stretchr/testify/assert" 24 | ) 25 | 26 | var ( 27 | OwnerDisplayName = "my-test-user" 28 | OwnerID = "123456789" 29 | RandomGranteeURI = "http://my-random-grantee.example.com/lol" 30 | ) 31 | 32 | func s(s string) *string { return &s } 33 | 34 | func provideOwner() *svcsdktypes.Owner { 35 | return &svcsdktypes.Owner{ 36 | DisplayName: &OwnerDisplayName, 37 | ID: &OwnerID, 38 | } 39 | } 40 | 41 | func provideOwnerGrantee() *svcsdktypes.Grantee { 42 | return &svcsdktypes.Grantee{ 43 | DisplayName: &OwnerDisplayName, 44 | ID: &OwnerID, 45 | Type: svcsdktypes.TypeCanonicalUser, 46 | } 47 | } 48 | 49 | func provideMockUserFullControl() []svcsdktypes.Grant { 50 | return []svcsdktypes.Grant{ 51 | { 52 | Grantee: provideOwnerGrantee(), 53 | Permission: svcsdktypes.PermissionFullControl, 54 | }, 55 | } 56 | } 57 | 58 | func wrapGrants(grants []svcsdktypes.Grant) *svcsdk.GetBucketAclOutput { 59 | return &svcsdk.GetBucketAclOutput{ 60 | Grants: grants, 61 | Owner: provideOwner(), 62 | } 63 | } 64 | 65 | func cannedPrivateOutput() *svcsdk.GetBucketAclOutput { 66 | return wrapGrants(provideMockUserFullControl()) 67 | } 68 | 69 | func cannedLogDeliveryOutput() *svcsdk.GetBucketAclOutput { 70 | grants := provideMockUserFullControl() 71 | logDeliveryGrantee := &svcsdktypes.Grantee{ 72 | Type: svcsdktypes.TypeGroup, 73 | URI: &bucket.GranteeLogDeliveryURI, 74 | } 75 | writeGrant := svcsdktypes.Grant{ 76 | Grantee: logDeliveryGrantee, 77 | Permission: svcsdktypes.PermissionWrite, 78 | } 79 | readACPGrant := svcsdktypes.Grant{ 80 | Grantee: logDeliveryGrantee, 81 | Permission: svcsdktypes.PermissionReadAcp, 82 | } 83 | grants = append(grants, writeGrant) 84 | grants = append(grants, readACPGrant) 85 | 86 | return wrapGrants(grants) 87 | } 88 | 89 | func cannedPublicReadWriteOutput() *svcsdk.GetBucketAclOutput { 90 | grants := provideMockUserFullControl() 91 | allUsersGrantee := &svcsdktypes.Grantee{ 92 | Type: svcsdktypes.TypeGroup, 93 | URI: &bucket.GranteeAllUsersURI, 94 | } 95 | writeGrant := svcsdktypes.Grant{ 96 | Grantee: allUsersGrantee, 97 | Permission: svcsdktypes.PermissionWrite, 98 | } 99 | readGrant := svcsdktypes.Grant{ 100 | Grantee: allUsersGrantee, 101 | Permission: svcsdktypes.PermissionRead, 102 | } 103 | grants = append(grants, writeGrant) 104 | grants = append(grants, readGrant) 105 | 106 | return wrapGrants(grants) 107 | } 108 | 109 | func allGrantsOutput() *svcsdk.GetBucketAclOutput { 110 | grants := provideMockUserFullControl() 111 | randomGrantee := &svcsdktypes.Grantee{ 112 | Type: svcsdktypes.TypeGroup, 113 | URI: &RandomGranteeURI, 114 | } 115 | writeGrant := svcsdktypes.Grant{ 116 | Grantee: randomGrantee, 117 | Permission: svcsdktypes.PermissionWrite, 118 | } 119 | writeACPGrant := svcsdktypes.Grant{ 120 | Grantee: randomGrantee, 121 | Permission: svcsdktypes.PermissionWriteAcp, 122 | } 123 | readGrant := svcsdktypes.Grant{ 124 | Grantee: randomGrantee, 125 | Permission: svcsdktypes.PermissionRead, 126 | } 127 | readACPGrant := svcsdktypes.Grant{ 128 | Grantee: randomGrantee, 129 | Permission: svcsdktypes.PermissionReadAcp, 130 | } 131 | grants = append(grants, writeGrant) 132 | grants = append(grants, writeACPGrant) 133 | grants = append(grants, readGrant) 134 | grants = append(grants, readACPGrant) 135 | 136 | return wrapGrants(grants) 137 | } 138 | 139 | func multiplePermissionGrantsOutput() *svcsdk.GetBucketAclOutput { 140 | grants := provideMockUserFullControl() 141 | anotherFulLControl := svcsdktypes.Grant{ 142 | Grantee: &svcsdktypes.Grantee{ 143 | Type: svcsdktypes.TypeGroup, 144 | URI: s(RandomGranteeURI), 145 | }, 146 | Permission: svcsdktypes.PermissionFullControl, 147 | } 148 | 149 | grants = append(grants, anotherFulLControl) 150 | return wrapGrants(grants) 151 | } 152 | 153 | func Test_GetHeadersFromGrants(t *testing.T) { 154 | assert := assert.New(t) 155 | 156 | privateGrants := cannedPrivateOutput() 157 | headers := bucket.GetHeadersFromGrants(privateGrants) 158 | assert.Equal(headers.FullControl, fmt.Sprintf("id=%s", OwnerID)) 159 | assert.Empty(headers.Read) 160 | assert.Empty(headers.ReadACP) 161 | assert.Empty(headers.Write) 162 | assert.Empty(headers.WriteACP) 163 | 164 | logDeliveryGrants := cannedLogDeliveryOutput() 165 | headers = bucket.GetHeadersFromGrants(logDeliveryGrants) 166 | assert.Equal(headers.FullControl, fmt.Sprintf("id=%s", OwnerID)) 167 | assert.Empty(headers.Read) 168 | assert.Equal(headers.ReadACP, fmt.Sprintf("uri=%s", bucket.GranteeLogDeliveryURI)) 169 | assert.Equal(headers.Write, fmt.Sprintf("uri=%s", bucket.GranteeLogDeliveryURI)) 170 | assert.Empty(headers.WriteACP) 171 | 172 | allGrants := allGrantsOutput() 173 | headers = bucket.GetHeadersFromGrants(allGrants) 174 | assert.Equal(headers.FullControl, fmt.Sprintf("id=%s", OwnerID)) 175 | assert.Equal(headers.Read, fmt.Sprintf("uri=%s", RandomGranteeURI)) 176 | assert.Equal(headers.ReadACP, fmt.Sprintf("uri=%s", RandomGranteeURI)) 177 | assert.Equal(headers.Write, fmt.Sprintf("uri=%s", RandomGranteeURI)) 178 | assert.Equal(headers.WriteACP, fmt.Sprintf("uri=%s", RandomGranteeURI)) 179 | 180 | multiplePermissionGrants := multiplePermissionGrantsOutput() 181 | headers = bucket.GetHeadersFromGrants(multiplePermissionGrants) 182 | assert.Equal(headers.FullControl, fmt.Sprintf("id=%s,uri=%s", OwnerID, RandomGranteeURI)) 183 | assert.Empty(headers.Read) 184 | assert.Empty(headers.ReadACP) 185 | assert.Empty(headers.Write) 186 | assert.Empty(headers.WriteACP) 187 | } 188 | 189 | func Test_GetPossibleCannedACLsFromGrants(t *testing.T) { 190 | assert := assert.New(t) 191 | 192 | privateGrants := cannedPrivateOutput() 193 | possibilities := bucket.GetPossibleCannedACLsFromGrants(privateGrants) 194 | assert.ElementsMatch(possibilities, []string{bucket.CannedACLPrivate, bucket.CannedBucketOwnerRead, bucket.CannedBucketOwnerFullControl}) 195 | 196 | logDeliveryGrants := cannedLogDeliveryOutput() 197 | possibilities = bucket.GetPossibleCannedACLsFromGrants(logDeliveryGrants) 198 | assert.ElementsMatch(possibilities, []string{bucket.CannedLogDeliveryWrite}) 199 | 200 | publicReadWriteGrants := cannedPublicReadWriteOutput() 201 | possibilities = bucket.GetPossibleCannedACLsFromGrants(publicReadWriteGrants) 202 | assert.ElementsMatch(possibilities, []string{bucket.CannedPublicReadWrite}) 203 | } 204 | -------------------------------------------------------------------------------- /helm/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for ack-s3-controller. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | 5 | image: 6 | repository: public.ecr.aws/aws-controllers-k8s/s3-controller 7 | tag: 1.2.0 8 | pullPolicy: IfNotPresent 9 | pullSecrets: [] 10 | 11 | nameOverride: "" 12 | fullnameOverride: "" 13 | 14 | deployment: 15 | annotations: {} 16 | labels: {} 17 | containerPort: 8080 18 | # Number of Deployment replicas 19 | # This determines how many instances of the controller will be running. It's recommended 20 | # to enable leader election if you need to increase the number of replicas > 1 21 | replicas: 1 22 | # Which nodeSelector to set? 23 | # See: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector 24 | nodeSelector: 25 | kubernetes.io/os: linux 26 | # Which tolerations to set? 27 | # See: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/ 28 | tolerations: [] 29 | # What affinity to set? 30 | # See: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity 31 | affinity: {} 32 | # Which priorityClassName to set? 33 | # See: https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/#pod-priority 34 | priorityClassName: "" 35 | # Specifies the hostname of the Pod. 36 | # If not specified, the pod's hostname will be set to a system-defined value. 37 | hostNetwork: false 38 | # Set DNS policy for the pod. 39 | # Defaults to "ClusterFirst". 40 | # Valid values are 'ClusterFirstWithHostNet', 'ClusterFirst', 'Default' or 'None'. 41 | # To have DNS options set along with hostNetwork, you have to specify DNS policy 42 | # explicitly to 'ClusterFirstWithHostNet'. 43 | dnsPolicy: ClusterFirst 44 | # Set rollout strategy for deployment. 45 | # See: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy 46 | strategy: {} 47 | extraVolumes: [] 48 | extraVolumeMounts: [] 49 | 50 | # Additional server container environment variables 51 | # 52 | # You specify this manually like you would a raw deployment manifest. 53 | # This means you can bind in environment variables from secrets. 54 | # 55 | # e.g. static environment variable: 56 | # - name: DEMO_GREETING 57 | # value: "Hello from the environment" 58 | # 59 | # e.g. secret environment variable: 60 | # - name: USERNAME 61 | # valueFrom: 62 | # secretKeyRef: 63 | # name: mysecret 64 | # key: username 65 | extraEnvVars: [] 66 | 67 | 68 | # If "installScope: cluster" then these labels will be applied to ClusterRole 69 | role: 70 | labels: {} 71 | 72 | metrics: 73 | service: 74 | # Set to true to automatically create a Kubernetes Service resource for the 75 | # Prometheus metrics server endpoint in controller 76 | create: false 77 | # Which Type to use for the Kubernetes Service? 78 | # See: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types 79 | type: "ClusterIP" 80 | 81 | resources: 82 | requests: 83 | memory: "64Mi" 84 | cpu: "50m" 85 | limits: 86 | memory: "128Mi" 87 | cpu: "100m" 88 | 89 | aws: 90 | # If specified, use the AWS region for AWS API calls 91 | region: "" 92 | endpoint_url: "" 93 | credentials: 94 | # If specified, Secret with shared credentials file to use. 95 | secretName: "" 96 | # Secret stringData key that contains the credentials 97 | secretKey: "credentials" 98 | # Profile used for AWS credentials 99 | profile: "default" 100 | 101 | # log level for the controller 102 | log: 103 | enable_development_logging: false 104 | level: info 105 | 106 | # Set to "namespace" to install the controller in a namespaced scope, will only 107 | # watch for object creation in the namespace. By default installScope is 108 | # cluster wide. 109 | installScope: cluster 110 | 111 | # Set the value of the "namespace" to be watched by the controller 112 | # This value is only used when the `installScope` is set to "namespace". If left empty, the default value is the release namespace for the chart. 113 | # You can set multiple namespaces by providing a comma separated list of namespaces. e.g "namespace1,namespace2" 114 | watchNamespace: "" 115 | 116 | # Set the value of labelsSelectors to be used by the controller to filter the resources to watch. 117 | # You can set multiple labelsSelectors by providing a comma separated list of a=b arguments. e.g "label1=value1,label2=value2" 118 | watchSelectors: "" 119 | 120 | resourceTags: 121 | # Configures the ACK service controller to always set key/value pairs tags on 122 | # resources that it manages. 123 | # Note: Tags with empty values are automatically skipped to keep resources clean. 124 | - services.k8s.aws/controller-version=%CONTROLLER_SERVICE%-%CONTROLLER_VERSION% 125 | - services.k8s.aws/namespace=%K8S_NAMESPACE% 126 | - app.kubernetes.io/managed-by=%MANAGED_BY% 127 | - kro.run/kro-version=%KRO_VERSION% 128 | 129 | # Set to "retain" to keep all AWS resources intact even after the K8s resources 130 | # have been deleted. By default, the ACK controller will delete the AWS resource 131 | # before the K8s resource is removed. 132 | deletionPolicy: delete 133 | 134 | # controller reconciliation configurations 135 | reconcile: 136 | # The default duration, in seconds, to wait before resyncing desired state of custom resources. 137 | defaultResyncPeriod: 36000 # 10 Hours 138 | # An object representing the reconcile resync configuration for each specific resource. 139 | resourceResyncPeriods: {} 140 | 141 | # The default number of concurrent syncs that a reconciler can perform. 142 | defaultMaxConcurrentSyncs: 1 143 | # An object representing the reconcile max concurrent syncs configuration for each specific 144 | # resource. 145 | resourceMaxConcurrentSyncs: {} 146 | 147 | # Set the value of resources to specify which resource kinds to reconcile. 148 | # If empty, all resources will be reconciled. 149 | # If specified, only the listed resource kinds will be reconciled. 150 | resources: 151 | - Bucket 152 | 153 | serviceAccount: 154 | # Specifies whether a service account should be created 155 | create: true 156 | # The name of the service account to use. 157 | name: ack-s3-controller 158 | annotations: {} 159 | # eks.amazonaws.com/role-arn: arn:aws:iam::AWS_ACCOUNT_ID:role/IAM_ROLE_NAME 160 | 161 | # Configuration of the leader election. Required for running multiple instances of the 162 | # controller within the same cluster. 163 | # See https://kubernetes.io/docs/concepts/architecture/leases/#leader-election 164 | leaderElection: 165 | # Enable Controller Leader Election. Set this to true to enable leader election 166 | # for this controller. 167 | enabled: false 168 | # Leader election can be scoped to a specific namespace. By default, the controller 169 | # will attempt to use the namespace of the service account mounted to the Controller 170 | # pod. 171 | namespace: "" 172 | 173 | # Enable Cross Account Resource Management (default = true). Set this to false to disable cross account resource management. 174 | enableCARM: true 175 | 176 | # Configuration for feature gates. These are optional controller features that 177 | # can be individually enabled ("true") or disabled ("false") by adding key/value 178 | # pairs below. 179 | featureGates: 180 | # Enables the Service level granularity for CARM. See https://github.com/aws-controllers-k8s/community/issues/2031 181 | ServiceLevelCARM: false 182 | # Enables the Team level granularity for CARM. See https://github.com/aws-controllers-k8s/community/issues/2031 183 | TeamLevelCARM: false 184 | # Enable ReadOnlyResources feature/annotation. 185 | ReadOnlyResources: true 186 | # Enable ResourceAdoption feature/annotation. 187 | ResourceAdoption: true 188 | # Enable IAMRoleSelector, a multirole feature, replacing CARM. See https://github.com/aws-controllers-k8s/community/pull/2628 189 | IAMRoleSelector: false -------------------------------------------------------------------------------- /apis/v1alpha1/bucket.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 | // BucketSpec defines the desired state of Bucket. 24 | // 25 | // In terms of implementation, a Bucket is a resource. 26 | type BucketSpec struct { 27 | 28 | // The canned ACL to apply to the bucket. 29 | // 30 | // This functionality is not supported for directory buckets. 31 | ACL *string `json:"acl,omitempty"` 32 | // Container for setting the transfer acceleration state. 33 | Accelerate *AccelerateConfiguration `json:"accelerate,omitempty"` 34 | Analytics []*AnalyticsConfiguration `json:"analytics,omitempty"` 35 | // Describes the cross-origin access configuration for objects in an Amazon 36 | // S3 bucket. For more information, see Enabling Cross-Origin Resource Sharing 37 | // (https://docs.aws.amazon.com/AmazonS3/latest/dev/cors.html) in the Amazon 38 | // S3 User Guide. 39 | CORS *CORSConfiguration `json:"cors,omitempty"` 40 | // The configuration information for the bucket. 41 | CreateBucketConfiguration *CreateBucketConfiguration `json:"createBucketConfiguration,omitempty"` 42 | Encryption *ServerSideEncryptionConfiguration `json:"encryption,omitempty"` 43 | // Allows grantee the read, write, read ACP, and write ACP permissions on the 44 | // bucket. 45 | // 46 | // This functionality is not supported for directory buckets. 47 | GrantFullControl *string `json:"grantFullControl,omitempty"` 48 | // Allows grantee to list the objects in the bucket. 49 | // 50 | // This functionality is not supported for directory buckets. 51 | GrantRead *string `json:"grantRead,omitempty"` 52 | // Allows grantee to read the bucket ACL. 53 | // 54 | // This functionality is not supported for directory buckets. 55 | GrantReadACP *string `json:"grantReadACP,omitempty"` 56 | // Allows grantee to create new objects in the bucket. 57 | // 58 | // For the bucket and object owners of existing objects, also allows deletions 59 | // and overwrites of those objects. 60 | // 61 | // This functionality is not supported for directory buckets. 62 | GrantWrite *string `json:"grantWrite,omitempty"` 63 | // Allows grantee to write the ACL for the applicable bucket. 64 | // 65 | // This functionality is not supported for directory buckets. 66 | GrantWriteACP *string `json:"grantWriteACP,omitempty"` 67 | IntelligentTiering []*IntelligentTieringConfiguration `json:"intelligentTiering,omitempty"` 68 | Inventory []*InventoryConfiguration `json:"inventory,omitempty"` 69 | // Container for lifecycle rules. You can add as many as 1,000 rules. 70 | Lifecycle *BucketLifecycleConfiguration `json:"lifecycle,omitempty"` 71 | // Container for logging status information. 72 | Logging *BucketLoggingStatus `json:"logging,omitempty"` 73 | Metrics []*MetricsConfiguration `json:"metrics,omitempty"` 74 | // The name of the bucket to create. 75 | // 76 | // General purpose buckets - For information about bucket naming restrictions, 77 | // see Bucket naming rules (https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html) 78 | // in the Amazon S3 User Guide. 79 | // 80 | // Directory buckets - When you use this operation with a directory bucket, 81 | // you must use path-style requests in the format https://s3express-control.region-code.amazonaws.com/bucket-name 82 | // . Virtual-hosted-style requests aren't supported. Directory bucket names 83 | // must be unique in the chosen Zone (Availability Zone or Local Zone). Bucket 84 | // names must also follow the format bucket-base-name--zone-id--x-s3 (for example, 85 | // DOC-EXAMPLE-BUCKET--usw2-az1--x-s3). For information about bucket naming 86 | // restrictions, see Directory bucket naming rules (https://docs.aws.amazon.com/AmazonS3/latest/userguide/directory-bucket-naming-rules.html) 87 | // in the Amazon S3 User Guide 88 | // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable once set" 89 | // +kubebuilder:validation:Required 90 | Name *string `json:"name"` 91 | Notification *NotificationConfiguration `json:"notification,omitempty"` 92 | // Specifies whether you want S3 Object Lock to be enabled for the new bucket. 93 | // 94 | // This functionality is not supported for directory buckets. 95 | ObjectLockEnabledForBucket *bool `json:"objectLockEnabledForBucket,omitempty"` 96 | ObjectOwnership *string `json:"objectOwnership,omitempty"` 97 | // The OwnershipControls (BucketOwnerEnforced, BucketOwnerPreferred, or ObjectWriter) 98 | // that you want to apply to this Amazon S3 bucket. 99 | OwnershipControls *OwnershipControls `json:"ownershipControls,omitempty"` 100 | // The bucket policy as a JSON document. 101 | // 102 | // For directory buckets, the only IAM action supported in the bucket policy 103 | // is s3express:CreateSession. 104 | Policy *string `json:"policy,omitempty"` 105 | // The PublicAccessBlock configuration that you want to apply to this Amazon 106 | // S3 bucket. You can enable the configuration options in any combination. For 107 | // more information about when Amazon S3 considers a bucket or object public, 108 | // see The Meaning of "Public" (https://docs.aws.amazon.com/AmazonS3/latest/dev/access-control-block-public-access.html#access-control-block-public-access-policy-status) 109 | // in the Amazon S3 User Guide. 110 | PublicAccessBlock *PublicAccessBlockConfiguration `json:"publicAccessBlock,omitempty"` 111 | Replication *ReplicationConfiguration `json:"replication,omitempty"` 112 | // Container for Payer. 113 | RequestPayment *RequestPaymentConfiguration `json:"requestPayment,omitempty"` 114 | // Container for the TagSet and Tag elements. 115 | Tagging *Tagging `json:"tagging,omitempty"` 116 | // Container for setting the versioning state. 117 | Versioning *VersioningConfiguration `json:"versioning,omitempty"` 118 | // Container for the request. 119 | Website *WebsiteConfiguration `json:"website,omitempty"` 120 | } 121 | 122 | // BucketStatus defines the observed state of Bucket 123 | type BucketStatus struct { 124 | // All CRs managed by ACK have a common `Status.ACKResourceMetadata` member 125 | // that is used to contain resource sync state, account ownership, 126 | // constructed ARN for the resource 127 | // +kubebuilder:validation:Optional 128 | ACKResourceMetadata *ackv1alpha1.ResourceMetadata `json:"ackResourceMetadata"` 129 | // All CRs managed by ACK have a common `Status.Conditions` member that 130 | // contains a collection of `ackv1alpha1.Condition` objects that describe 131 | // the various terminal states of the CR and its backend AWS service API 132 | // resource 133 | // +kubebuilder:validation:Optional 134 | Conditions []*ackv1alpha1.Condition `json:"conditions"` 135 | // A forward slash followed by the name of the bucket. 136 | // +kubebuilder:validation:Optional 137 | Location *string `json:"location,omitempty"` 138 | } 139 | 140 | // Bucket is the Schema for the Buckets API 141 | // +kubebuilder:object:root=true 142 | // +kubebuilder:subresource:status 143 | type Bucket struct { 144 | metav1.TypeMeta `json:",inline"` 145 | metav1.ObjectMeta `json:"metadata,omitempty"` 146 | Spec BucketSpec `json:"spec,omitempty"` 147 | Status BucketStatus `json:"status,omitempty"` 148 | } 149 | 150 | // BucketList contains a list of Bucket 151 | // +kubebuilder:object:root=true 152 | type BucketList struct { 153 | metav1.TypeMeta `json:",inline"` 154 | metav1.ListMeta `json:"metadata,omitempty"` 155 | Items []Bucket `json:"items"` 156 | } 157 | 158 | func init() { 159 | SchemeBuilder.Register(&Bucket{}, &BucketList{}) 160 | } 161 | -------------------------------------------------------------------------------- /helm/values.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft-07/schema#", 3 | "properties": { 4 | "image": { 5 | "description": "Container Image", 6 | "properties": { 7 | "repository": { 8 | "type": "string", 9 | "minLength": 1 10 | }, 11 | "tag": { 12 | "type": "string", 13 | "minLength": 1 14 | }, 15 | "pullPolicy": { 16 | "type": "string", 17 | "enum": ["IfNotPresent", "Always", "Never"] 18 | }, 19 | "pullSecrets": { 20 | "type": "array" 21 | } 22 | }, 23 | "required": [ 24 | "repository", 25 | "tag", 26 | "pullPolicy" 27 | ], 28 | "type": "object" 29 | }, 30 | "nameOverride": { 31 | "type": "string" 32 | }, 33 | "fullNameOverride": { 34 | "type": "string" 35 | }, 36 | "deployment": { 37 | "description": "Deployment settings", 38 | "properties": { 39 | "annotations": { 40 | "type": "object" 41 | }, 42 | "labels": { 43 | "type": "object" 44 | }, 45 | "containerPort": { 46 | "type": "integer", 47 | "minimum": 1, 48 | "maximum": 65535 49 | }, 50 | "replicas": { 51 | "type": "integer" 52 | }, 53 | "nodeSelector": { 54 | "type": "object" 55 | }, 56 | "tolerations": { 57 | "type": "array" 58 | }, 59 | "affinity": { 60 | "type": "object" 61 | }, 62 | "priorityClassName": { 63 | "type": "string" 64 | }, 65 | "extraVolumeMounts": { 66 | "type": "array" 67 | }, 68 | "extraVolumes": { 69 | "type": "array" 70 | }, 71 | "extraEnvVars": { 72 | "type": "array" 73 | } 74 | }, 75 | "required": [ 76 | "containerPort" 77 | ], 78 | "type": "object" 79 | }, 80 | "role": { 81 | "description": "Role settings", 82 | "properties": { 83 | "labels": { 84 | "type": "object" 85 | } 86 | } 87 | }, 88 | "metrics": { 89 | "description": "Metrics settings", 90 | "properties": { 91 | "service": { 92 | "description": "Kubernetes service settings", 93 | "properties": { 94 | "create": { 95 | "type": "boolean" 96 | }, 97 | "type": { 98 | "type": "string", 99 | "enum": ["ClusterIP", "NodePort", "LoadBalancer", "ExternalName"] 100 | } 101 | }, 102 | "required": [ 103 | "create", 104 | "type" 105 | ], 106 | "type": "object" 107 | } 108 | }, 109 | "required": [ 110 | "service" 111 | ], 112 | "type": "object" 113 | }, 114 | "resources": { 115 | "description": "Kubernetes resources settings", 116 | "properties": { 117 | "requests": { 118 | "description": "Kubernetes resource requests", 119 | "properties": { 120 | "memory": { 121 | "oneOf": [ 122 | { "type": "number" }, 123 | { "type": "string" } 124 | ] 125 | }, 126 | "cpu": { 127 | "oneOf": [ 128 | { "type": "number" }, 129 | { "type": "string" } 130 | ] 131 | } 132 | }, 133 | "required": [ 134 | "memory", 135 | "cpu" 136 | ], 137 | "type": "object" 138 | }, 139 | "limits": { 140 | "description": "Kubernetes resource limits", 141 | "properties": { 142 | "memory": { 143 | "oneOf": [ 144 | { "type": "number" }, 145 | { "type": "string" } 146 | ] 147 | }, 148 | "cpu": { 149 | "oneOf": [ 150 | { "type": "number" }, 151 | { "type": "string" } 152 | ] 153 | } 154 | }, 155 | "required": [ 156 | "memory", 157 | "cpu" 158 | ], 159 | "type": "object" 160 | } 161 | }, 162 | "required": [ 163 | "requests", 164 | "limits" 165 | ], 166 | "type": "object" 167 | }, 168 | "aws": { 169 | "description": "AWS API settings", 170 | "properties": { 171 | "region": { 172 | "type": "string" 173 | }, 174 | "endpoint": { 175 | "type": "string" 176 | }, 177 | "credentials": { 178 | "description": "AWS credentials information", 179 | "properties": { 180 | "secretName": { 181 | "type": "string" 182 | }, 183 | "secretKey": { 184 | "type": "string" 185 | }, 186 | "profile": { 187 | "type": "string" 188 | } 189 | }, 190 | "type": "object" 191 | } 192 | }, 193 | "type": "object" 194 | }, 195 | "log": { 196 | "description": "Logging settings", 197 | "properties": { 198 | "enable_development_logging": { 199 | "type": "boolean" 200 | }, 201 | "level": { 202 | "type": "string" 203 | } 204 | }, 205 | "type": "object" 206 | }, 207 | "installScope": { 208 | "type": "string", 209 | "enum": ["cluster", "namespace"] 210 | }, 211 | "watchNamespace": { 212 | "type": "string" 213 | }, 214 | "watchSelectors": { 215 | "type": "string" 216 | }, 217 | "resourceTags": { 218 | "type": "array", 219 | "items": { 220 | "type": "string", 221 | "pattern": "(^$|^.*=.*$)" 222 | } 223 | }, 224 | "deletionPolicy": { 225 | "type": "string", 226 | "enum": ["delete", "retain"] 227 | }, 228 | "reconcile": { 229 | "description": "Reconcile settings. This is used to configure the controller's reconciliation behavior. e.g resyncPeriod and maxConcurrentSyncs", 230 | "properties": { 231 | "defaultResyncPeriod": { 232 | "type": "number" 233 | }, 234 | "resourceResyncPeriods": { 235 | "type": "object" 236 | }, 237 | "defaultMaxConcurentSyncs": { 238 | "type": "number" 239 | }, 240 | "resourceMaxConcurrentSyncs": { 241 | "type": "object" 242 | }, 243 | "resources": { 244 | "type": "array", 245 | "items": { 246 | "type": "string" 247 | }, 248 | "description": "List of resource kinds to reconcile. If empty, all resources will be reconciled.", 249 | "default": [] 250 | } 251 | }, 252 | "type": "object" 253 | }, 254 | "leaderElection": { 255 | "description": "Parameter to configure the controller's leader election system.", 256 | "properties": { 257 | "enabled": { 258 | "type": "boolean" 259 | }, 260 | "namespace": { 261 | "type": "string" 262 | } 263 | }, 264 | "type": "object" 265 | }, 266 | "enableCARM": { 267 | "description": "Parameter to enable or disable cross account resource management.", 268 | "type": "boolean", 269 | "default": true 270 | }, 271 | "serviceAccount": { 272 | "description": "ServiceAccount settings", 273 | "properties": { 274 | "create": { 275 | "type": "boolean" 276 | }, 277 | "name": { 278 | "type": "string" 279 | }, 280 | "annotations": { 281 | "type": "object" 282 | } 283 | }, 284 | "type": "object" 285 | } 286 | }, 287 | "featureGates": { 288 | "description": "Feature gates settings", 289 | "type": "object", 290 | "additionalProperties": { 291 | "type": "boolean" 292 | } 293 | }, 294 | "required": [ 295 | "image", 296 | "deployment", 297 | "metrics", 298 | "resources", 299 | "log", 300 | "installScope", 301 | "resourceTags", 302 | "serviceAccount" 303 | ], 304 | "title": "Values", 305 | "type": "object" 306 | } 307 | -------------------------------------------------------------------------------- /pkg/resource/bucket/acl_custom.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 bucket 15 | 16 | import ( 17 | "fmt" 18 | "strings" 19 | 20 | svcsdk "github.com/aws/aws-sdk-go-v2/service/s3" 21 | svcsdktypes "github.com/aws/aws-sdk-go-v2/service/s3/types" 22 | ) 23 | 24 | // Only some of these exist in the SDK, so duplicating them all here 25 | var ( 26 | CannedACLPrivate = "private" 27 | CannedPublicRead = "public-read" 28 | CannedPublicReadWrite = "public-read-write" 29 | CannedAWSExecRead = "aws-exec-read" 30 | CannedAuthenticatedRead = "authenticated-read" 31 | CannedBucketOwnerRead = "bucket-owner-read" 32 | CannedBucketOwnerFullControl = "bucket-owner-full-control" 33 | CannedLogDeliveryWrite = "log-delivery-write" 34 | ) 35 | 36 | var ( 37 | GranteeZATeamID = "6aa5a366c34c1cbe25dc49211496e913e0351eb0e8c37aa3477e40942ec6b97c" 38 | GranteeLogDeliveryURI = "http://acs.amazonaws.com/groups/s3/LogDelivery" 39 | GranteeAllUsersURI = "http://acs.amazonaws.com/groups/global/AllUsers" 40 | GranteeAuthenticatedUsersURI = "http://acs.amazonaws.com/groups/global/AuthenticatedUsers" 41 | ) 42 | 43 | var ( 44 | HeaderUserIDFormat = "id=%s" 45 | HeaderURIFormat = "uri=%s" 46 | ) 47 | 48 | type aclGrantHeaders struct { 49 | FullControl string 50 | Read string 51 | ReadACP string 52 | Write string 53 | WriteACP string 54 | } 55 | 56 | // hasOwnerFullControl returns true if any of the grants matches the owner 57 | // and has full control permissions. 58 | func hasOwnerFullControl(owner *svcsdktypes.Owner, grants []svcsdktypes.Grant) bool { 59 | for _, grant := range grants { 60 | if grant.Grantee == nil || 61 | grant.Grantee.ID == nil || 62 | *grant.Grantee.ID != *owner.ID { 63 | continue 64 | } 65 | 66 | return grant.Permission == svcsdktypes.PermissionFullControl 67 | } 68 | return false 69 | } 70 | 71 | // grantsContainPermission will return true if any of the grants have the 72 | // permission matching the one supplied. 73 | func grantsContainPermission(permission svcsdktypes.Permission, grants []*svcsdktypes.Grant) bool { 74 | for _, grant := range grants { 75 | if grant.Permission == svcsdktypes.Permission(permission) { 76 | return true 77 | } 78 | } 79 | return false 80 | } 81 | 82 | // getGrantsByGroupURI searches a list of ACL grants for any that have a 83 | // group type grantee with the given URI. 84 | func getGrantsByGroupURI(uri string, grants []svcsdktypes.Grant) []*svcsdktypes.Grant { 85 | matching := []*svcsdktypes.Grant{} 86 | 87 | for _, grant := range grants { 88 | if grant.Grantee == nil { 89 | continue 90 | } 91 | 92 | if grant.Grantee.Type != svcsdktypes.TypeGroup { 93 | continue 94 | } 95 | 96 | if *grant.Grantee.URI == uri { 97 | matching = append(matching, &grant) 98 | } 99 | } 100 | return matching 101 | } 102 | 103 | // getGrantsByCanonicalUserID searches a list of ACL grants for any that have a 104 | // canonical user type grantee with the given ID. 105 | func getGrantsByCanonicalUserID(id string, grants []svcsdktypes.Grant) []*svcsdktypes.Grant { 106 | matching := []*svcsdktypes.Grant{} 107 | 108 | for _, grant := range grants { 109 | if grant.Grantee == nil { 110 | continue 111 | } 112 | 113 | if grant.Grantee.Type != svcsdktypes.TypeCanonicalUser { 114 | continue 115 | } 116 | 117 | if *grant.Grantee.ID == id { 118 | matching = append(matching, &grant) 119 | } 120 | } 121 | return matching 122 | } 123 | 124 | // getGrantsByPermission searches a list of ACL grants for any that have the 125 | // given permission. 126 | func getGrantsByPermission(permission string, grants []svcsdktypes.Grant) []*svcsdktypes.Grant { 127 | matching := []*svcsdktypes.Grant{} 128 | 129 | for _, grant := range grants { 130 | if grant.Permission == svcsdktypes.Permission(permission) { 131 | matching = append(matching, &grant) 132 | } 133 | } 134 | return matching 135 | } 136 | 137 | // formGrantHeader will form a grant header string from a list of grants 138 | func formGrantHeader(grants []*svcsdktypes.Grant) string { 139 | headers := []string{} 140 | for _, grant := range grants { 141 | if grant.Grantee == nil { 142 | continue 143 | } 144 | 145 | if grant.Grantee.Type == svcsdktypes.TypeGroup { 146 | headers = append(headers, fmt.Sprintf(HeaderURIFormat, *grant.Grantee.URI)) 147 | } 148 | if grant.Grantee.Type == svcsdktypes.TypeCanonicalUser { 149 | headers = append(headers, fmt.Sprintf(HeaderUserIDFormat, *grant.Grantee.ID)) 150 | } 151 | } 152 | return strings.Join(headers, ",") 153 | } 154 | 155 | // isDefaultCannedACLPossibilities determines whether the list of joined ACL 156 | // possibilites is the default for a bucket. 157 | func isDefaultCannedACLPossibilities(joinedPossibilities string) bool { 158 | return matchPossibleCannedACL(CannedACLPrivate, joinedPossibilities) != nil 159 | } 160 | 161 | // matchPossibleCannedACL attempts to find a canned ACL string in a joined 162 | // list of possibilities. If any of the possibilities matches, it will be 163 | // returned, otherwise nil. 164 | func matchPossibleCannedACL(search string, joinedPossibilities string) *string { 165 | splitPossibilities := strings.Split(joinedPossibilities, CannedACLJoinDelimiter) 166 | for _, possible := range splitPossibilities { 167 | if search == possible { 168 | return &possible 169 | } 170 | } 171 | return nil 172 | } 173 | 174 | // GetHeadersFromGrants will return a list of grant headers from grants 175 | func GetHeadersFromGrants( 176 | resp *svcsdk.GetBucketAclOutput, 177 | ) aclGrantHeaders { 178 | headers := aclGrantHeaders{ 179 | FullControl: formGrantHeader(getGrantsByPermission(string(svcsdktypes.PermissionFullControl), resp.Grants)), 180 | Read: formGrantHeader(getGrantsByPermission(string(svcsdktypes.PermissionRead), resp.Grants)), 181 | ReadACP: formGrantHeader(getGrantsByPermission(string(svcsdktypes.PermissionReadAcp), resp.Grants)), 182 | Write: formGrantHeader(getGrantsByPermission(string(svcsdktypes.PermissionWrite), resp.Grants)), 183 | WriteACP: formGrantHeader(getGrantsByPermission(string(svcsdktypes.PermissionWriteAcp), resp.Grants)), 184 | } 185 | 186 | return headers 187 | } 188 | 189 | // GetPossibleCannedACLsFromGrants will return a list of canned ACLs that match 190 | // the list of grants. This method will return nil if the grants did not match 191 | // any canned ACLs. 192 | func GetPossibleCannedACLsFromGrants( 193 | resp *svcsdk.GetBucketAclOutput, 194 | ) []string { 195 | owner := resp.Owner 196 | grants := resp.Grants 197 | 198 | // All canned ACLs include a grant with owner full control 199 | if !hasOwnerFullControl(owner, grants) { 200 | return []string{} 201 | } 202 | 203 | switch len(grants) { 204 | case 1: 205 | return []string{CannedACLPrivate, CannedBucketOwnerRead, CannedBucketOwnerFullControl} 206 | case 2: 207 | execTeamGrant := getGrantsByCanonicalUserID(GranteeZATeamID, grants) 208 | if grantsContainPermission(svcsdktypes.PermissionRead, execTeamGrant) { 209 | return []string{CannedAWSExecRead} 210 | } 211 | 212 | allUsersGrants := getGrantsByGroupURI(GranteeAllUsersURI, grants) 213 | if grantsContainPermission(svcsdktypes.PermissionRead, allUsersGrants) { 214 | return []string{CannedPublicRead} 215 | } 216 | 217 | authenticatedUsersGrants := getGrantsByGroupURI(GranteeAuthenticatedUsersURI, grants) 218 | if grantsContainPermission(svcsdktypes.PermissionRead, authenticatedUsersGrants) { 219 | return []string{CannedAuthenticatedRead} 220 | } 221 | case 3: 222 | logDeliveryGrants := getGrantsByGroupURI(GranteeLogDeliveryURI, grants) 223 | if grantsContainPermission(svcsdktypes.PermissionWrite, logDeliveryGrants) && 224 | grantsContainPermission(svcsdktypes.PermissionReadAcp, logDeliveryGrants) { 225 | return []string{CannedLogDeliveryWrite} 226 | } 227 | 228 | allUsersGrants := getGrantsByGroupURI(GranteeAllUsersURI, grants) 229 | if grantsContainPermission(svcsdktypes.PermissionRead, allUsersGrants) && 230 | grantsContainPermission(svcsdktypes.PermissionWrite, allUsersGrants) { 231 | return []string{CannedPublicReadWrite} 232 | } 233 | } 234 | 235 | return []string{} 236 | } 237 | -------------------------------------------------------------------------------- /helm/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ include "ack-s3-controller.app.fullname" . }} 5 | namespace: {{ .Release.Namespace }} 6 | labels: 7 | app.kubernetes.io/name: {{ include "ack-s3-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-s3-controller.app.name" . }} 12 | helm.sh/chart: {{ include "ack-s3-controller.chart.name-version" . }} 13 | {{- range $key, $value := .Values.deployment.labels }} 14 | {{ $key }}: {{ $value | quote }} 15 | {{- end }} 16 | spec: 17 | replicas: {{ .Values.deployment.replicas }} 18 | selector: 19 | matchLabels: 20 | app.kubernetes.io/name: {{ include "ack-s3-controller.app.name" . }} 21 | app.kubernetes.io/instance: {{ .Release.Name }} 22 | template: 23 | metadata: 24 | {{- if .Values.deployment.annotations }} 25 | annotations: 26 | {{- range $key, $value := .Values.deployment.annotations }} 27 | {{ $key }}: {{ $value | quote }} 28 | {{- end }} 29 | {{- end }} 30 | labels: 31 | app.kubernetes.io/name: {{ include "ack-s3-controller.app.name" . }} 32 | app.kubernetes.io/instance: {{ .Release.Name }} 33 | app.kubernetes.io/managed-by: Helm 34 | k8s-app: {{ include "ack-s3-controller.app.name" . }} 35 | {{- range $key, $value := .Values.deployment.labels }} 36 | {{ $key }}: {{ $value | quote }} 37 | {{- end }} 38 | spec: 39 | serviceAccountName: {{ include "ack-s3-controller.service-account.name" . }} 40 | {{- if .Values.image.pullSecrets }} 41 | imagePullSecrets: 42 | {{- range .Values.image.pullSecrets }} 43 | - name: {{ . }} 44 | {{- end }} 45 | {{- end }} 46 | containers: 47 | - command: 48 | - ./bin/controller 49 | args: 50 | - --aws-region 51 | - "$(AWS_REGION)" 52 | - --aws-endpoint-url 53 | - "$(AWS_ENDPOINT_URL)" 54 | {{- if .Values.log.enable_development_logging }} 55 | - --enable-development-logging 56 | {{- end }} 57 | - --log-level 58 | - "$(ACK_LOG_LEVEL)" 59 | - --resource-tags 60 | - "$(ACK_RESOURCE_TAGS)" 61 | - --watch-namespace 62 | - "$(ACK_WATCH_NAMESPACE)" 63 | - --watch-selectors 64 | - "$(ACK_WATCH_SELECTORS)" 65 | - --reconcile-resources 66 | - "$(RECONCILE_RESOURCES)" 67 | - --deletion-policy 68 | - "$(DELETION_POLICY)" 69 | {{- if .Values.leaderElection.enabled }} 70 | - --enable-leader-election 71 | - --leader-election-namespace 72 | - "$(LEADER_ELECTION_NAMESPACE)" 73 | {{- end }} 74 | {{- if gt (int .Values.reconcile.defaultResyncPeriod) 0 }} 75 | - --reconcile-default-resync-seconds 76 | - "$(RECONCILE_DEFAULT_RESYNC_SECONDS)" 77 | {{- end }} 78 | {{- range $key, $value := .Values.reconcile.resourceResyncPeriods }} 79 | - --reconcile-resource-resync-seconds 80 | - "$(RECONCILE_RESOURCE_RESYNC_SECONDS_{{ $key | upper }})" 81 | {{- end }} 82 | {{- if gt (int .Values.reconcile.defaultMaxConcurrentSyncs) 0 }} 83 | - --reconcile-default-max-concurrent-syncs 84 | - "$(RECONCILE_DEFAULT_MAX_CONCURRENT_SYNCS)" 85 | {{- end }} 86 | {{- range $key, $value := .Values.reconcile.resourceMaxConcurrentSyncs }} 87 | - --reconcile-resource-max-concurrent-syncs 88 | - "$(RECONCILE_RESOURCE_MAX_CONCURRENT_SYNCS_{{ $key | upper }})" 89 | {{- end }} 90 | {{- if .Values.featureGates}} 91 | - --feature-gates 92 | - "$(FEATURE_GATES)" 93 | {{- end }} 94 | - --enable-carm={{ .Values.enableCARM }} 95 | image: {{ .Values.image.repository }}:{{ .Values.image.tag }} 96 | imagePullPolicy: {{ .Values.image.pullPolicy }} 97 | name: controller 98 | ports: 99 | - name: http 100 | containerPort: {{ .Values.deployment.containerPort }} 101 | resources: 102 | {{- toYaml .Values.resources | nindent 10 }} 103 | env: 104 | - name: ACK_SYSTEM_NAMESPACE 105 | valueFrom: 106 | fieldRef: 107 | fieldPath: metadata.namespace 108 | - name: AWS_REGION 109 | value: {{ .Values.aws.region }} 110 | - name: AWS_ENDPOINT_URL 111 | value: {{ .Values.aws.endpoint_url | quote }} 112 | - name: ACK_WATCH_NAMESPACE 113 | value: {{ include "ack-s3-controller.watch-namespace" . }} 114 | - name: ACK_WATCH_SELECTORS 115 | value: {{ .Values.watchSelectors }} 116 | - name: RECONCILE_RESOURCES 117 | value: {{ join "," .Values.reconcile.resources | quote }} 118 | - name: DELETION_POLICY 119 | value: {{ .Values.deletionPolicy }} 120 | - name: LEADER_ELECTION_NAMESPACE 121 | value: {{ .Values.leaderElection.namespace | quote }} 122 | - name: ACK_LOG_LEVEL 123 | value: {{ .Values.log.level | quote }} 124 | - name: ACK_RESOURCE_TAGS 125 | value: {{ join "," .Values.resourceTags | quote }} 126 | {{- if gt (int .Values.reconcile.defaultResyncPeriod) 0 }} 127 | - name: RECONCILE_DEFAULT_RESYNC_SECONDS 128 | value: {{ .Values.reconcile.defaultResyncPeriod | quote }} 129 | {{- end }} 130 | {{- range $key, $value := .Values.reconcile.resourceResyncPeriods }} 131 | - name: RECONCILE_RESOURCE_RESYNC_SECONDS_{{ $key | upper }} 132 | value: {{ $key }}={{ $value }} 133 | {{- end }} 134 | {{- if gt (int .Values.reconcile.defaultMaxConcurrentSyncs) 0 }} 135 | - name: RECONCILE_DEFAULT_MAX_CONCURRENT_SYNCS 136 | value: {{ .Values.reconcile.defaultMaxConcurrentSyncs | quote }} 137 | {{- end }} 138 | {{- range $key, $value := .Values.reconcile.resourceMaxConcurrentSyncs }} 139 | - name: RECONCILE_RESOURCE_MAX_CONCURRENT_SYNCS_{{ $key | upper }} 140 | value: {{ $key }}={{ $value }} 141 | {{- end }} 142 | {{- if .Values.featureGates}} 143 | - name: FEATURE_GATES 144 | value: {{ include "ack-s3-controller.feature-gates" . }} 145 | {{- end }} 146 | {{- if .Values.aws.credentials.secretName }} 147 | - name: AWS_SHARED_CREDENTIALS_FILE 148 | value: {{ include "ack-s3-controller.aws.credentials.path" . }} 149 | - name: AWS_PROFILE 150 | value: {{ .Values.aws.credentials.profile }} 151 | {{- end }} 152 | {{- if .Values.deployment.extraEnvVars -}} 153 | {{ toYaml .Values.deployment.extraEnvVars | nindent 8 }} 154 | {{- end }} 155 | {{- if or .Values.aws.credentials.secretName .Values.deployment.extraVolumeMounts }} 156 | volumeMounts: 157 | {{- if .Values.aws.credentials.secretName }} 158 | - name: {{ .Values.aws.credentials.secretName }} 159 | mountPath: {{ include "ack-s3-controller.aws.credentials.secret_mount_path" . }} 160 | readOnly: true 161 | {{- end }} 162 | {{- if .Values.deployment.extraVolumeMounts -}} 163 | {{ toYaml .Values.deployment.extraVolumeMounts | nindent 10 }} 164 | {{- end }} 165 | {{- end }} 166 | securityContext: 167 | allowPrivilegeEscalation: false 168 | privileged: false 169 | readOnlyRootFilesystem: true 170 | runAsNonRoot: true 171 | capabilities: 172 | drop: 173 | - ALL 174 | livenessProbe: 175 | httpGet: 176 | path: /healthz 177 | port: 8081 178 | initialDelaySeconds: 15 179 | periodSeconds: 20 180 | readinessProbe: 181 | httpGet: 182 | path: /readyz 183 | port: 8081 184 | initialDelaySeconds: 5 185 | periodSeconds: 10 186 | securityContext: 187 | seccompProfile: 188 | type: RuntimeDefault 189 | terminationGracePeriodSeconds: 10 190 | nodeSelector: {{ toYaml .Values.deployment.nodeSelector | nindent 8 }} 191 | {{ if .Values.deployment.tolerations -}} 192 | tolerations: {{ toYaml .Values.deployment.tolerations | nindent 8 }} 193 | {{ end -}} 194 | {{ if .Values.deployment.affinity -}} 195 | affinity: {{ toYaml .Values.deployment.affinity | nindent 8 }} 196 | {{ end -}} 197 | {{ if .Values.deployment.priorityClassName -}} 198 | priorityClassName: {{ .Values.deployment.priorityClassName }} 199 | {{ end -}} 200 | hostIPC: false 201 | hostPID: false 202 | hostNetwork: {{ .Values.deployment.hostNetwork }} 203 | dnsPolicy: {{ .Values.deployment.dnsPolicy }} 204 | {{- if or .Values.aws.credentials.secretName .Values.deployment.extraVolumes }} 205 | volumes: 206 | {{- if .Values.aws.credentials.secretName }} 207 | - name: {{ .Values.aws.credentials.secretName }} 208 | secret: 209 | secretName: {{ .Values.aws.credentials.secretName }} 210 | {{- end }} 211 | {{- if .Values.deployment.extraVolumes }} 212 | {{- toYaml .Values.deployment.extraVolumes | nindent 8 }} 213 | {{- end }} 214 | {{- end }} 215 | {{- with .Values.deployment.strategy }} 216 | strategy: {{- toYaml . | nindent 4 }} 217 | {{- end }} 218 | -------------------------------------------------------------------------------- /test/e2e/tests/test_bucket_adoption_policy.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 | """Integration tests for the deletion policy annotation on Bucket. 15 | """ 16 | 17 | from enum import Enum 18 | import pytest 19 | import time 20 | import logging 21 | 22 | from acktest.resources import random_suffix_name 23 | from acktest.k8s import resource as k8s 24 | from acktest import tags as tags 25 | from e2e import service_marker, CRD_GROUP, CRD_VERSION, load_s3_resource 26 | from e2e.tests.test_bucket import bucket_exists, get_bucket 27 | from e2e.replacement_values import REPLACEMENT_VALUES 28 | 29 | CREATE_WAIT_AFTER_SECONDS = 10 30 | MODIFY_WAIT_AFTER_SECONDS = 20 31 | DELETE_WAIT_AFTER_SECONDS = 10 32 | ACK_SYSTEM_TAG_PREFIX = "services.k8s.aws/" 33 | AWS_SYSTEM_TAG_PREFIX = "aws:" 34 | 35 | class AdoptionPolicy(str, Enum): 36 | NONE = "" 37 | ADOPT = "adopt" 38 | ADOPT_OR_CREATE = "adopt-or-create" 39 | 40 | 41 | @pytest.fixture 42 | def bucket_adoption_policy(request, s3_client): 43 | replacements = REPLACEMENT_VALUES.copy() 44 | bucket_name = replacements["ADOPTION_BUCKET_NAME"] 45 | 46 | replacements["ADOPTION_POLICY"] = AdoptionPolicy.ADOPT 47 | replacements["ADOPTION_FIELDS"] = f'{{\\\"name\\\": \\\"{bucket_name}\\\"}}' 48 | replacements["BUCKET_NAME"] = bucket_name 49 | 50 | filename = "" 51 | 52 | resource_name = "" 53 | 54 | marker = request.node.get_closest_marker("resource_data") 55 | assert marker is not None 56 | data = marker.args[0] 57 | assert 'adoption-policy' in data 58 | replacements["ADOPTION_POLICY"] = data['adoption-policy'] 59 | assert 'filename' in data 60 | filename = data['filename'] 61 | assert 'resource-name' in data 62 | resource_name = random_suffix_name(data['resource-name'], 32) 63 | replacements["RANDOM_BUCKET_NAME"] = resource_name 64 | 65 | resource_data = load_s3_resource( 66 | filename, 67 | additional_replacements=replacements, 68 | ) 69 | 70 | # Create k8s resource 71 | ref = k8s.CustomResourceReference( 72 | CRD_GROUP, CRD_VERSION, "buckets", 73 | resource_name, namespace="default") 74 | k8s.create_custom_resource(ref, resource_data) 75 | 76 | time.sleep(CREATE_WAIT_AFTER_SECONDS) 77 | cr = k8s.wait_resource_consumed_by_controller(ref) 78 | 79 | k8s.wait_on_condition(ref, "ACK.ResourceSynced", "True", wait_periods=5) 80 | cr = k8s.get_resource(ref) 81 | assert cr is not None 82 | 83 | yield (ref, cr) 84 | 85 | _, deleted = k8s.delete_custom_resource(ref, DELETE_WAIT_AFTER_SECONDS) 86 | assert deleted 87 | 88 | @pytest.fixture(scope="module") 89 | def adopt_stack_bucket(s3_client): 90 | replacements = REPLACEMENT_VALUES.copy() 91 | bucket_name = replacements["STACK_BUCKET_NAME"] 92 | replacements["ADOPTION_POLICY"] = AdoptionPolicy.ADOPT 93 | replacements["ADOPTION_FIELDS"] = f'{{\\\"name\\\": \\\"{bucket_name}\\\"}}' 94 | 95 | resource_data = load_s3_resource( 96 | "bucket_adoption_stack", 97 | additional_replacements=replacements, 98 | ) 99 | 100 | # Create k8s resource 101 | ref = k8s.CustomResourceReference( 102 | CRD_GROUP, CRD_VERSION, "buckets", 103 | bucket_name, namespace="default") 104 | k8s.create_custom_resource(ref, resource_data) 105 | 106 | time.sleep(CREATE_WAIT_AFTER_SECONDS) 107 | cr = k8s.wait_resource_consumed_by_controller(ref) 108 | 109 | assert cr is not None 110 | assert k8s.get_resource_exists(ref) 111 | 112 | yield (ref, cr) 113 | 114 | _, deleted = k8s.delete_custom_resource(ref, DELETE_WAIT_AFTER_SECONDS) 115 | assert deleted 116 | 117 | 118 | @service_marker 119 | @pytest.mark.canary 120 | class TestAdoptionPolicyBucket: 121 | @pytest.mark.resource_data({'adoption-policy': AdoptionPolicy.ADOPT, 'filename': 'bucket_adopt', 'resource-name': 'adopt'}) 122 | def test_adopt_policy( 123 | self, s3_client, bucket_adoption_policy, s3_resource 124 | ): 125 | (ref, cr) = bucket_adoption_policy 126 | 127 | # Spec will be added by controller 128 | assert 'spec' in cr 129 | assert 'name' in cr['spec'] 130 | bucket_name = cr['spec']['name'] 131 | 132 | updates = { 133 | "spec": { 134 | "versioning": { 135 | "status": "Suspended" 136 | }, 137 | } 138 | } 139 | 140 | k8s.patch_custom_resource(ref, updates) 141 | time.sleep(MODIFY_WAIT_AFTER_SECONDS) 142 | 143 | cr = k8s.wait_resource_consumed_by_controller(ref) 144 | assert cr is not None 145 | assert 'spec' in cr 146 | assert 'versioning' in cr['spec'] 147 | assert 'status' in cr['spec']['versioning'] 148 | status = cr['spec']['versioning']['status'] 149 | latest = get_bucket(s3_resource, bucket_name) 150 | assert latest is not None 151 | versioning = latest.Versioning() 152 | assert versioning.status == status 153 | 154 | @pytest.mark.resource_data({'adoption-policy': AdoptionPolicy.ADOPT_OR_CREATE, 'filename': 'bucket_adopt_or_create', 'resource-name': 'adopt-or-create'}) 155 | def test_adopt_or_create_policy( 156 | self, s3_client, bucket_adoption_policy, s3_resource 157 | ): 158 | (ref, cr) = bucket_adoption_policy 159 | 160 | # Spec will be added by controller 161 | k8s.wait_on_condition(ref, "ACK.ResourceSynced", "True", wait_periods=5) 162 | assert 'spec' in cr 163 | assert 'name' in cr['spec'] 164 | bucket_name = cr['spec']['name'] 165 | 166 | latest = get_bucket(s3_resource, bucket_name) 167 | assert latest is not None 168 | tagging = latest.Tagging() 169 | 170 | initial_tags = { 171 | "tag_key": "tag_value" 172 | } 173 | tags.assert_ack_system_tags( 174 | tags=tagging.tag_set, 175 | ) 176 | tags.assert_equal_without_ack_tags( 177 | expected=initial_tags, 178 | actual=tagging.tag_set, 179 | ) 180 | 181 | 182 | @pytest.mark.resource_data({'adoption-policy': AdoptionPolicy.ADOPT_OR_CREATE, 'filename': 'bucket_adopt_or_create_not_exist', 'resource-name': 'adopt-or-create-not-exist'}) 183 | def test_adopt_or_create_policy_non_existent( 184 | self, s3_client, bucket_adoption_policy, s3_resource 185 | ): 186 | (ref, cr) = bucket_adoption_policy 187 | 188 | # Spec will be added by controller 189 | assert 'spec' in cr 190 | assert 'name' in cr['spec'] 191 | k8s.wait_on_condition(ref, "ACK.ResourceSynced", "True", wait_periods=5) 192 | 193 | name = cr['spec']['name'] 194 | latest = get_bucket(s3_resource, name) 195 | assert latest is not None 196 | 197 | def test_adoption_update_tags( 198 | self, s3_client, adopt_stack_bucket, s3_resource 199 | ): 200 | (ref, cr) = adopt_stack_bucket 201 | 202 | # Spec will be added by controller 203 | assert 'spec' in cr 204 | assert 'name' in cr['spec'] 205 | assert 'tagSet' not in cr['spec']['tagging'] 206 | bucket_name = cr['spec']['name'] 207 | 208 | updates = { 209 | "spec": { 210 | "tagging": { 211 | "tagSet": [ 212 | {"key": "newKey", "value": "newVal"} 213 | ] 214 | } 215 | } 216 | } 217 | 218 | k8s.patch_custom_resource(ref, updates) 219 | time.sleep(MODIFY_WAIT_AFTER_SECONDS) 220 | k8s.wait_on_condition(ref, "ACK.ResourceSynced", "True", wait_periods=5) 221 | 222 | cr = k8s.get_resource(ref) 223 | assert cr is not None 224 | assert 'spec' in cr 225 | assert 'tagging' in cr['spec'] 226 | assert 'tagSet' in cr['spec']['tagging'] 227 | 228 | latest = get_bucket(s3_resource, bucket_name) 229 | assert latest is not None 230 | tagging = latest.Tagging() 231 | 232 | latest = cleanTags(tagging.tag_set) 233 | # +2 here because we want to see if we're also filtering 234 | # through the aws tags, besides just the ack tags 235 | assert len(tagging.tag_set) > len(latest) + 2 236 | desired = cr['spec']['tagging']['tagSet'] 237 | for i in range(1): 238 | assert desired[i]["key"] == latest[i]["Key"] 239 | assert desired[i]["value"] == latest[i]["Value"] 240 | 241 | 242 | def cleanTags(tags: list, 243 | key_member_name: str = 'Key', 244 | ) -> list: 245 | if isinstance(tags, list): 246 | return [ 247 | t for t in tags if not t[key_member_name].startswith(AWS_SYSTEM_TAG_PREFIX) 248 | and not t[key_member_name].startswith(ACK_SYSTEM_TAG_PREFIX) 249 | ] 250 | else: 251 | raise RuntimeError('tags parameter can only be list type') -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | -------------------------------------------------------------------------------- /test/e2e/tests/test_bucket.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 | """Integration tests for the S3 Bucket API. 15 | """ 16 | 17 | import pytest 18 | import time 19 | import logging 20 | import re 21 | import boto3 22 | from typing import Generator 23 | from dataclasses import dataclass 24 | 25 | from acktest.resources import random_suffix_name 26 | from acktest.k8s import resource as k8s 27 | from acktest.aws.identity import get_region 28 | from acktest import adoption as adoption 29 | from acktest import tags as tags 30 | from e2e import service_marker, CRD_GROUP, CRD_VERSION, load_s3_resource 31 | from e2e.replacement_values import REPLACEMENT_VALUES 32 | 33 | RESOURCE_KIND = "Bucket" 34 | RESOURCE_PLURAL = "buckets" 35 | 36 | CREATE_WAIT_AFTER_SECONDS = 10 37 | MODIFY_WAIT_AFTER_SECONDS = 10 38 | DELETE_WAIT_AFTER_SECONDS = 10 39 | 40 | @dataclass 41 | class Bucket: 42 | ref: k8s.CustomResourceReference 43 | resource_name: str 44 | resource_data: str 45 | 46 | def get_bucket(s3_resource, bucket_name: str): 47 | return s3_resource.Bucket(bucket_name) 48 | 49 | def bucket_exists(s3_client, bucket: Bucket) -> bool: 50 | try: 51 | resp = s3_client.list_buckets() 52 | except Exception as e: 53 | logging.debug(e) 54 | return False 55 | 56 | buckets = resp["Buckets"] 57 | for _bucket in buckets: 58 | if _bucket["Name"] == bucket.resource_name: 59 | return True 60 | 61 | return False 62 | 63 | def load_bucket_resource(resource_file_name: str, resource_name: str, additional_replacements: dict = {}): 64 | replacements = {**REPLACEMENT_VALUES.copy(), **additional_replacements} 65 | replacements["BUCKET_NAME"] = resource_name 66 | 67 | resource_data = load_s3_resource( 68 | resource_file_name, 69 | additional_replacements=replacements, 70 | ) 71 | logging.debug(resource_data) 72 | return resource_data 73 | 74 | def create_bucket(resource_file_name: str, namespace: str = "default", additional_replacements: dict = {}) -> Bucket: 75 | resource_name = random_suffix_name("s3-bucket", 24) 76 | resource_data = load_bucket_resource(resource_file_name, resource_name, additional_replacements) 77 | 78 | logging.info(f"Creating bucket {resource_name}") 79 | # Create k8s resource 80 | ref = k8s.CustomResourceReference( 81 | CRD_GROUP, CRD_VERSION, RESOURCE_PLURAL, 82 | resource_name, namespace=namespace, 83 | ) 84 | resource_data = k8s.create_custom_resource(ref, resource_data) 85 | k8s.wait_resource_consumed_by_controller(ref) 86 | 87 | time.sleep(CREATE_WAIT_AFTER_SECONDS) 88 | 89 | return Bucket(ref, resource_name, resource_data) 90 | 91 | def replace_bucket_spec(bucket: Bucket, resource_file_name: str): 92 | resource_data = load_bucket_resource(resource_file_name, bucket.resource_name) 93 | 94 | # Fetch latest version before patching 95 | bucket.resource_data = k8s.get_resource(bucket.ref) 96 | bucket.resource_data["spec"] = resource_data["spec"] 97 | bucket.resource_data = k8s.replace_custom_resource(bucket.ref, bucket.resource_data) 98 | 99 | time.sleep(MODIFY_WAIT_AFTER_SECONDS) 100 | 101 | def delete_bucket(bucket: Bucket): 102 | if not k8s.get_resource_exists(bucket.ref): 103 | return 104 | 105 | # Delete k8s resource 106 | _, deleted = k8s.delete_custom_resource(bucket.ref) 107 | assert deleted is True 108 | 109 | time.sleep(DELETE_WAIT_AFTER_SECONDS) 110 | 111 | @pytest.fixture(scope="function") 112 | def basic_bucket(s3_client) -> Generator[Bucket, None, None]: 113 | bucket = None 114 | try: 115 | bucket = create_bucket("bucket") 116 | assert k8s.get_resource_exists(bucket.ref) 117 | 118 | # assert bucket ARN is present in status 119 | bucket_k8s = bucket.resource_data = k8s.get_resource(bucket.ref) 120 | assert "arn:aws:s3:::" + bucket.resource_name == bucket_k8s["status"]["ackResourceMetadata"]["arn"] 121 | 122 | exists = bucket_exists(s3_client, bucket) 123 | assert exists 124 | except: 125 | if bucket is not None: 126 | delete_bucket(bucket) 127 | return pytest.fail("Bucket failed to create") 128 | 129 | yield bucket 130 | 131 | delete_bucket(bucket) 132 | exists = bucket_exists(s3_client, bucket) 133 | assert not exists 134 | 135 | @service_marker 136 | class TestBucket: 137 | def test_basic(self, basic_bucket): 138 | # Existance assertions are handled by the fixture 139 | assert basic_bucket 140 | 141 | def test_put_fields(self, s3_client, s3_resource, basic_bucket): 142 | self._update_assert_accelerate(basic_bucket, s3_client) 143 | self._update_assert_cors(basic_bucket, s3_resource) 144 | self._update_assert_encryption(basic_bucket, s3_client) 145 | self._update_assert_lifecycle(basic_bucket, s3_resource) 146 | self._update_assert_logging(basic_bucket, s3_resource) 147 | self._update_assert_notification(basic_bucket, s3_resource) 148 | self._update_assert_ownership_controls(basic_bucket, s3_client) 149 | self._update_assert_policy(basic_bucket, s3_resource) 150 | self._update_assert_public_access_block(basic_bucket, s3_client) 151 | self._update_assert_replication(basic_bucket, s3_client) 152 | self._update_assert_request_payment(basic_bucket, s3_resource) 153 | self._update_assert_tagging(basic_bucket, s3_resource) 154 | self._update_assert_versioning(basic_bucket, s3_resource) 155 | self._update_assert_website(basic_bucket, s3_resource) 156 | 157 | def _update_assert_accelerate(self, bucket: Bucket, s3_client): 158 | replace_bucket_spec(bucket, "bucket_accelerate") 159 | 160 | accelerate_configuration = s3_client.get_bucket_accelerate_configuration(Bucket=bucket.resource_name) 161 | 162 | desired = bucket.resource_data["spec"]["accelerate"] 163 | latest = accelerate_configuration 164 | 165 | assert desired["status"] == latest["Status"] 166 | 167 | def _update_assert_cors(self, bucket: Bucket, s3_resource): 168 | replace_bucket_spec(bucket, "bucket_cors") 169 | 170 | latest = get_bucket(s3_resource, bucket.resource_name) 171 | cors = latest.Cors() 172 | 173 | desired_rule = bucket.resource_data["spec"]["cors"]["corsRules"][0] 174 | latest_rule = cors.cors_rules[0] 175 | 176 | assert desired_rule.get("allowedMethods", []) == latest_rule.get("AllowedMethods", []) 177 | assert desired_rule.get("allowedOrigins", []) == latest_rule.get("AllowedOrigins", []) 178 | assert desired_rule.get("allowedHeaders", []) == latest_rule.get("AllowedHeaders", []) 179 | assert desired_rule.get("exposeHeaders", []) == latest_rule.get("ExposeHeaders", []) 180 | 181 | def _update_assert_encryption(self, bucket: Bucket, s3_client): 182 | replace_bucket_spec(bucket, "bucket_encryption") 183 | 184 | encryption = s3_client.get_bucket_encryption(Bucket=bucket.resource_name) 185 | 186 | desired_rule = bucket.resource_data["spec"]["encryption"]["rules"][0] 187 | latest_rule = encryption["ServerSideEncryptionConfiguration"]["Rules"][0] 188 | 189 | assert desired_rule["applyServerSideEncryptionByDefault"]["sseAlgorithm"] == \ 190 | latest_rule["ApplyServerSideEncryptionByDefault"]["SSEAlgorithm"] 191 | 192 | def _update_assert_lifecycle(self, bucket: Bucket, s3_resource): 193 | replace_bucket_spec(bucket, "bucket_lifecycle") 194 | 195 | latest = get_bucket(s3_resource, bucket.resource_name) 196 | request_payment = latest.LifecycleConfiguration() 197 | 198 | desired_rule = bucket.resource_data["spec"]["lifecycle"]["rules"][0] 199 | latest_rule = request_payment.rules[0] 200 | 201 | assert desired_rule["id"] == latest_rule["ID"] 202 | assert desired_rule["status"] == latest_rule["Status"] 203 | 204 | def _update_assert_logging(self, bucket: Bucket, s3_resource): 205 | replace_bucket_spec(bucket, "bucket_logging") 206 | 207 | latest = get_bucket(s3_resource, bucket.resource_name) 208 | logging = latest.Logging() 209 | 210 | desired = bucket.resource_data["spec"]["logging"]["loggingEnabled"] 211 | latest = logging.logging_enabled 212 | 213 | assert desired["targetBucket"] == latest["TargetBucket"] 214 | assert desired["targetPrefix"] == latest["TargetPrefix"] 215 | 216 | def _update_assert_notification(self, bucket: Bucket, s3_resource): 217 | replace_bucket_spec(bucket, "bucket_notification") 218 | 219 | latest = get_bucket(s3_resource, bucket.resource_name) 220 | notification = latest.Notification() 221 | 222 | desired_config = bucket.resource_data["spec"]["notification"]["topicConfigurations"][0] 223 | latest_config = notification.topic_configurations[0] 224 | 225 | assert desired_config["id"] == latest_config["Id"] 226 | assert desired_config["topicARN"] == latest_config["TopicArn"] 227 | 228 | def _update_assert_ownership_controls(self, bucket: Bucket, s3_client): 229 | replace_bucket_spec(bucket, "bucket_ownership_controls") 230 | 231 | ownership_controls = s3_client.get_bucket_ownership_controls(Bucket=bucket.resource_name) 232 | 233 | desired_rule = bucket.resource_data["spec"]["ownershipControls"]["rules"][0] 234 | latest_rule = ownership_controls["OwnershipControls"]["Rules"][0] 235 | 236 | assert desired_rule["objectOwnership"] == latest_rule["ObjectOwnership"] 237 | 238 | def _update_assert_policy(self, bucket: Bucket, s3_resource): 239 | replace_bucket_spec(bucket, "bucket_policy") 240 | 241 | latest = get_bucket(s3_resource, bucket.resource_name) 242 | policy = latest.Policy() 243 | 244 | # Strip any whitespace from between the two 245 | desired = re.sub(r"\s+", "", bucket.resource_data["spec"]["policy"], flags=re.UNICODE) 246 | latest = re.sub(r"\s+", "", policy.policy, flags=re.UNICODE) 247 | 248 | assert desired == latest 249 | 250 | def _update_assert_public_access_block(self, bucket: Bucket, s3_client): 251 | replace_bucket_spec(bucket, "bucket_public_access_block") 252 | 253 | public_access_block = s3_client.get_public_access_block(Bucket=bucket.resource_name) 254 | 255 | desired = bucket.resource_data["spec"]["publicAccessBlock"] 256 | latest = public_access_block["PublicAccessBlockConfiguration"] 257 | 258 | assert desired["blockPublicACLs"] == latest["BlockPublicAcls"] 259 | assert desired["blockPublicPolicy"] == latest["BlockPublicPolicy"] 260 | 261 | def _update_assert_replication(self, bucket: Bucket, s3_client): 262 | replace_bucket_spec(bucket, "bucket_replication") 263 | 264 | replication = s3_client.get_bucket_replication(Bucket=bucket.resource_name) 265 | 266 | desired = bucket.resource_data["spec"]["replication"] 267 | latest = replication["ReplicationConfiguration"] 268 | 269 | desired_rule = desired["rules"][0] 270 | latest_rule = latest["Rules"][0] 271 | 272 | assert desired["role"] == latest["Role"] 273 | assert desired_rule["id"] == latest_rule["ID"] 274 | assert desired_rule["destination"]["bucket"] == latest_rule["Destination"]["Bucket"] 275 | 276 | def _update_assert_request_payment(self, bucket: Bucket, s3_resource): 277 | replace_bucket_spec(bucket, "bucket_request_payment") 278 | 279 | latest = get_bucket(s3_resource, bucket.resource_name) 280 | request_payment = latest.RequestPayment() 281 | 282 | desired = bucket.resource_data["spec"]["requestPayment"]["payer"] 283 | latest = request_payment.payer 284 | 285 | assert desired == latest 286 | 287 | def _update_assert_tagging(self, bucket: Bucket, s3_resource): 288 | replace_bucket_spec(bucket, "bucket_tagging") 289 | 290 | latest = get_bucket(s3_resource, bucket.resource_name) 291 | tagging = latest.Tagging() 292 | 293 | desired = bucket.resource_data["spec"]["tagging"]["tagSet"] 294 | latest = tags.clean(tagging.tag_set) 295 | 296 | for i in range(2): 297 | assert desired[i]["key"] == latest[i]["Key"] 298 | assert desired[i]["value"] == latest[i]["Value"] 299 | 300 | def _update_assert_versioning(self, bucket: Bucket, s3_resource): 301 | replace_bucket_spec(bucket, "bucket_versioning") 302 | 303 | latest = get_bucket(s3_resource, bucket.resource_name) 304 | versioning = latest.Versioning() 305 | 306 | desired = bucket.resource_data["spec"]["versioning"]["status"] 307 | latest = versioning.status 308 | 309 | assert desired == latest 310 | 311 | def _update_assert_website(self, bucket: Bucket, s3_resource): 312 | replace_bucket_spec(bucket, "bucket_website") 313 | 314 | latest = get_bucket(s3_resource, bucket.resource_name) 315 | website = latest.Website() 316 | 317 | desired = bucket.resource_data["spec"]["website"] 318 | latest = website 319 | 320 | assert desired["errorDocument"]["key"] == latest.error_document["Key"] 321 | assert desired["indexDocument"]["suffix"] == latest.index_document["Suffix"] --------------------------------------------------------------------------------