Skip to content

Commit 201f68a

Browse files
Add deletion policy CLI arguments and annotations (#107)
Issue #, if available: aws-controllers-k8s/community#82 Description of changes: As per the proposal: aws-controllers-k8s/community#1148 Adds support for a new optional CLI argument for the controller binary `--deletion-policy`, supporting `delete` and `retain` as available options. Setting this argument to `delete` leaves the controller as the current default behaviour, whereby it deletes the AWS resource under management before it is removed from the K8s API server. Setting this argument to `retain` modifies the default behaviour, leaving the AWS resources intact (taking no action on them) before removing it from the K8s API server. Adds support for a new annotation on `Namespace` objects: `{service}.services.k8s.aws/deletion-policy: delete/retain` (where `{service}` is the service alias of the controller eg. `s3`). This annotation overrides the deletion policy behaviour configured for the controller for all resources deployed under the namespace. Adds support for a new annotation on any ACK resource object: `services.k8s.aws/deletion-policy: delete/retain`. This annotation overrides only the deletion policy behaviour configured for the resource. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent da127a6 commit 201f68a

File tree

5 files changed

+146
-7
lines changed

5 files changed

+146
-7
lines changed

apis/core/v1alpha1/annotations.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,11 @@ const (
5959
// will either use the default behavior of aws-sdk-go to create endpoints or
6060
// aws-endpoint-url if it is set in controller binary flags and environment variables.
6161
AnnotationEndpointURL = AnnotationPrefix + "endpoint-url"
62+
// AnnotationDeletionPolicy is an annotation whose value is the identifier for the
63+
// the deletion policy for the current resource. If this annotation is set
64+
// to "delete" the resource manager will delete the AWS resource when the
65+
// K8s resource is deleted. If this annotation is set to "retain" the
66+
// resource manager will leave the AWS resource intact when the K8s resource
67+
// is deleted.
68+
AnnotationDeletionPolicy = AnnotationPrefix + "deletion-policy"
6269
)
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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 v1alpha1
15+
16+
import (
17+
"fmt"
18+
)
19+
20+
// DeletionPolicy represents how the ACK reconciler will handle the deletion of
21+
// a resource. A DeletionPolicy of "delete" will delete the underlying AWS
22+
// resource, whereas a DeletionPolicy of "retain" will only delete the K8s
23+
// object leaving the AWS resource intact.
24+
type DeletionPolicy string
25+
26+
const (
27+
DeletionPolicyDelete DeletionPolicy = "delete"
28+
DeletionPolicyRetain DeletionPolicy = "retain"
29+
)
30+
31+
func (e *DeletionPolicy) String() string {
32+
return string(*e)
33+
}
34+
35+
func (e *DeletionPolicy) Set(v string) error {
36+
switch v {
37+
case string(DeletionPolicyDelete), string(DeletionPolicyRetain):
38+
*e = DeletionPolicy(v)
39+
return nil
40+
default:
41+
return fmt.Errorf("invalid DeletionPolicy value: %s", v)
42+
}
43+
}
44+
45+
func (e *DeletionPolicy) Type() string {
46+
return "DeletionPolicy"
47+
}

pkg/config/config.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
ctrlrt "sigs.k8s.io/controller-runtime"
2828
"sigs.k8s.io/controller-runtime/pkg/log/zap"
2929

30+
ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1"
3031
acktags "github.com/aws-controllers-k8s/runtime/pkg/tags"
3132
)
3233

@@ -43,6 +44,7 @@ const (
4344
flagWatchNamespace = "watch-namespace"
4445
flagEnableWebhookServer = "enable-webhook-server"
4546
flagWebhookServerAddr = "webhook-server-addr"
47+
flagDeletionPolicy = "deletion-policy"
4648
envVarAWSRegion = "AWS_REGION"
4749
)
4850

@@ -74,6 +76,7 @@ type Config struct {
7476
WatchNamespace string
7577
EnableWebhookServer bool
7678
WebhookServerAddr string
79+
DeletionPolicy ackv1alpha1.DeletionPolicy
7780
}
7881

7982
// BindFlags defines CLI/runtime configuration options
@@ -145,6 +148,10 @@ func (cfg *Config) BindFlags() {
145148
"Specific namespace the service controller will watch for object creation from CRD. "+
146149
" By default it will listen to all namespaces",
147150
)
151+
flag.Var(
152+
&cfg.DeletionPolicy, flagDeletionPolicy,
153+
"The default deletion policy for all resources managed by the controller",
154+
)
148155
}
149156

150157
// SetupLogger initializes the logger used in the service controller
@@ -222,6 +229,10 @@ func (cfg *Config) Validate() error {
222229
if cfg.EnableWebhookServer && cfg.WebhookServerAddr == "" {
223230
return errors.New("empty webhook server address")
224231
}
232+
233+
if cfg.DeletionPolicy == "" {
234+
cfg.DeletionPolicy = ackv1alpha1.DeletionPolicyDelete
235+
}
225236
return nil
226237
}
227238

pkg/runtime/cache/namespace.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
package cache
1515

1616
import (
17+
"strings"
1718
"sync"
1819

1920
"github.com/go-logr/logr"
@@ -33,6 +34,8 @@ type namespaceInfo struct {
3334
ownerAccountID string
3435
// services.k8s.aws/endpoint-url Annotation
3536
endpointURL string
37+
// {service}.services.k8s.aws/deletion-policy Annotations (keyed by service)
38+
deletionPolicies map[string]string
3639
}
3740

3841
// getDefaultRegion returns the default region value
@@ -59,6 +62,17 @@ func (n *namespaceInfo) getEndpointURL() string {
5962
return n.endpointURL
6063
}
6164

65+
// getDeletionPolicy returns the namespace deletion policy for a given service
66+
func (n *namespaceInfo) getDeletionPolicy(service string) string {
67+
if n == nil {
68+
return ""
69+
}
70+
if val, exists := n.deletionPolicies[strings.ToLower(service)]; exists {
71+
return val
72+
}
73+
return ""
74+
}
75+
6276
// NamespaceCache is responsible of keeping track of namespaces
6377
// annotations, and caching those related to the ACK controller.
6478
type NamespaceCache struct {
@@ -150,6 +164,16 @@ func (c *NamespaceCache) GetEndpointURL(namespace string) (string, bool) {
150164
return "", false
151165
}
152166

167+
// GetDeletionPolicy returns the deletion policy if it exists
168+
func (c *NamespaceCache) GetDeletionPolicy(namespace string, service string) (string, bool) {
169+
info, ok := c.getNamespaceInfo(namespace)
170+
if ok {
171+
e := info.getDeletionPolicy(service)
172+
return e, e != ""
173+
}
174+
return "", false
175+
}
176+
153177
// getNamespaceInfo reads a namespace cached annotations and
154178
// return a given namespace default aws region, owner account id and endpoint url.
155179
// This function is thread safe.
@@ -177,6 +201,17 @@ func (c *NamespaceCache) setNamespaceInfoFromK8sObject(ns *corev1.Namespace) {
177201
if ok {
178202
nsInfo.endpointURL = EndpointURL
179203
}
204+
205+
nsInfo.deletionPolicies = map[string]string{}
206+
nsDeletionPolicySuffix := "." + ackv1alpha1.AnnotationDeletionPolicy
207+
for key, elem := range nsa {
208+
if !strings.HasSuffix(key, nsDeletionPolicySuffix) {
209+
continue
210+
}
211+
service := strings.TrimSuffix(key, nsDeletionPolicySuffix)
212+
nsInfo.deletionPolicies[service] = elem
213+
}
214+
180215
c.Lock()
181216
defer c.Unlock()
182217
c.namespaceInfos[ns.ObjectMeta.Name] = nsInfo

pkg/runtime/reconciler.go

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -194,10 +194,20 @@ func (r *resourceReconciler) reconcile(
194194
res acktypes.AWSResource,
195195
) (acktypes.AWSResource, error) {
196196
if res.IsBeingDeleted() {
197-
// Resolve references before deleting the resource.
198-
// Ignore any errors while resolving the references
199-
res, _ = rm.ResolveReferences(ctx, r.apiReader, res)
200-
return r.deleteResource(ctx, rm, res)
197+
// Determine whether we should retain or delete the resource
198+
if r.getDeletionPolicy(res) == ackv1alpha1.DeletionPolicyDelete {
199+
// Resolve references before deleting the resource.
200+
// Ignore any errors while resolving the references
201+
res, _ = rm.ResolveReferences(ctx, r.apiReader, res)
202+
return r.deleteResource(ctx, rm, res)
203+
}
204+
205+
rlog := ackrtlog.FromContext(ctx)
206+
rlog.Info("AWS resource will not be deleted - deletion policy set to retain")
207+
if err := r.setResourceUnmanaged(ctx, res); err != nil {
208+
return res, err
209+
}
210+
return r.handleRequeues(ctx, res)
201211
}
202212
latest, err := r.Sync(ctx, rm, res)
203213
if err != nil {
@@ -1015,9 +1025,9 @@ func (r *resourceReconciler) getRoleARN(
10151025
//
10161026
// If the resource has not yet been created, we look for the AWS region
10171027
// in the following order of precedence:
1018-
// - The resource's `services.k8s.aws/region` annotation, if present
1019-
// - The resource's Namespace's `services.k8s.aws/region` annotation, if present
1020-
// - The controller's `--aws-region` CLI flag
1028+
// - The resource's `services.k8s.aws/region` annotation, if present
1029+
// - The resource's Namespace's `services.k8s.aws/region` annotation, if present
1030+
// - The controller's `--aws-region` CLI flag
10211031
func (r *resourceReconciler) getRegion(
10221032
res acktypes.AWSResource,
10231033
) ackv1alpha1.AWSRegion {
@@ -1045,6 +1055,35 @@ func (r *resourceReconciler) getRegion(
10451055
return ackv1alpha1.AWSRegion(r.cfg.Region)
10461056
}
10471057

1058+
// getDeletionPolicy returns the resource's deletion policy based on the default
1059+
// behaviour or any other overriding annotations.
1060+
//
1061+
// We look for the deletion policy in the annotations based on the following
1062+
// precedence:
1063+
// - The resource's `services.k8s.aws/deletion-policy` annotation, if present
1064+
// - The resource's Namespace's `{service}.services.k8s.aws/deletion-policy` annotation, if present
1065+
// - The controller's `--deletion-policy` CLI flag
1066+
func (r *resourceReconciler) getDeletionPolicy(
1067+
res acktypes.AWSResource,
1068+
) ackv1alpha1.DeletionPolicy {
1069+
// look for deletion policy in CR metadata annotations
1070+
resAnnotations := res.MetaObject().GetAnnotations()
1071+
deletionPolicy, ok := resAnnotations[ackv1alpha1.AnnotationDeletionPolicy]
1072+
if ok {
1073+
return ackv1alpha1.DeletionPolicy(deletionPolicy)
1074+
}
1075+
1076+
// look for default deletion policy in namespace metadata annotations
1077+
ns := res.MetaObject().GetNamespace()
1078+
deletionPolicy, ok = r.cache.Namespaces.GetDeletionPolicy(ns, r.sc.GetMetadata().ServiceAlias)
1079+
if ok {
1080+
return ackv1alpha1.DeletionPolicy(deletionPolicy)
1081+
}
1082+
1083+
// use controller configuration policy
1084+
return r.cfg.DeletionPolicy
1085+
}
1086+
10481087
// getEndpointURL returns the AWS account that owns the supplied resource.
10491088
// We look for the namespace associated endpoint url, if that is set we use it.
10501089
// Otherwise if none of these annotations are set we use the endpoint url specified

0 commit comments

Comments
 (0)