Skip to content

Commit

Permalink
feat(pkger): add initial outline of stack annotations and the ownerhs…
Browse files Browse the repository at this point in the history
…hip model

references: #18240
  • Loading branch information
jsteenb2 committed Jul 2, 2020
1 parent 34ebc85 commit ace5f19
Show file tree
Hide file tree
Showing 6 changed files with 369 additions and 26 deletions.
122 changes: 122 additions & 0 deletions annotation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package influxdb

import (
"encoding/json"
"sort"
)

// not exporting this so users are unable to create annotations.
// this can change but for now want to keep this under lock and key.
type annotation int

const (
annotationUnknown annotation = iota
annotationStackOwner
annotationStackReference
)

type Annotations struct {
// Hide the internals so it can't be manipulated freely.
m map[annotation]interface{}
}

func (a Annotations) MarshalJSON() ([]byte, error) {
return json.Marshal(a.m)
}

func (a *Annotations) UnmarshalJSON(b []byte) error {
return json.Unmarshal(b, &a.m)
}

func (a *Annotations) Stacks() struct {
Owner ID
References []ID
} {
var out struct {
Owner ID
References []ID
}
if a == nil {
return out
}

ownerIDRaw, ok := a.m[annotationStackOwner].(string)
if ok {
if id, err := IDFromString(ownerIDRaw); err == nil {
out.Owner = *id
}
}

refs, _ := a.m[annotationStackReference].([]interface{})
for _, ref := range refs {
refIDRaw, ok := ref.(string)
if ok {
if id, err := IDFromString(refIDRaw); err == nil {
out.References = append(out.References, *id)
}
}
}

return out
}

type AnnotationSetFn func(a *Annotations)

func (a *Annotations) Set(setFn AnnotationSetFn) {
if a.m == nil {
a.m = make(map[annotation]interface{})
}
setFn(a)
}

func (a *Annotations) Clone() Annotations {
m := make(map[annotation]interface{})
for k, v := range a.m {
m[k] = v
}
return Annotations{m: m}
}

func AnnotationSetStack(stackID ID) AnnotationSetFn {
return func(a *Annotations) {
stAnnot := a.Stacks()
if stackID == stAnnot.Owner {
return
}
if stAnnot.Owner == 0 {
AnnotationSetStackOwner(stackID)(a)
return
}
AnnotationSetStackReference(stackID)(a)
}
}

func AnnotationSetStackOwner(stackID ID) AnnotationSetFn {
return func(a *Annotations) {
a.m[annotationStackOwner] = stackID.String()
}
}

func AnnotationSetStackReference(stackID ID) AnnotationSetFn {
return func(a *Annotations) {
refs, _ := a.m[annotationStackReference].([]interface{})

m := map[string]bool{
stackID.String(): true,
}
for _, refRaw := range refs {
if id, ok := refRaw.(string); ok {
m[id] = true
}
}

var newRefs []interface{}
for ref := range m {
newRefs = append(newRefs, ref)
}
sort.Slice(newRefs, func(i, j int) bool {
return newRefs[i].(string) < newRefs[j].(string)
})
a.m[annotationStackReference] = newRefs
}
}
166 changes: 166 additions & 0 deletions cmd/influxd/launcher/pkger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2177,6 +2177,166 @@ func TestLauncher_Pkger(t *testing.T) {
})
})
})

t.Run("stack ownership", func(t *testing.T) {
t.Run("labels", func(t *testing.T) {
newLabelTemplate := func(name, desc, color string) *pkger.Template {
return newTemplate(newLabelObject("label-1", name, desc, color))
}

getLabenFn := func(t *testing.T, id pkger.SafeID) influxdb.Label {
t.Helper()
return resourceCheck.mustGetLabel(t, bySafeID(id))
}

isEmptyLabel := func(t *testing.T, label influxdb.Label) {
t.Helper()

assert.Equal(t, "foo", label.Name)
assert.Empty(t, label.Properties["description"])
assert.Empty(t, label.Properties["color"])
}

createInitial := func(t *testing.T) (pkger.ImpactSummary, func()) {
t.Helper()

impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID,
pkger.ApplyWithTemplate(newLabelTemplate("foo", "", "")),
)
require.NoError(t, err)
defer func() {
if t.Failed() {
deleteStackFn(t, impact.StackID)
}
}()

summary := impact.Summary

require.Len(t, summary.Labels, 1)
require.NotZero(t, summary.Labels[0].ID)

label := getLabenFn(t, summary.Labels[0].ID)
isEmptyLabel(t, label)
stackAnno := label.Annotations.Stacks()
assert.Equal(t, impact.StackID, stackAnno.Owner)

return impact, func() {
deleteStackFn(t, impact.StackID)
}
}

t.Run("when resource newly created stack assumes ownership", func(t *testing.T) {
_, cleanup := createInitial(t)
cleanup()
})

t.Run("when stack attempts to apply template with a label", func(t *testing.T) {
t.Run("by the stack owner", func(t *testing.T) {
initialImpact, cleanup := createInitial(t)
defer cleanup()

updateImpact, err := svc.Apply(ctx, l.Org.ID, l.User.ID,
pkger.ApplyWithTemplate(newLabelTemplate("foo", "new desc", "peru")),
pkger.ApplyWithStackID(initialImpact.StackID),
)
require.NoError(t, err)

require.Len(t, updateImpact.Summary.Labels, 1)
require.Equal(t, initialImpact.Summary.Labels[0].ID, updateImpact.Summary.Labels[0].ID)

label := getLabenFn(t, initialImpact.Summary.Labels[0].ID)
assert.Equal(t, influxdb.ID(initialImpact.Summary.Labels[0].ID), label.ID)
assert.Equal(t, "foo", label.Name)
assert.Equal(t, "peru", label.Properties["color"])
assert.Equal(t, "new desc", label.Properties["description"])

stackAnno := label.Annotations.Stacks()
assert.Equal(t, initialImpact.StackID, stackAnno.Owner)
assert.Empty(t, stackAnno.References)
})

t.Run("owned by another stack it should add a ref annotation but not update the label", func(t *testing.T) {
initialImpact, cleanup := createInitial(t)
defer cleanup()

updateImpact, err := svc.Apply(ctx, l.Org.ID, l.User.ID,
pkger.ApplyWithTemplate(newLabelTemplate("foo", "new desc", "peru")),
)
require.NoError(t, err)
defer deleteStackFn(t, updateImpact.StackID)

require.Len(t, updateImpact.Summary.Labels, 1)
require.Equal(t, initialImpact.Summary.Labels[0].ID, updateImpact.Summary.Labels[0].ID)

require.Len(t, updateImpact.Summary.Labels, 1)
label := getLabenFn(t, updateImpact.Summary.Labels[0].ID)
assert.Equal(t, initialImpact.Summary.Labels[0].ID, updateImpact.Summary.Labels[0].ID)
isEmptyLabel(t, label)

stackAnno := label.Annotations.Stacks()
assert.Equal(t, initialImpact.StackID, stackAnno.Owner)
assert.Equal(t, []influxdb.ID{updateImpact.StackID}, stackAnno.References)
})

t.Run("owned by another stack and applied multiple times should only update reference once", func(t *testing.T) {
initialImpact, cleanup := createInitial(t)
defer cleanup()

updateImpact, err := svc.Apply(ctx, l.Org.ID, l.User.ID,
pkger.ApplyWithTemplate(newLabelTemplate("foo", "new desc", "peru")),
)
require.NoError(t, err)
defer deleteStackFn(t, updateImpact.StackID)

expectedLabel := func(t *testing.T, updImpact pkger.ImpactSummary) {
t.Helper()

require.Len(t, updImpact.Summary.Labels, 1)
require.Equal(t, initialImpact.Summary.Labels[0].ID, updImpact.Summary.Labels[0].ID)
label := getLabenFn(t, updImpact.Summary.Labels[0].ID)
isEmptyLabel(t, label)
stackAnno := label.Annotations.Stacks()
assert.Equal(t, initialImpact.StackID, stackAnno.Owner)
assert.Equal(t, []influxdb.ID{updImpact.StackID}, stackAnno.References)
}
expectedLabel(t, updateImpact)

updateImpact, err = svc.Apply(ctx, l.Org.ID, l.User.ID,
pkger.ApplyWithTemplate(newLabelTemplate("foo", "new desc", "peru")),
pkger.ApplyWithStackID(updateImpact.StackID),
)
require.NoError(t, err)
expectedLabel(t, updateImpact)
})

t.Run("owned by another stack and deleting stack removes reference", func(t *testing.T) {
initialImpact, cleanup := createInitial(t)
defer cleanup()

updateImpact, err := svc.Apply(ctx, l.Org.ID, l.User.ID,
pkger.ApplyWithTemplate(newLabelTemplate("foo", "new desc", "peru")),
)
require.NoError(t, err)
defer deleteStackFn(t, updateImpact.StackID)

expectedLabel := func(t *testing.T, updImpact pkger.ImpactSummary, refs ...influxdb.ID) {
t.Helper()

require.Len(t, updImpact.Summary.Labels, 1)
require.Equal(t, initialImpact.Summary.Labels[0].ID, updImpact.Summary.Labels[0].ID)
label := getLabenFn(t, updImpact.Summary.Labels[0].ID)
isEmptyLabel(t, label)
stackAnno := label.Annotations.Stacks()
assert.Equal(t, initialImpact.StackID, stackAnno.Owner)
assert.Equal(t, refs, stackAnno.References)
}
expectedLabel(t, updateImpact, updateImpact.StackID)
deleteStackFn(t, updateImpact.StackID)
expectedLabel(t, updateImpact) // no refs
})
})
})
})
})

t.Run("errors incurred during application of package rolls back to state before package", func(t *testing.T) {
Expand Down Expand Up @@ -4006,6 +4166,12 @@ func byID(id influxdb.ID) getResourceOptFn {
}
}

func bySafeID(id pkger.SafeID) getResourceOptFn {
return func() getResourceOpt {
return getResourceOpt{id: influxdb.ID(id)}
}
}

func byName(name string) getResourceOptFn {
return func() getResourceOpt {
return getResourceOpt{name: name}
Expand Down
4 changes: 4 additions & 0 deletions kv/label.go
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,10 @@ func (s *Service) updateLabel(ctx context.Context, tx Tx, id influxdb.ID, upd in
label.Properties = make(map[string]string)
}

if upd.Annotations != nil {
label.Annotations = *upd.Annotations
}

for k, v := range upd.Properties {
if v == "" {
delete(label.Properties, k)
Expand Down
14 changes: 8 additions & 6 deletions label.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,11 @@ type LabelService interface {

// Label is a tag set on a resource, typically used for filtering on a UI.
type Label struct {
ID ID `json:"id,omitempty"`
OrgID ID `json:"orgID,omitempty"`
Name string `json:"name"`
Properties map[string]string `json:"properties,omitempty"`
ID ID `json:"id,omitempty"`
OrgID ID `json:"orgID,omitempty"`
Name string `json:"name"`
Properties map[string]string `json:"properties,omitempty"`
Annotations Annotations `json:"annotations"`
}

// Validate returns an error if the label is invalid.
Expand Down Expand Up @@ -123,8 +124,9 @@ func (l *LabelMapping) Validate() error {
// LabelUpdate represents a changeset for a label.
// Only the properties specified are updated.
type LabelUpdate struct {
Name string `json:"name,omitempty"`
Properties map[string]string `json:"properties,omitempty"`
Name string `json:"name,omitempty"`
Properties map[string]string `json:"properties,omitempty"`
Annotations *Annotations `json:"annotations,omitempty"`
}

// LabelFilter represents a set of filters that restrict the returned results.
Expand Down
Loading

0 comments on commit ace5f19

Please sign in to comment.