Skip to content

Commit b52262f

Browse files
authored
Split CARMv2 functionality into Team Level Role and Service Level Role (#158)
Issue #, if available: Description of changes: This PR aims to resolve a concern where a user migrating from CARMv1 to v2 (i.e. to teamIDs and service level isolation support) might end up with their resources re-created into incorrect accounts just by enabling the feature flag, due to lack of v2 configuration. The PR splits CARMv2 feature into 2 different features, each behind its own feature flag: 1. team level role - `TeamLevelCARM` , the mappings are being stored in a new configmap `ack-role-team-map` 2. service level role - `ServiceLevelCARM` , the mappings can be stored in both the existing configmap `ack-role-account-map` and the new configmap `ack-role-team-map` When both feature flags are **ENABLED**, the configmap setup may look like below (this is currently all squeezed into the CARMv2 map i.e. `ack-carm-map`): `ack-role-team-map` 👇 ``` data: team-a: "arn:aws:iam::111111111111:role/team-a-global-role" s3.team-a: "arn:aws:iam::111111111111:role/team-a-s3-role" dynamodb.team-a: "arn:aws:iam::111111111111:role/team-a-dynamodb-role" ``` `ack-role-account-map` 👇 ``` data: 111111111111: arn:aws:iam::111111111111:role/global-role s3.111111111111: arn:aws:iam::111111111111:role/s3-role dynamodb.111111111111: arn:aws:iam::111111111111:role/dynamodb-role ``` When both feature flags are **DISABLED**, or neither teamID annotation or service level roles are setup, runtime continues to use the existing CARMv1 setup: `ack-role-account-map` :point_down: ``` data: 111111111111: arn:aws:iam::111111111111:role/global-role ``` By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent a606fd1 commit b52262f

File tree

5 files changed

+96
-110
lines changed

5 files changed

+96
-110
lines changed

pkg/featuregate/features.go

+7-3
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,19 @@
1717
package featuregate
1818

1919
const (
20-
// CARMv2 is the name of the CARMv2 feature.
21-
CARMv2 = "CARMv2"
20+
// TeamLevelCARM is a feature gate for enabling CARM for team-level resources.
21+
TeamLevelCARM = "TeamLevelCARM"
22+
23+
// ServiceLevelCARM is a feature gate for enabling CARM for service-level resources.
24+
ServiceLevelCARM = "ServiceLevelCARM"
2225
)
2326

2427
// defaultACKFeatureGates is a map of feature names to Feature structs
2528
// representing the default feature gates for ACK controllers.
2629
var defaultACKFeatureGates = FeatureGates{
2730
// Set feature gates here
28-
CARMv2: {Stage: Alpha, Enabled: false},
31+
TeamLevelCARM: {Stage: Alpha, Enabled: false},
32+
ServiceLevelCARM: {Stage: Alpha, Enabled: false},
2933
}
3034

3135
// FeatureStage represents the development stage of a feature.

pkg/runtime/adoption_reconciler.go

+38-46
Original file line numberDiff line numberDiff line change
@@ -113,42 +113,31 @@ func (r *adoptionReconciler) reconcile(ctx context.Context, req ctrlrt.Request)
113113
// If a user has specified a namespace that is annotated with the
114114
// an owner account ID, we need an appropriate role ARN to assume
115115
// in order to perform the reconciliation. The roles ARN are typically
116-
// stored in a ConfigMap in the ACK system namespace.
116+
// stored in the `ack-role-account-map` ConfigMap in the ACK system namespace.
117117
// If the ConfigMap is not created, or not populated with an
118118
// accountID to roleARN mapping, we need to properly requeue with a
119119
// helpful message to the user.
120120
acctID, needCARMLookup := r.getOwnerAccountID(res)
121121

122122
var roleARN ackv1alpha1.AWSResourceName
123-
if r.cfg.FeatureGates.IsEnabled(featuregate.CARMv2) {
124-
teamID := r.getTeamID(res)
125-
if teamID != "" {
126-
// The user is specifying a namespace that is annotated with a team ID.
127-
// Requeue if the corresponding roleARN is not available in the CARMv2 configmap.
128-
// Additionally, set the account ID to the role's account ID.
129-
roleARN, err = r.getRoleARNv2(string(teamID))
130-
if err != nil {
131-
ackrtlog.InfoAdoptedResource(r.log, res, fmt.Sprintf("Unable to start adoption reconcilliation %s: %v", acctID, err))
132-
return requeue.NeededAfter(err, roleARNNotAvailableRequeueDelay)
133-
}
134-
parsedARN, err := arn.Parse(string(roleARN))
135-
if err != nil {
136-
return fmt.Errorf("parsing role ARN %q from namespace annotation: %v", roleARN, err)
137-
}
138-
acctID = ackv1alpha1.AWSAccountID(parsedARN.AccountID)
139-
} else if needCARMLookup {
140-
// The user is specifying a namespace that is annotated with an owner account ID.
141-
// Requeue if the corresponding roleARN is not available in the CARMv2 configmap.
142-
roleARN, err = r.getRoleARNv2(string(acctID))
143-
if err != nil {
144-
ackrtlog.InfoAdoptedResource(r.log, res, fmt.Sprintf("Unable to start adoption reconcilliation %s: %v", acctID, err))
145-
return requeue.NeededAfter(err, roleARNNotAvailableRequeueDelay)
146-
}
123+
if teamID := r.getTeamID(res); teamID != "" && r.cfg.FeatureGates.IsEnabled(featuregate.TeamLevelCARM) {
124+
// The user is specifying a namespace that is annotated with a team ID.
125+
// Requeue if the corresponding roleARN is not available in the Teams configmap.
126+
// Additionally, set the account ID to the role's account ID.
127+
roleARN, err = r.getRoleARN(string(teamID), ackrtcache.ACKRoleTeamMap)
128+
if err != nil {
129+
ackrtlog.InfoAdoptedResource(r.log, res, fmt.Sprintf("Unable to start adoption reconcilliation %s: %v", acctID, err))
130+
return requeue.NeededAfter(err, roleARNNotAvailableRequeueDelay)
131+
}
132+
parsedARN, err := arn.Parse(string(roleARN))
133+
if err != nil {
134+
return fmt.Errorf("parsing role ARN %q from %q configmap: %v", roleARN, ackrtcache.ACKRoleTeamMap, err)
147135
}
136+
acctID = ackv1alpha1.AWSAccountID(parsedARN.AccountID)
148137
} else if needCARMLookup {
149138
// The user is specifying a namespace that is annotated with an owner account ID.
150-
// Requeue if the corresponding roleARN is not available in the Accounts (CARMv1) configmap.
151-
roleARN, err = r.getRoleARN(acctID)
139+
// Requeue if the corresponding roleARN is not available in the Accounts configmap.
140+
roleARN, err = r.getRoleARN(string(acctID), ackrtcache.ACKRoleAccountMap)
152141
if err != nil {
153142
ackrtlog.InfoAdoptedResource(r.log, res, fmt.Sprintf("Unable to start adoption reconcilliation %s: %v", acctID, err))
154143
return requeue.NeededAfter(err, roleARNNotAvailableRequeueDelay)
@@ -517,28 +506,31 @@ func (r *adoptionReconciler) getEndpointURL(
517506
return r.cfg.EndpointURL
518507
}
519508

520-
// getRoleARNv2 returns the Role ARN that should be assumed for the given account/team ID,
521-
// from the CARMv2 configmap, in order to manage the resources.
522-
func (r *adoptionReconciler) getRoleARNv2(id string) (ackv1alpha1.AWSResourceName, error) {
523-
// use service level roleARN if present
524-
serviceID := r.sc.GetMetadata().ServiceAlias + "." + id
525-
if roleARN, err := r.cache.CARMMaps.GetValue(serviceID); err == nil {
526-
return ackv1alpha1.AWSResourceName(roleARN), nil
527-
}
528-
// otherwise use account/team level roleARN
529-
roleARN, err := r.cache.CARMMaps.GetValue(id)
530-
if err != nil {
531-
return "", fmt.Errorf("retrieving role ARN for account/team ID %q from %q configmap: %v", id, ackrtcache.ACKCARMMapV2, err)
509+
// getRoleARN returns the Role ARN that should be assumed for the given accountID or teamID,
510+
// from the appropriate configmap, in order to manage the resources.
511+
func (r *adoptionReconciler) getRoleARN(id string, cacheName string) (ackv1alpha1.AWSResourceName, error) {
512+
var cache *ackrtcache.CARMMap
513+
switch cacheName {
514+
case ackrtcache.ACKRoleTeamMap:
515+
cache = r.cache.Teams
516+
case ackrtcache.ACKRoleAccountMap:
517+
cache = r.cache.Accounts
518+
default:
519+
return "", fmt.Errorf("invalid cache name: %s", cacheName)
520+
}
521+
522+
if r.cfg.FeatureGates.IsEnabled(featuregate.ServiceLevelCARM) {
523+
// use service level roleARN if present
524+
serviceID := r.sc.GetMetadata().ServiceAlias + "." + id
525+
if roleARN, err := cache.GetValue(serviceID); err == nil {
526+
return ackv1alpha1.AWSResourceName(roleARN), nil
527+
}
532528
}
533-
return ackv1alpha1.AWSResourceName(roleARN), nil
534-
}
535529

536-
// getRoleARN returns the Role ARN that should be assumed for the given account ID,
537-
// from the CARMv1 configmap, in order to manage the resources.
538-
func (r *adoptionReconciler) getRoleARN(acctID ackv1alpha1.AWSAccountID) (ackv1alpha1.AWSResourceName, error) {
539-
roleARN, err := r.cache.Accounts.GetValue(string(acctID))
530+
// otherwise use account/team level roleARN
531+
roleARN, err := cache.GetValue(id)
540532
if err != nil {
541-
return "", fmt.Errorf("retrieving role ARN for account ID %q from %q configMap: %v", acctID, ackrtcache.ACKRoleAccountMap, err)
533+
return "", fmt.Errorf("retrieving role ARN for account/team ID %q from %q configmap: %v", id, cacheName, err)
542534
}
543535
return ackv1alpha1.AWSResourceName(roleARN), nil
544536
}

pkg/runtime/cache/account.go

+3-4
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,9 @@ const (
4141
// all the AWS Account IDs associated with their AWS Role ARNs.
4242
ACKRoleAccountMap = "ack-role-account-map"
4343

44-
// ACKCARMMapV2 is the name of the v2 CARM map.
45-
// It stores the mapping for:
46-
// - Account ID to the AWS role ARNs.
47-
ACKCARMMapV2 = "ack-carm-map"
44+
// ACKRoleTeamMap is the name of the config map object storing
45+
// all the AWS Team IDs associated with their AWS Role ARNs.
46+
ACKRoleTeamMap = "ack-role-team-map"
4847
)
4948

5049
// CARMMap is responsible for caching the CARM configmap

pkg/runtime/cache/cache.go

+11-13
Original file line numberDiff line numberDiff line change
@@ -79,24 +79,22 @@ type Caches struct {
7979
// Accounts cache
8080
Accounts *CARMMap
8181

82-
// CARMMaps v2 cache
83-
CARMMaps *CARMMap
82+
// Teams cache
83+
Teams *CARMMap
8484

8585
// Namespaces cache
8686
Namespaces *NamespaceCache
8787
}
8888

8989
// New instantiate a new Caches object.
9090
func New(log logr.Logger, config Config, features featuregate.FeatureGates) Caches {
91-
var carmMaps, accounts *CARMMap
92-
if features.IsEnabled(featuregate.CARMv2) {
93-
carmMaps = NewCARMMapCache(log)
94-
} else {
95-
accounts = NewCARMMapCache(log)
91+
var teams *CARMMap
92+
if features.IsEnabled(featuregate.TeamLevelCARM) {
93+
teams = NewCARMMapCache(log)
9694
}
9795
return Caches{
98-
Accounts: accounts,
99-
CARMMaps: carmMaps,
96+
Accounts: NewCARMMapCache(log),
97+
Teams: teams,
10098
Namespaces: NewNamespaceCache(log, config.WatchScope, config.Ignored),
10199
}
102100
}
@@ -107,8 +105,8 @@ func (c Caches) Run(clientSet kubernetes.Interface) {
107105
if c.Accounts != nil {
108106
c.Accounts.Run(ACKRoleAccountMap, clientSet, stopCh)
109107
}
110-
if c.CARMMaps != nil {
111-
c.CARMMaps.Run(ACKCARMMapV2, clientSet, stopCh)
108+
if c.Teams != nil {
109+
c.Teams.Run(ACKRoleTeamMap, clientSet, stopCh)
112110
}
113111
if c.Namespaces != nil {
114112
c.Namespaces.Run(clientSet, stopCh)
@@ -127,8 +125,8 @@ func (c Caches) WaitForCachesToSync(ctx context.Context) bool {
127125
if c.Accounts != nil {
128126
accountSynced = cache.WaitForCacheSync(ctx.Done(), c.Accounts.hasSynced)
129127
}
130-
if c.CARMMaps != nil {
131-
carmSynced = cache.WaitForCacheSync(ctx.Done(), c.CARMMaps.hasSynced)
128+
if c.Teams != nil {
129+
carmSynced = cache.WaitForCacheSync(ctx.Done(), c.Teams.hasSynced)
132130
}
133131
return namespaceSynced && accountSynced && carmSynced
134132
}

pkg/runtime/reconciler.go

+37-44
Original file line numberDiff line numberDiff line change
@@ -230,40 +230,30 @@ func (r *resourceReconciler) Reconcile(ctx context.Context, req ctrlrt.Request)
230230
// If a user has specified a namespace that is annotated with the
231231
// an owner account ID, we need an appropriate role ARN to assume
232232
// in order to perform the reconciliation. The roles ARN are typically
233-
// stored in a ConfigMap in the ACK system namespace.
233+
// stored in the `ack-role-account-map` ConfigMap in the ACK system namespace.
234234
// If the ConfigMap is not created, or not populated with an
235235
// accountID to roleARN mapping, we need to properly requeue with a
236236
// helpful message to the user.
237237
acctID, needCARMLookup := r.getOwnerAccountID(desired)
238238

239239
var roleARN ackv1alpha1.AWSResourceName
240-
if r.cfg.FeatureGates.IsEnabled(featuregate.CARMv2) {
241-
teamID := r.getTeamID(desired)
242-
if teamID != "" {
243-
// The user is specifying a namespace that is annotated with a team ID.
244-
// Requeue if the corresponding roleARN is not available in the CARMv2 configmap.
245-
// Additionally, set the account ID to the role's account ID.
246-
roleARN, err = r.getRoleARNv2(string(teamID))
247-
if err != nil {
248-
return r.handleCacheError(ctx, err, desired)
249-
}
250-
parsedARN, err := arn.Parse(string(roleARN))
251-
if err != nil {
252-
return ctrlrt.Result{}, fmt.Errorf("parsing role ARN %q from namespace annotation: %v", roleARN, err)
253-
}
254-
acctID = ackv1alpha1.AWSAccountID(parsedARN.AccountID)
255-
} else if needCARMLookup {
256-
// The user is specifying a namespace that is annotated with an owner account ID.
257-
// Requeue if the corresponding roleARN is not available in the CARMv2 configmap.
258-
roleARN, err = r.getRoleARNv2(string(acctID))
259-
if err != nil {
260-
return r.handleCacheError(ctx, err, desired)
261-
}
240+
if teamID := r.getTeamID(desired); teamID != "" && r.cfg.FeatureGates.IsEnabled(featuregate.TeamLevelCARM) {
241+
// The user is specifying a namespace that is annotated with a team ID.
242+
// Requeue if the corresponding roleARN is not available in the Teams configmap.
243+
// Additionally, set the account ID to the role's account ID.
244+
roleARN, err = r.getRoleARN(string(teamID), ackrtcache.ACKRoleTeamMap)
245+
if err != nil {
246+
return r.handleCacheError(ctx, err, desired)
262247
}
248+
parsedARN, err := arn.Parse(string(roleARN))
249+
if err != nil {
250+
return ctrlrt.Result{}, fmt.Errorf("parsing role ARN %q from %q configmap: %v", roleARN, ackrtcache.ACKRoleTeamMap, err)
251+
}
252+
acctID = ackv1alpha1.AWSAccountID(parsedARN.AccountID)
263253
} else if needCARMLookup {
264254
// The user is specifying a namespace that is annotated with an owner account ID.
265-
// Requeue if the corresponding roleARN is not available in the Accounts (CARMv1) configmap.
266-
roleARN, err = r.getRoleARN(acctID)
255+
// Requeue if the corresponding roleARN is not available in the Accounts configmap.
256+
roleARN, err = r.getRoleARN(string(acctID), ackrtcache.ACKRoleAccountMap)
267257
if err != nil {
268258
return r.handleCacheError(ctx, err, desired)
269259
}
@@ -1134,28 +1124,31 @@ func (r *resourceReconciler) getTeamID(
11341124
return ackv1alpha1.TeamID("")
11351125
}
11361126

1137-
// getRoleARNv2 returns the Role ARN that should be assumed for the given account/team ID,
1138-
// from the CARMv2 configmap, in order to manage the resources.
1139-
func (r *resourceReconciler) getRoleARNv2(id string) (ackv1alpha1.AWSResourceName, error) {
1140-
// use service level roleARN if present
1141-
serviceID := r.sc.GetMetadata().ServiceAlias + "." + id
1142-
if roleARN, err := r.cache.CARMMaps.GetValue(serviceID); err == nil {
1143-
return ackv1alpha1.AWSResourceName(roleARN), nil
1144-
}
1145-
// otherwise use account/team level roleARN
1146-
roleARN, err := r.cache.CARMMaps.GetValue(id)
1147-
if err != nil {
1148-
return "", fmt.Errorf("retrieving role ARN for account/team ID %q from %q configmap: %v", id, ackrtcache.ACKCARMMapV2, err)
1127+
// getRoleARN returns the Role ARN that should be assumed for the given accountID or teamID,
1128+
// from the appropriate configmap, in order to manage the resources.
1129+
func (r *resourceReconciler) getRoleARN(id string, cacheName string) (ackv1alpha1.AWSResourceName, error) {
1130+
var cache *ackrtcache.CARMMap
1131+
switch cacheName {
1132+
case ackrtcache.ACKRoleTeamMap:
1133+
cache = r.cache.Teams
1134+
case ackrtcache.ACKRoleAccountMap:
1135+
cache = r.cache.Accounts
1136+
default:
1137+
return "", fmt.Errorf("invalid cache name: %s", cacheName)
1138+
}
1139+
1140+
if r.cfg.FeatureGates.IsEnabled(featuregate.ServiceLevelCARM) {
1141+
// use service level roleARN if present
1142+
serviceID := r.sc.GetMetadata().ServiceAlias + "." + id
1143+
if roleARN, err := cache.GetValue(serviceID); err == nil {
1144+
return ackv1alpha1.AWSResourceName(roleARN), nil
1145+
}
11491146
}
1150-
return ackv1alpha1.AWSResourceName(roleARN), nil
1151-
}
11521147

1153-
// getRoleARN returns the Role ARN that should be assumed for the given account ID,
1154-
// from the CARMv1 configmap, in order to manage the resources.
1155-
func (r *resourceReconciler) getRoleARN(acctID ackv1alpha1.AWSAccountID) (ackv1alpha1.AWSResourceName, error) {
1156-
roleARN, err := r.cache.Accounts.GetValue(string(acctID))
1148+
// otherwise use account/team level roleARN
1149+
roleARN, err := cache.GetValue(id)
11571150
if err != nil {
1158-
return "", fmt.Errorf("retrieving role ARN for account ID %q from %q configMap: %v", acctID, ackrtcache.ACKRoleAccountMap, err)
1151+
return "", fmt.Errorf("retrieving role ARN for account/team ID %q from %q configmap: %v", id, cacheName, err)
11591152
}
11601153
return ackv1alpha1.AWSResourceName(roleARN), nil
11611154
}

0 commit comments

Comments
 (0)