Skip to content

Commit c9dd0d5

Browse files
authored
Merge pull request #213 from anchore/account-routing
2 parents 07b4ac2 + 132bfbb commit c9dd0d5

File tree

7 files changed

+188
-21
lines changed

7 files changed

+188
-21
lines changed

anchore-k8s-inventory.yaml

+11
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,17 @@ account-routes:
5252
# - default
5353
# - ^kube-*
5454

55+
# Route namespaces to anchore accounts by a label on the namespace
56+
account-route-by-namespace-label:
57+
# The name of the namespace label that will be used to route the contents of
58+
# that namespace to the Anchore account matching the value of the label
59+
key: # e.g anchore.io/account.name
60+
# The name of the account to route inventory to for a namespace that is missing the label
61+
# If not set then it will default to the account specified in the anchore credentials
62+
default-account: # e.g. admin
63+
# If true will exclude inventorying namespaces that are missing the specified label
64+
ignore-namespace-missing-label: false
65+
5566
# Kubernetes API configuration parameters (should not need tuning)
5667
kubernetes:
5768
# Sets the request timeout for kubernetes API requests

internal/config/config.go

+17-9
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,16 @@ type Application struct {
4040
Quiet bool `mapstructure:"quiet"`
4141
Log Logging `mapstructure:"log"`
4242
CliOptions CliOnlyOptions
43-
Dev Development `mapstructure:"dev"`
44-
KubeConfig KubeConf `mapstructure:"kubeconfig"`
45-
Kubernetes KubernetesAPI `mapstructure:"kubernetes"`
46-
Namespaces []string `mapstructure:"namespaces"`
47-
KubernetesRequestTimeoutSeconds int64 `mapstructure:"kubernetes-request-timeout-seconds"`
48-
NamespaceSelectors NamespaceSelector `mapstructure:"namespace-selectors"`
49-
AccountRoutes AccountRoutes `mapstructure:"account-routes"`
50-
MissingRegistryOverride string `mapstructure:"missing-registry-override"`
51-
MissingTagPolicy MissingTagConf `mapstructure:"missing-tag-policy"`
43+
Dev Development `mapstructure:"dev"`
44+
KubeConfig KubeConf `mapstructure:"kubeconfig"`
45+
Kubernetes KubernetesAPI `mapstructure:"kubernetes"`
46+
Namespaces []string `mapstructure:"namespaces"`
47+
KubernetesRequestTimeoutSeconds int64 `mapstructure:"kubernetes-request-timeout-seconds"`
48+
NamespaceSelectors NamespaceSelector `mapstructure:"namespace-selectors"`
49+
AccountRoutes AccountRoutes `mapstructure:"account-routes"`
50+
AccountRouteByNamespaceLabel AccountRouteByNamespaceLabel `mapstructure:"account-route-by-namespace-label"`
51+
MissingRegistryOverride string `mapstructure:"missing-registry-override"`
52+
MissingTagPolicy MissingTagConf `mapstructure:"missing-tag-policy"`
5253
RunMode mode.Mode
5354
Mode string `mapstructure:"mode"`
5455
IgnoreNotRunning bool `mapstructure:"ignore-not-running"`
@@ -78,6 +79,12 @@ type AccountRouteDetails struct {
7879
Namespaces []string `mapstructure:"namespaces"`
7980
}
8081

82+
type AccountRouteByNamespaceLabel struct {
83+
LabelKey string `mapstructure:"key"`
84+
DefaultAccount string `mapstructure:"default-account"`
85+
IgnoreMissingLabel bool `mapstructure:"ignore-missing-label"`
86+
}
87+
8188
// KubernetesAPI details the configuration for interacting with the k8s api server
8289
type KubernetesAPI struct {
8390
RequestTimeoutSeconds int64 `mapstructure:"request-timeout-seconds"`
@@ -138,6 +145,7 @@ func setNonCliDefaultValues(v *viper.Viper) {
138145
v.SetDefault("missing-tag-policy.policy", "digest")
139146
v.SetDefault("missing-tag-policy.tag", "UNKNOWN")
140147
v.SetDefault("account-routes", AccountRoutes{})
148+
v.SetDefault("account-route-by-namespace-label", AccountRouteByNamespaceLabel{})
141149
v.SetDefault("namespaces", []string{})
142150
v.SetDefault("namespace-selectors.include", []string{})
143151
v.SetDefault("namespace-selectors.exclude", []string{})

internal/config/test-fixtures/snapshot/TestDefaultConfigString.golden

+4
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ namespaceselectors:
3232
exclude: []
3333
ignoreempty: false
3434
accountroutes: {}
35+
accountroutebynamespacelabel:
36+
labelkey: ""
37+
defaultaccount: ""
38+
ignoremissinglabel: false
3539
missingregistryoverride: ""
3640
missingtagpolicy:
3741
policy: digest

internal/config/test-fixtures/snapshot/TestEmptyConfigString.golden

+4
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ namespaceselectors:
3232
exclude: []
3333
ignoreempty: false
3434
accountroutes: {}
35+
accountroutebynamespacelabel:
36+
labelkey: ""
37+
defaultaccount: ""
38+
ignoremissinglabel: false
3539
missingregistryoverride: ""
3640
missingtagpolicy:
3741
policy: ""

internal/config/test-fixtures/snapshot/TestSensitiveConfigString.golden

+4
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ namespaceselectors:
3232
exclude: []
3333
ignoreempty: false
3434
accountroutes: {}
35+
accountroutebynamespacelabel:
36+
labelkey: ""
37+
defaultaccount: ""
38+
ignoremissinglabel: false
3539
missingregistryoverride: ""
3640
missingtagpolicy:
3741
policy: digest

pkg/lib.go

+20-5
Original file line numberDiff line numberDiff line change
@@ -261,9 +261,14 @@ func GetAllNamespaces(cfg *config.Application) ([]inventory.Namespace, error) {
261261
return namespaces, nil
262262
}
263263

264-
func GetAccountRoutedNamespaces(defaultAccount string, namespaces []inventory.Namespace, accountRoutes config.AccountRoutes) map[string][]inventory.Namespace {
264+
func GetAccountRoutedNamespaces(defaultAccount string, namespaces []inventory.Namespace,
265+
accountRoutes config.AccountRoutes, namespaceLabelRouting config.AccountRouteByNamespaceLabel) map[string][]inventory.Namespace {
265266
accountRoutesForAllNamespaces := make(map[string][]inventory.Namespace)
266267

268+
if namespaceLabelRouting.DefaultAccount != "" {
269+
defaultAccount = namespaceLabelRouting.DefaultAccount
270+
}
271+
267272
accountNamespaces := make(map[string]struct{})
268273
for routeNS, route := range accountRoutes {
269274
for _, ns := range namespaces {
@@ -275,9 +280,19 @@ func GetAccountRoutedNamespaces(defaultAccount string, namespaces []inventory.Na
275280
}
276281
}
277282
}
278-
// Add namespaces that are not in any account route to the default account
283+
// If there is a namespace label routing, add namespaces to the account routes based on the label,
284+
// if the namespace has not already been added to an account route set via explicit configuration in
285+
// accountRoutes config. (This overrides the label routing for the case where the label cannot be changed).
286+
// Otherwise, add namespaces that are not in any account route to the default account unless disabled.
279287
for _, ns := range namespaces {
280-
if _, ok := accountNamespaces[ns.Name]; !ok {
288+
_, namespaceRouted := accountNamespaces[ns.Name]
289+
if namespaceLabelRouting.LabelKey != "" && !namespaceRouted {
290+
if account, ok := ns.Labels[namespaceLabelRouting.LabelKey]; ok {
291+
accountRoutesForAllNamespaces[account] = append(accountRoutesForAllNamespaces[account], ns)
292+
} else if !namespaceLabelRouting.IgnoreMissingLabel {
293+
accountRoutesForAllNamespaces[defaultAccount] = append(accountRoutesForAllNamespaces[defaultAccount], ns)
294+
}
295+
} else if !namespaceRouted {
281296
accountRoutesForAllNamespaces[defaultAccount] = append(accountRoutesForAllNamespaces[defaultAccount], ns)
282297
}
283298
}
@@ -292,14 +307,14 @@ func GetInventoryReports(cfg *config.Application) (AccountRoutedReports, error)
292307

293308
namespaces, _ := GetAllNamespaces(cfg)
294309

295-
if len(cfg.AccountRoutes) == 0 {
310+
if len(cfg.AccountRoutes) == 0 && cfg.AccountRouteByNamespaceLabel.LabelKey == "" {
296311
allNamespacesReport, err := GetInventoryReportForNamespaces(cfg, namespaces)
297312
if err != nil {
298313
return AccountRoutedReports{}, err
299314
}
300315
reports[cfg.AnchoreDetails.Account] = allNamespacesReport
301316
} else {
302-
accountRoutesForAllNamespaces := GetAccountRoutedNamespaces(cfg.AnchoreDetails.Account, namespaces, cfg.AccountRoutes)
317+
accountRoutesForAllNamespaces := GetAccountRoutedNamespaces(cfg.AnchoreDetails.Account, namespaces, cfg.AccountRoutes, cfg.AccountRouteByNamespaceLabel)
303318

304319
for account, namespaces := range accountRoutesForAllNamespaces {
305320
nsNames := make([]string, 0)

pkg/lib_test.go

+128-7
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,31 @@ import (
1212
var (
1313
TestNamespace1 = inventory.Namespace{
1414
Name: "ns1",
15+
Labels: map[string]string{
16+
"anchore.io/account": "account1",
17+
},
1518
}
1619
TestNamespace2 = inventory.Namespace{
1720
Name: "ns2",
21+
Labels: map[string]string{
22+
"anchore.io/account": "account2",
23+
},
1824
}
1925
TestNamespace3 = inventory.Namespace{
2026
Name: "ns3",
27+
Labels: map[string]string{
28+
"anchore.io/account": "account3",
29+
},
2130
}
2231
TestNamespace4 = inventory.Namespace{
2332
Name: "ns4",
33+
Labels: map[string]string{
34+
"anchore.io/account": "account4",
35+
},
36+
}
37+
TestNamespace5 = inventory.Namespace{
38+
Name: "ns5-no-label",
39+
Labels: map[string]string{},
2440
}
2541
TestNamespaces = []inventory.Namespace{
2642
TestNamespace1,
@@ -32,9 +48,10 @@ var (
3248

3349
func TestGetAccountRoutedNamespaces(t *testing.T) {
3450
type args struct {
35-
defaultAccount string
36-
namespaces []inventory.Namespace
37-
accountRoutes config.AccountRoutes
51+
defaultAccount string
52+
namespaces []inventory.Namespace
53+
accountRoutes config.AccountRoutes
54+
namespaceLabelRouting config.AccountRouteByNamespaceLabel
3855
}
3956
tests := []struct {
4057
name string
@@ -44,9 +61,10 @@ func TestGetAccountRoutedNamespaces(t *testing.T) {
4461
{
4562
name: "no account routes all to default",
4663
args: args{
47-
defaultAccount: "admin",
48-
namespaces: TestNamespaces,
49-
accountRoutes: config.AccountRoutes{},
64+
defaultAccount: "admin",
65+
namespaces: TestNamespaces,
66+
accountRoutes: config.AccountRoutes{},
67+
namespaceLabelRouting: config.AccountRouteByNamespaceLabel{},
5068
},
5169
want: map[string][]inventory.Namespace{
5270
"admin": TestNamespaces,
@@ -71,6 +89,7 @@ func TestGetAccountRoutedNamespaces(t *testing.T) {
7189
Namespaces: []string{"ns4"},
7290
},
7391
},
92+
namespaceLabelRouting: config.AccountRouteByNamespaceLabel{},
7493
},
7594
want: map[string][]inventory.Namespace{
7695
"account1": {TestNamespace1},
@@ -89,15 +108,117 @@ func TestGetAccountRoutedNamespaces(t *testing.T) {
89108
Namespaces: []string{"ns.*"},
90109
},
91110
},
111+
namespaceLabelRouting: config.AccountRouteByNamespaceLabel{},
92112
},
93113
want: map[string][]inventory.Namespace{
94114
"account1": TestNamespaces,
95115
},
96116
},
117+
{
118+
name: "namespaces to accounts that match a label only",
119+
args: args{
120+
defaultAccount: "admin",
121+
namespaces: TestNamespaces,
122+
accountRoutes: config.AccountRoutes{},
123+
namespaceLabelRouting: config.AccountRouteByNamespaceLabel{
124+
LabelKey: "anchore.io/account",
125+
DefaultAccount: "default",
126+
IgnoreMissingLabel: false,
127+
},
128+
},
129+
want: map[string][]inventory.Namespace{
130+
"account1": {TestNamespace1},
131+
"account2": {TestNamespace2},
132+
"account3": {TestNamespace3},
133+
"account4": {TestNamespace4},
134+
},
135+
},
136+
{
137+
name: "namespaces to accounts that match a label only with namespace missing label (default account not set)",
138+
args: args{
139+
defaultAccount: "admin",
140+
namespaces: append(TestNamespaces, TestNamespace5),
141+
accountRoutes: config.AccountRoutes{},
142+
namespaceLabelRouting: config.AccountRouteByNamespaceLabel{
143+
LabelKey: "anchore.io/account",
144+
DefaultAccount: "",
145+
IgnoreMissingLabel: false,
146+
},
147+
},
148+
want: map[string][]inventory.Namespace{
149+
"account1": {TestNamespace1},
150+
"account2": {TestNamespace2},
151+
"account3": {TestNamespace3},
152+
"account4": {TestNamespace4},
153+
"admin": {TestNamespace5},
154+
},
155+
},
156+
{
157+
name: "namespaces to accounts that match a label only with namespace missing label (default account set)",
158+
args: args{
159+
defaultAccount: "admin",
160+
namespaces: append(TestNamespaces, TestNamespace5),
161+
accountRoutes: config.AccountRoutes{},
162+
namespaceLabelRouting: config.AccountRouteByNamespaceLabel{
163+
LabelKey: "anchore.io/account",
164+
DefaultAccount: "defaultoverride",
165+
IgnoreMissingLabel: false,
166+
},
167+
},
168+
want: map[string][]inventory.Namespace{
169+
"account1": {TestNamespace1},
170+
"account2": {TestNamespace2},
171+
"account3": {TestNamespace3},
172+
"account4": {TestNamespace4},
173+
"defaultoverride": {TestNamespace5},
174+
},
175+
},
176+
{
177+
name: "namespaces to accounts that match a label only with namespace missing label set to ignore",
178+
args: args{
179+
defaultAccount: "admin",
180+
namespaces: append(TestNamespaces, TestNamespace5),
181+
accountRoutes: config.AccountRoutes{},
182+
namespaceLabelRouting: config.AccountRouteByNamespaceLabel{
183+
LabelKey: "anchore.io/account",
184+
DefaultAccount: "",
185+
IgnoreMissingLabel: true,
186+
},
187+
},
188+
want: map[string][]inventory.Namespace{
189+
"account1": {TestNamespace1},
190+
"account2": {TestNamespace2},
191+
"account3": {TestNamespace3},
192+
"account4": {TestNamespace4},
193+
},
194+
},
195+
{
196+
name: "mix of account routes and label routing",
197+
args: args{
198+
defaultAccount: "admin",
199+
namespaces: TestNamespaces,
200+
accountRoutes: config.AccountRoutes{
201+
"explicitaccount1": config.AccountRouteDetails{
202+
Namespaces: []string{"ns1"},
203+
},
204+
},
205+
namespaceLabelRouting: config.AccountRouteByNamespaceLabel{
206+
LabelKey: "anchore.io/account",
207+
DefaultAccount: "default",
208+
IgnoreMissingLabel: false,
209+
},
210+
},
211+
want: map[string][]inventory.Namespace{
212+
"explicitaccount1": {TestNamespace1},
213+
"account2": {TestNamespace2},
214+
"account3": {TestNamespace3},
215+
"account4": {TestNamespace4},
216+
},
217+
},
97218
}
98219
for _, tt := range tests {
99220
t.Run(tt.name, func(t *testing.T) {
100-
got := GetAccountRoutedNamespaces(tt.args.defaultAccount, tt.args.namespaces, tt.args.accountRoutes)
221+
got := GetAccountRoutedNamespaces(tt.args.defaultAccount, tt.args.namespaces, tt.args.accountRoutes, tt.args.namespaceLabelRouting)
101222
assert.Equal(t, tt.want, got)
102223
})
103224
}

0 commit comments

Comments
 (0)