diff --git a/bootcfg/storage/storagepb/group.go b/bootcfg/storage/storagepb/group.go index eb195d3b..7d2cd70f 100644 --- a/bootcfg/storage/storagepb/group.go +++ b/bootcfg/storage/storagepb/group.go @@ -29,16 +29,18 @@ func ParseGroup(data []byte) (*Group, error) { return group, err } -// AssertValid validates a Group. Returns nil if there are no validation -// errors. -func (g *Group) AssertValid() error { - if g.Id == "" { - return ErrIdRequired +func (g *Group) Copy() *Group { + selectors := make(map[string]string) + for k, v := range g.Selector { + selectors[k] = v } - if g.Profile == "" { - return ErrProfileRequired + return &Group{ + Id: g.Id, + Name: g.Name, + Profile: g.Profile, + Selector: selectors, + Metadata: g.Metadata, } - return nil } // Matches returns true if the given labels satisfy all the selector @@ -69,6 +71,18 @@ func (g *Group) Normalize() error { return nil } +// AssertValid validates a Group. Returns nil if there are no validation +// errors. +func (g *Group) AssertValid() error { + if g.Id == "" { + return ErrIdRequired + } + if g.Profile == "" { + return ErrProfileRequired + } + return nil +} + // selectorString returns Group selectors as a string of sorted key value // pairs for comparisons. func (g *Group) selectorString() string { diff --git a/bootcfg/storage/storagepb/group_test.go b/bootcfg/storage/storagepb/group_test.go index ac1e37f9..2de7e85b 100644 --- a/bootcfg/storage/storagepb/group_test.go +++ b/bootcfg/storage/storagepb/group_test.go @@ -39,19 +39,40 @@ func TestGroupParse(t *testing.T) { } } -func TestGroupValidate(t *testing.T) { +func TestGroupCopy(t *testing.T) { + copy := testGroup.Copy() + // assert that: + // - Group fields are copied + // - mutation of the copy does not affect the original + assert.Equal(t, testGroup.Id, copy.Id) + assert.Equal(t, testGroup.Name, copy.Name) + assert.Equal(t, testGroup.Profile, copy.Profile) + assert.Equal(t, testGroup.Selector, copy.Selector) + assert.Equal(t, testGroup.Metadata, copy.Metadata) + + copy.Id = "a-copy" + copy.Selector["region"] = "us-west" + assert.NotEqual(t, testGroup.Id, copy.Id) + assert.NotEqual(t, testGroup.Selector, copy.Selector) +} + +func TestGroupMatches(t *testing.T) { cases := []struct { - group *Group - valid bool + labels map[string]string + selectors map[string]string + expected bool }{ - {&Group{Id: "node1", Profile: "k8s-master"}, true}, - {testGroupWithoutProfile, false}, - {&Group{Id: "node1"}, false}, - {&Group{}, false}, + {map[string]string{"a": "b"}, map[string]string{"a": "b"}, true}, + {map[string]string{"a": "b"}, map[string]string{"a": "c"}, false}, + {map[string]string{"uuid": "a", "mac": "b"}, map[string]string{"uuid": "a"}, true}, + {map[string]string{"uuid": "a"}, map[string]string{"uuid": "a", "mac": "b"}, false}, } + // assert that: + // - Group selectors must be satisfied for a match + // - labels may provide additional key/value pairs for _, c := range cases { - valid := c.group.AssertValid() == nil - assert.Equal(t, c.valid, valid) + group := &Group{Selector: c.selectors} + assert.Equal(t, c.expected, group.Matches(c.labels)) } } @@ -82,23 +103,19 @@ func TestNormalize(t *testing.T) { } } -func TestGroupMatches(t *testing.T) { +func TestGroupValidate(t *testing.T) { cases := []struct { - labels map[string]string - selectors map[string]string - expected bool + group *Group + valid bool }{ - {map[string]string{"a": "b"}, map[string]string{"a": "b"}, true}, - {map[string]string{"a": "b"}, map[string]string{"a": "c"}, false}, - {map[string]string{"uuid": "a", "mac": "b"}, map[string]string{"uuid": "a"}, true}, - {map[string]string{"uuid": "a"}, map[string]string{"uuid": "a", "mac": "b"}, false}, + {&Group{Id: "node1", Profile: "k8s-master"}, true}, + {testGroupWithoutProfile, false}, + {&Group{Id: "node1"}, false}, + {&Group{}, false}, } - // assert that: - // - Group selectors must be satisfied for a match - // - labels may provide additional key/value pairs for _, c := range cases { - group := &Group{Selector: c.selectors} - assert.Equal(t, c.expected, group.Matches(c.labels)) + valid := c.group.AssertValid() == nil + assert.Equal(t, c.valid, valid) } } diff --git a/bootcfg/storage/storagepb/profile.go b/bootcfg/storage/storagepb/profile.go index 3f1010a0..7b76a991 100644 --- a/bootcfg/storage/storagepb/profile.go +++ b/bootcfg/storage/storagepb/profile.go @@ -25,3 +25,27 @@ func (p *Profile) AssertValid() error { } return nil } + +func (p *Profile) Copy() *Profile { + return &Profile{ + Id: p.Id, + Name: p.Name, + IgnitionId: p.IgnitionId, + CloudId: p.CloudId, + Boot: p.Boot.Copy(), + } +} + +func (b *NetBoot) Copy() *NetBoot { + initrd := make([]string, len(b.Initrd)) + copy(initrd, b.Initrd) + cmdline := make(map[string]string) + for k, v := range b.Cmdline { + cmdline[k] = v + } + return &NetBoot{ + Kernel: b.Kernel, + Initrd: initrd, + Cmdline: cmdline, + } +} diff --git a/bootcfg/storage/storagepb/profile_test.go b/bootcfg/storage/storagepb/profile_test.go index 9415b2b1..d83d585e 100644 --- a/bootcfg/storage/storagepb/profile_test.go +++ b/bootcfg/storage/storagepb/profile_test.go @@ -41,3 +41,32 @@ func TestProfileValidate(t *testing.T) { assert.Equal(t, c.valid, valid) } } + +func TestProfileCopy(t *testing.T) { + profile := &Profile{ + Id: "id", + CloudId: "cloudy.tmpl", + IgnitionId: "ignition.tmpl", + Boot: &NetBoot{ + Kernel: "/image/kernel", + Initrd: []string{"/image/initrd_a"}, + Cmdline: map[string]string{"a": "b"}, + }, + } + copy := profile.Copy() + // assert that: + // - Profile fields are copied + // - mutation of the copy does not affect the original + assert.Equal(t, profile.Id, copy.Id) + assert.Equal(t, profile.Name, copy.Name) + assert.Equal(t, profile.IgnitionId, copy.IgnitionId) + assert.Equal(t, profile.CloudId, copy.CloudId) + assert.Equal(t, profile.Boot, copy.Boot) + + copy.Id = "a-copy" + copy.Boot.Initrd = []string{"/image/initrd_b"} + copy.Boot.Cmdline["c"] = "d" + assert.NotEqual(t, profile.Id, copy.Id) + assert.NotEqual(t, profile.Boot.Initrd, copy.Boot.Initrd) + assert.NotEqual(t, profile.Boot.Cmdline, copy.Boot.Cmdline) +}