Skip to content

Commit 2fa1a04

Browse files
committed
Order branches and commits where loaded, remove marshal overrides
1 parent 7c717b8 commit 2fa1a04

File tree

8 files changed

+329
-226
lines changed

8 files changed

+329
-226
lines changed

cmd/ci-secret-bootstrap/main_test.go

Lines changed: 71 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -176,36 +176,61 @@ var (
176176
}
177177

178178
defaultConfig = secretbootstrap.Config{
179+
ClusterGroups: map[string][]string{},
179180
Secrets: []secretbootstrap.SecretConfig{
181+
{
182+
From: map[string]secretbootstrap.ItemContext{
183+
".dockerconfigjson": {
184+
Item: "quay.io",
185+
Field: "pull-credentials",
186+
DockerConfigJSONData: []secretbootstrap.DockerConfigJSONData{},
187+
},
188+
},
189+
To: []secretbootstrap.SecretContext{
190+
{
191+
Cluster: "default",
192+
Namespace: "ci",
193+
Name: "ci-pull-credentials",
194+
Type: "kubernetes.io/dockerconfigjson",
195+
},
196+
},
197+
},
180198
{
181199
From: map[string]secretbootstrap.ItemContext{
182200
"key-name-1": {
183-
Item: "item-name-1",
184-
Field: "field-name-1",
201+
Item: "item-name-1",
202+
Field: "field-name-1",
203+
DockerConfigJSONData: []secretbootstrap.DockerConfigJSONData{},
185204
},
186205
"key-name-2": {
187-
Item: "item-name-1",
188-
Field: "field-name-2",
206+
Item: "item-name-1",
207+
Field: "field-name-2",
208+
DockerConfigJSONData: []secretbootstrap.DockerConfigJSONData{},
189209
},
190210
"key-name-3": {
191-
Item: "item-name-1",
192-
Field: "field-name-3",
211+
Item: "item-name-1",
212+
Field: "field-name-3",
213+
DockerConfigJSONData: []secretbootstrap.DockerConfigJSONData{},
193214
},
194215
"key-name-4": {
195-
Item: "item-name-2",
196-
Field: "field-name-1",
216+
Item: "item-name-2",
217+
Field: "field-name-1",
218+
DockerConfigJSONData: []secretbootstrap.DockerConfigJSONData{},
197219
},
198220
"key-name-5": {
199-
Item: "item-name-2",
200-
Field: "field-name-2",
221+
Item: "item-name-2",
222+
Field: "field-name-2",
223+
DockerConfigJSONData: []secretbootstrap.DockerConfigJSONData{},
201224
},
202225
"key-name-6": {
203-
Item: "item-name-3",
204-
Field: "field-name-1",
226+
Item: "item-name-3",
227+
Field: "field-name-1",
228+
DockerConfigJSONData: []secretbootstrap.DockerConfigJSONData{},
205229
},
206230
"key-name-7": {
207-
Item: "item-name-2",
208-
Field: "field-name-2",
231+
Item: "item-name-2",
232+
Field: "field-name-2",
233+
DockerConfigJSONData: []secretbootstrap.DockerConfigJSONData{},
209234
},
210235
},
211236
To: []secretbootstrap.SecretContext{
@@ -221,55 +246,47 @@ var (
221246
},
222247
},
223248
},
224-
{
225-
From: map[string]secretbootstrap.ItemContext{
226-
".dockerconfigjson": {
227-
Item: "quay.io",
228-
Field: "pull-credentials",
229-
},
230-
},
231-
To: []secretbootstrap.SecretContext{
232-
{
233-
Cluster: "default",
234-
Namespace: "ci",
235-
Name: "ci-pull-credentials",
236-
Type: "kubernetes.io/dockerconfigjson",
237-
},
238-
},
239-
},
240249
},
241250
}
242251
defaultConfigWithoutDefaultCluster = secretbootstrap.Config{
252+
ClusterGroups: map[string][]string{},
243253
Secrets: []secretbootstrap.SecretConfig{
244254
{
245255
From: map[string]secretbootstrap.ItemContext{
246256
"key-name-1": {
247-
Item: "item-name-1",
248-
Field: "field-name-1",
257+
Item: "item-name-1",
258+
Field: "field-name-1",
259+
DockerConfigJSONData: []secretbootstrap.DockerConfigJSONData{},
249260
},
250261
"key-name-2": {
251-
Item: "item-name-1",
252-
Field: "field-name-2",
262+
Item: "item-name-1",
263+
Field: "field-name-2",
264+
DockerConfigJSONData: []secretbootstrap.DockerConfigJSONData{},
253265
},
254266
"key-name-3": {
255-
Item: "item-name-1",
256-
Field: "field-name-3",
267+
Item: "item-name-1",
268+
Field: "field-name-3",
269+
DockerConfigJSONData: []secretbootstrap.DockerConfigJSONData{},
257270
},
258271
"key-name-4": {
259-
Item: "item-name-2",
260-
Field: "field-name-1",
272+
Item: "item-name-2",
273+
Field: "field-name-1",
274+
DockerConfigJSONData: []secretbootstrap.DockerConfigJSONData{},
261275
},
262276
"key-name-5": {
263-
Item: "item-name-2",
264-
Field: "field-name-2",
277+
Item: "item-name-2",
278+
Field: "field-name-2",
279+
DockerConfigJSONData: []secretbootstrap.DockerConfigJSONData{},
265280
},
266281
"key-name-6": {
267-
Item: "item-name-3",
268-
Field: "field-name-1",
282+
Item: "item-name-3",
283+
Field: "field-name-1",
284+
DockerConfigJSONData: []secretbootstrap.DockerConfigJSONData{},
269285
},
270286
"key-name-7": {
271-
Item: "item-name-2",
272-
Field: "field-name-2",
287+
Item: "item-name-2",
288+
Field: "field-name-2",
289+
DockerConfigJSONData: []secretbootstrap.DockerConfigJSONData{},
273290
},
274291
},
275292
To: []secretbootstrap.SecretContext{
@@ -375,8 +392,14 @@ func TestCompleteOptions(t *testing.T) {
375392
expectedConfig: secretbootstrap.Config{
376393
ClusterGroups: map[string][]string{"group-a": {"default"}},
377394
Secrets: []secretbootstrap.SecretConfig{{
378-
From: map[string]secretbootstrap.ItemContext{"key-name-1": {Item: "item-name-1", Field: "field-name-1"}},
379-
To: []secretbootstrap.SecretContext{{ClusterGroups: []string{"group-a"}, Cluster: "default", Namespace: "ns", Name: "name"}},
395+
From: map[string]secretbootstrap.ItemContext{
396+
"key-name-1": {
397+
Item: "item-name-1",
398+
Field: "field-name-1",
399+
DockerConfigJSONData: []secretbootstrap.DockerConfigJSONData{},
400+
},
401+
},
402+
To: []secretbootstrap.SecretContext{{ClusterGroups: []string{"group-a"}, Cluster: "default", Namespace: "ns", Name: "name"}},
380403
}},
381404
},
382405
expectedClusters: []string{"default"},
@@ -997,12 +1020,12 @@ func TestConstructSecrets(t *testing.T) {
9971020
},
9981021
},
9991022
},
1000-
expectedError: `[config.0."key-name-1": item at path "prefix/item-name-1" has no key "field-name-1", config.1.".dockerconfigjson": Error making API request.
1023+
expectedError: `[config.0.".dockerconfigjson": Error making API request.
10011024
10021025
URL: GET fakeVaultClient.GetKV
10031026
Code: 404. Errors:
10041027
1005-
* no data at path prefix/quay.io]`,
1028+
* no data at path prefix/quay.io, config.1."key-name-1": item at path "prefix/item-name-1" has no key "field-name-1"]`,
10061029
expected: map[string][]*coreapi.Secret{},
10071030
},
10081031
{

pkg/api/secretbootstrap/secretboostrap.go

Lines changed: 77 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
package secretbootstrap
22

33
import (
4-
"encoding/json"
54
"fmt"
65
"os"
76
"reflect"
7+
"sort"
88
"strings"
99

1010
"github.com/getlantern/deepcopy"
@@ -71,14 +71,89 @@ func LoadConfigFromFile(file string, config *Config) error {
7171
}
7272

7373
// SaveConfigToFile serializes a Config object to the given file
74+
// It orders a copy of the config for deterministic output before saving
7475
func SaveConfigToFile(file string, config *Config) error {
75-
bytes, err := yaml.Marshal(config)
76+
// Create a deep copy to avoid mutating the original config
77+
var configCopy Config
78+
if err := deepcopy.Copy(&configCopy, config); err != nil {
79+
return fmt.Errorf("failed to copy config: %w", err)
80+
}
81+
// Order the copy for deterministic output
82+
configCopy.orderConfig()
83+
bytes, err := yaml.Marshal(&configCopy)
7684
if err != nil {
7785
return err
7886
}
7987
return os.WriteFile(file, bytes, 0644)
8088
}
8189

90+
// orderConfig sorts the Config data structures for deterministic ordering
91+
func (c *Config) orderConfig() {
92+
// Sort ClusterGroups map keys
93+
sortedClusterGroups := make(map[string][]string)
94+
clusterGroupKeys := make([]string, 0, len(c.ClusterGroups))
95+
for k := range c.ClusterGroups {
96+
clusterGroupKeys = append(clusterGroupKeys, k)
97+
}
98+
sort.Strings(clusterGroupKeys)
99+
for _, k := range clusterGroupKeys {
100+
clusters := make([]string, len(c.ClusterGroups[k]))
101+
copy(clusters, c.ClusterGroups[k])
102+
sort.Strings(clusters)
103+
sortedClusterGroups[k] = clusters
104+
}
105+
c.ClusterGroups = sortedClusterGroups
106+
107+
// Sort Secrets slice
108+
sort.Slice(c.Secrets, func(i, j int) bool {
109+
// Sort by first To entry's Cluster, then Namespace, then Name
110+
// This groups secrets by cluster, making the output more organized
111+
if len(c.Secrets[i].To) == 0 && len(c.Secrets[j].To) == 0 {
112+
return false
113+
}
114+
if len(c.Secrets[i].To) == 0 {
115+
return true
116+
}
117+
if len(c.Secrets[j].To) == 0 {
118+
return false
119+
}
120+
toI := c.Secrets[i].To[0]
121+
toJ := c.Secrets[j].To[0]
122+
if toI.Cluster != toJ.Cluster {
123+
return toI.Cluster < toJ.Cluster
124+
}
125+
if toI.Namespace != toJ.Namespace {
126+
return toI.Namespace < toJ.Namespace
127+
}
128+
return toI.Name < toJ.Name
129+
})
130+
131+
// Sort UserSecretsTargetClusters
132+
sort.Strings(c.UserSecretsTargetClusters)
133+
134+
// Order each SecretConfig's DockerConfigJSONData slices for deterministic ordering
135+
// (YAML marshaler already sorts map keys, so we only need to sort slices)
136+
for i := range c.Secrets {
137+
for k, itemCtx := range c.Secrets[i].From {
138+
if len(itemCtx.DockerConfigJSONData) > 0 {
139+
sortedData := make([]DockerConfigJSONData, len(itemCtx.DockerConfigJSONData))
140+
copy(sortedData, itemCtx.DockerConfigJSONData)
141+
sort.Slice(sortedData, func(i, j int) bool {
142+
if sortedData[i].RegistryURL != sortedData[j].RegistryURL {
143+
return sortedData[i].RegistryURL < sortedData[j].RegistryURL
144+
}
145+
if sortedData[i].Item != sortedData[j].Item {
146+
return sortedData[i].Item < sortedData[j].Item
147+
}
148+
return sortedData[i].AuthField < sortedData[j].AuthField
149+
})
150+
itemCtx.DockerConfigJSONData = sortedData
151+
c.Secrets[i].From[k] = itemCtx
152+
}
153+
}
154+
}
155+
}
156+
82157
// Config is what we version in our repository
83158
type Config struct {
84159
VaultDPTPPrefix string `json:"vault_dptp_prefix,omitempty"`
@@ -87,40 +162,6 @@ type Config struct {
87162
UserSecretsTargetClusters []string `json:"user_secrets_target_clusters,omitempty"`
88163
}
89164

90-
type configWithoutUnmarshaler Config
91-
92-
func (c *Config) UnmarshalJSON(d []byte) error {
93-
var target configWithoutUnmarshaler
94-
if err := json.Unmarshal(d, &target); err != nil {
95-
return err
96-
}
97-
98-
*c = Config(target)
99-
return c.resolve()
100-
}
101-
102-
func (c *Config) MarshalJSON() ([]byte, error) {
103-
target := &configWithoutUnmarshaler{
104-
VaultDPTPPrefix: c.VaultDPTPPrefix,
105-
ClusterGroups: c.ClusterGroups,
106-
UserSecretsTargetClusters: c.UserSecretsTargetClusters,
107-
}
108-
pre := c.VaultDPTPPrefix + "/"
109-
var secrets []SecretConfig
110-
for _, s := range c.Secrets {
111-
var secret SecretConfig
112-
if err := deepcopy.Copy(&secret, s); err != nil {
113-
return nil, err
114-
}
115-
stripVaultPrefix(&secret, pre)
116-
secret.groupClusters()
117-
secrets = append(secrets, secret)
118-
}
119-
120-
target.Secrets = secrets
121-
return json.Marshal(target)
122-
}
123-
124165
func (s *SecretConfig) groupClusters() {
125166
var secrets []SecretContext
126167
for _, to := range s.To {

0 commit comments

Comments
 (0)