package launcher import ( "context" "errors" "fmt" nethttp "net/http" "net/http/httptest" "os" "runtime" "sort" "strings" "testing" "time" "github.com/influxdata/influxdb/v2" "github.com/influxdata/influxdb/v2/kit/platform" errors2 "github.com/influxdata/influxdb/v2/kit/platform/errors" "github.com/influxdata/influxdb/v2/mock" "github.com/influxdata/influxdb/v2/notification" "github.com/influxdata/influxdb/v2/notification/check" "github.com/influxdata/influxdb/v2/notification/endpoint" "github.com/influxdata/influxdb/v2/notification/rule" "github.com/influxdata/influxdb/v2/pkger" "github.com/influxdata/influxdb/v2/task/taskmodel" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap" ) var ctx = context.Background() func TestLauncher_Pkger(t *testing.T) { l := RunAndSetupNewLauncherOrFail(ctx, t, func(o *InfluxdOpts) { o.LogLevel = zap.ErrorLevel }) defer l.ShutdownOrFail(t, ctx) require.NoError(t, l.BucketService(t).DeleteBucket(ctx, l.Bucket.ID)) svc := l.PkgerService(t) resourceCheck := newResourceChecker(l) deleteStackFn := func(t *testing.T, stackID platform.ID) { t.Helper() err := svc.DeleteStack(ctx, struct{ OrgID, UserID, StackID platform.ID }{ OrgID: l.Org.ID, UserID: l.User.ID, StackID: stackID, }) require.NoError(t, err, "failed to delete stack and its associated resources") } newStackFn := func(t *testing.T, stack pkger.StackCreate) (pkger.Stack, func()) { t.Helper() if stack.OrgID == 0 { stack.OrgID = l.Org.ID } newStack, err := svc.InitStack(ctx, l.User.ID, stack) require.NoError(t, err) assert.Equal(t, l.Org.ID, newStack.OrgID) ev := newStack.LatestEvent() assert.Equal(t, stack.Name, ev.Name) assert.Equal(t, stack.Description, ev.Description) assert.NotNil(t, ev.Resources, "failed to match stack resources") expectedURLs := stack.TemplateURLs if expectedURLs == nil { expectedURLs = []string{} } assert.Equal(t, expectedURLs, ev.TemplateURLs, "failed to match stack URLs") assert.NotZero(t, newStack.CreatedAt) assert.NotZero(t, ev.UpdatedAt) return newStack, func() { // deletes are idempotent, so any error encountered here is not a not found error // but rather an error to concern ourselves with. deleteStackFn(t, newStack.ID) } } newTemplate := func(objects ...pkger.Object) *pkger.Template { return &pkger.Template{Objects: objects} } newBucketObject := func(pkgName, name, desc string) pkger.Object { obj := pkger.BucketToObject("", influxdb.Bucket{ Name: name, Description: desc, }) obj.SetMetadataName(pkgName) return obj } newCheckBase := func(t *testing.T, name string, every time.Duration) check.Base { t.Helper() d, err := notification.FromTimeDuration(every) require.NoError(t, err) return check.Base{ Name: name, Every: &d, Query: influxdb.DashboardQuery{ Text: `from(bucket: "rucket_1") |> range(start: -1d)`, }, StatusMessageTemplate: "Check: ${ r._check_name } is: ${ r._level }", } } newCheckDeadmanObject := func(t *testing.T, pkgName, name string, every time.Duration) pkger.Object { t.Helper() obj := pkger.CheckToObject("", &check.Deadman{ Base: newCheckBase(t, name, every), Level: notification.Critical, }) obj.SetMetadataName(pkgName) return obj } newCheckThresholdObject := func(t *testing.T, pkgName, name string, every time.Duration) pkger.Object { t.Helper() obj := pkger.CheckToObject("", &check.Threshold{ Base: newCheckBase(t, name, every), Thresholds: []check.ThresholdConfig{ check.Lesser{ ThresholdConfigBase: check.ThresholdConfigBase{ Level: notification.Critical, }, Value: 0.5, }, }, }) obj.SetMetadataName(pkgName) return obj } newDashObject := func(pkgName, name, desc string) pkger.Object { obj := pkger.DashboardToObject("", influxdb.Dashboard{ Name: name, Description: desc, }) obj.SetMetadataName(pkgName) return obj } newEndpointBase := func(name, desc string) endpoint.Base { return endpoint.Base{ Name: name, Description: desc, Status: influxdb.Inactive, } } newEndpointHTTP := func(pkgName, name, description string) pkger.Object { obj := pkger.NotificationEndpointToObject("", &endpoint.HTTP{ Base: newEndpointBase(name, description), AuthMethod: "none", URL: "http://example.com", Method: "GET", }) obj.SetMetadataName(pkgName) return obj } newEndpointPagerDuty := func(pkgName, name, description string) pkger.Object { obj := pkger.NotificationEndpointToObject("", &endpoint.PagerDuty{ Base: newEndpointBase(name, description), ClientURL: "http://example.com", RoutingKey: influxdb.SecretField{ Key: "routing-key", Value: strPtr("threeve"), }, }) obj.SetMetadataName(pkgName) return obj } newEndpointSlack := func(pkgName, name, description string) pkger.Object { obj := pkger.NotificationEndpointToObject("", &endpoint.Slack{ Base: newEndpointBase(name, description), URL: "http://influxslack.com", }) obj.SetMetadataName(pkgName) return obj } newLabelObject := func(pkgName, name, desc, color string) pkger.Object { obj := pkger.LabelToObject("", influxdb.Label{ Name: name, Properties: map[string]string{ "color": color, "description": desc, }, }) obj.SetMetadataName(pkgName) return obj } newRuleObject := func(t *testing.T, pkgName, name, endpointPkgName, desc string) pkger.Object { t.Helper() every, err := notification.FromTimeDuration(time.Hour) require.NoError(t, err) obj := pkger.NotificationRuleToObject("", endpointPkgName, &rule.HTTP{ Base: rule.Base{ Name: name, Description: desc, Every: &every, StatusRules: []notification.StatusRule{{CurrentLevel: notification.Critical}}, }, }) obj.SetMetadataName(pkgName) return obj } newTaskObject := func(pkgName, name, description string) pkger.Object { obj := pkger.TaskToObject("", taskmodel.Task{ Name: name, Description: description, Flux: "buckets()", Every: "1h", }) obj.SetMetadataName(pkgName) return obj } newTelegrafObject := func(pkgName, name, description string) pkger.Object { obj := pkger.TelegrafToObject("", influxdb.TelegrafConfig{ Name: name, Description: description, Config: telegrafCfg, }) obj.SetMetadataName(pkgName) return obj } newVariableObject := func(pkgName, name, description string, selected ...string) pkger.Object { obj := pkger.VariableToObject("", influxdb.Variable{ Name: name, Description: description, Selected: selected, Arguments: &influxdb.VariableArguments{ Type: "constant", Values: influxdb.VariableConstantValues{"a", "b"}, }, }) obj.SetMetadataName(pkgName) return obj } validateAllResourcesRemoved := func(t *testing.T, summary pkger.Summary) { t.Helper() for _, b := range summary.Buckets { _, err := resourceCheck.getBucket(t, bySafeID(b.ID)) assertErrorCode(t, errors2.ENotFound, err) } for _, c := range summary.Checks { _, err := resourceCheck.getCheck(t, byID(c.Check.GetID())) assert.Error(t, err) } for _, d := range summary.Dashboards { _, err := resourceCheck.getDashboard(t, bySafeID(d.ID)) assertErrorCode(t, errors2.ENotFound, err) } for _, l := range summary.Labels { _, err := resourceCheck.getLabel(t, bySafeID(l.ID)) assertErrorCode(t, errors2.ENotFound, err) } for _, e := range summary.NotificationEndpoints { _, err := resourceCheck.getEndpoint(t, byID(e.NotificationEndpoint.GetID())) assert.Error(t, err) } for _, r := range summary.NotificationRules { _, err := resourceCheck.getRule(t, bySafeID(r.ID)) assert.Error(t, err) } for _, ta := range summary.Tasks { _, err := resourceCheck.getTask(t, bySafeID(ta.ID)) assert.Error(t, err) } for _, te := range summary.TelegrafConfigs { _, err := resourceCheck.getTelegrafConfig(t, byID(te.TelegrafConfig.ID)) assert.Error(t, err) } for _, v := range summary.Variables { _, err := resourceCheck.getVariable(t, bySafeID(v.ID)) assertErrorCode(t, errors2.ENotFound, err) } } t.Run("managing pkg lifecycle with stacks", func(t *testing.T) { t.Run("list stacks", func(t *testing.T) { stacks, err := svc.ListStacks(ctx, l.Org.ID, pkger.ListFilter{}) require.NoError(t, err) require.Empty(t, stacks) newStack1, cleanup1 := newStackFn(t, pkger.StackCreate{ Name: "first stack", }) defer cleanup1() newStack2, cleanup2 := newStackFn(t, pkger.StackCreate{ Name: "second stack", }) defer cleanup2() containsStack := func(t *testing.T, haystack []pkger.Stack, needle pkger.Stack) { t.Helper() for _, hay := range haystack { if hay.ID == needle.ID { return } } require.FailNowf(t, "did not find expected stack", "got: %+v", needle) } t.Run("returns all stacks when no filter args provided", func(t *testing.T) { stacks, err := svc.ListStacks(ctx, l.Org.ID, pkger.ListFilter{}) require.NoError(t, err) containsStack(t, stacks, newStack1) containsStack(t, stacks, newStack2) }) t.Run("filters stacks by ID filter", func(t *testing.T) { stacks, err := svc.ListStacks(ctx, l.Org.ID, pkger.ListFilter{ StackIDs: []platform.ID{newStack1.ID}, }) require.NoError(t, err) require.Len(t, stacks, 1) containsStack(t, stacks, newStack1) }) t.Run("filter stacks by names", func(t *testing.T) { stacks, err := svc.ListStacks(ctx, l.Org.ID, pkger.ListFilter{ Names: []string{newStack2.LatestEvent().Name}, }) require.NoError(t, err) require.Len(t, stacks, 1) containsStack(t, stacks, newStack2) }) }) t.Run("creating a stack", func(t *testing.T) { _, cleanup := newStackFn(t, pkger.StackCreate{ OrgID: l.Org.ID, Name: "first stack", Description: "desc", TemplateURLs: []string{"http://example.com"}, }) cleanup() }) t.Run("uninstall a stack", func(t *testing.T) { t.Run("should remove all resources associated with it", func(t *testing.T) { newStack, cleanup := newStackFn(t, pkger.StackCreate{}) defer cleanup() newEndpointPkgName := "non-existent-endpoint" allResourcesPkg := newTemplate( newBucketObject("non-existent-bucket", "", ""), newCheckDeadmanObject(t, "non-existent-check", "", time.Minute), newDashObject("non-existent-dash", "", ""), newEndpointHTTP(newEndpointPkgName, "", ""), newLabelObject("non-existent-label", "", "", ""), newRuleObject(t, "non-existent-rule", "", newEndpointPkgName, ""), newTaskObject("non-existent-task", "", ""), newTelegrafObject("non-existent-tele", "", ""), newVariableObject("non-existent-var", "", ""), ) impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(allResourcesPkg), pkger.ApplyWithStackID(newStack.ID), ) require.NoError(t, err) sum := impact.Summary require.Len(t, sum.Buckets, 1) assert.NotZero(t, sum.Buckets[0].ID) require.Len(t, sum.Checks, 1) assert.NotZero(t, sum.Checks[0].Check.GetID()) require.Len(t, sum.Dashboards, 1) assert.NotZero(t, sum.Dashboards[0].ID) require.Len(t, sum.Labels, 1) assert.NotZero(t, sum.Labels[0].ID) require.Len(t, sum.NotificationEndpoints, 1) assert.NotZero(t, sum.NotificationEndpoints[0].NotificationEndpoint.GetID()) require.Len(t, sum.NotificationRules, 1) assert.NotZero(t, sum.NotificationRules[0].ID) require.Len(t, sum.Tasks, 1) assert.NotZero(t, sum.Tasks[0].ID) require.Len(t, sum.TelegrafConfigs, 1) assert.NotZero(t, sum.TelegrafConfigs[0].TelegrafConfig.ID) require.Len(t, sum.Variables, 1) assert.NotZero(t, sum.Variables[0].ID) _, err = svc.UninstallStack(ctx, struct{ OrgID, UserID, StackID platform.ID }{ OrgID: l.Org.ID, UserID: l.User.ID, StackID: newStack.ID, }) require.NoError(t, err) matchingStack, err := svc.ReadStack(ctx, newStack.ID) require.NoError(t, err) ev := matchingStack.LatestEvent() assert.Equal(t, pkger.StackEventUninstalled, ev.EventType) assert.Empty(t, ev.Resources) validateAllResourcesRemoved(t, sum) }) }) t.Run("delete a stack", func(t *testing.T) { t.Run("should delete the stack and all resources associated with it", func(t *testing.T) { newStack, cleanup := newStackFn(t, pkger.StackCreate{}) defer cleanup() newEndpointPkgName := "non-existent-endpoint" allResourcesPkg := newTemplate( newBucketObject("non-existent-bucket", "", ""), newCheckDeadmanObject(t, "non-existent-check", "", time.Minute), newDashObject("non-existent-dash", "", ""), newEndpointHTTP(newEndpointPkgName, "", ""), newLabelObject("non-existent-label", "", "", ""), newRuleObject(t, "non-existent-rule", "", newEndpointPkgName, ""), newTaskObject("non-existent-task", "", ""), newTelegrafObject("non-existent-tele", "", ""), newVariableObject("non-existent-var", "", ""), ) impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(allResourcesPkg), pkger.ApplyWithStackID(newStack.ID), ) require.NoError(t, err) sum := impact.Summary require.Len(t, sum.Buckets, 1) assert.NotZero(t, sum.Buckets[0].ID) require.Len(t, sum.Checks, 1) assert.NotZero(t, sum.Checks[0].Check.GetID()) require.Len(t, sum.Dashboards, 1) assert.NotZero(t, sum.Dashboards[0].ID) require.Len(t, sum.Labels, 1) assert.NotZero(t, sum.Labels[0].ID) require.Len(t, sum.NotificationEndpoints, 1) assert.NotZero(t, sum.NotificationEndpoints[0].NotificationEndpoint.GetID()) require.Len(t, sum.NotificationRules, 1) assert.NotZero(t, sum.NotificationRules[0].ID) require.Len(t, sum.Tasks, 1) assert.NotZero(t, sum.Tasks[0].ID) require.Len(t, sum.TelegrafConfigs, 1) assert.NotZero(t, sum.TelegrafConfigs[0].TelegrafConfig.ID) require.Len(t, sum.Variables, 1) assert.NotZero(t, sum.Variables[0].ID) err = svc.DeleteStack(ctx, struct{ OrgID, UserID, StackID platform.ID }{ OrgID: l.Org.ID, UserID: l.User.ID, StackID: newStack.ID, }) require.NoError(t, err) validateAllResourcesRemoved(t, sum) }) t.Run("that has already been deleted should be successful", func(t *testing.T) { newStack, cleanup := newStackFn(t, pkger.StackCreate{}) defer cleanup() err := svc.DeleteStack(ctx, struct{ OrgID, UserID, StackID platform.ID }{ OrgID: l.Org.ID, UserID: l.User.ID, StackID: newStack.ID, }) require.NoError(t, err) // delete same stack err = svc.DeleteStack(ctx, struct{ OrgID, UserID, StackID platform.ID }{ OrgID: l.Org.ID, UserID: l.User.ID, StackID: newStack.ID, }) require.NoError(t, err) }) t.Run("that doesn't exist should be successful", func(t *testing.T) { // delete stack that doesn't exist err := svc.DeleteStack(ctx, struct{ OrgID, UserID, StackID platform.ID }{ OrgID: l.Org.ID, UserID: l.User.ID, StackID: 9000, }) require.NoError(t, err) }) }) t.Run("read a stack", func(t *testing.T) { newStack1, cleanup1 := newStackFn(t, pkger.StackCreate{ Name: "first stack", }) defer cleanup1() newStack2, cleanup2 := newStackFn(t, pkger.StackCreate{ Name: "second stack", }) defer cleanup2() actual, err := svc.ReadStack(ctx, newStack1.ID) require.NoError(t, err) assert.Equal(t, newStack1, actual) actual, err = svc.ReadStack(ctx, newStack2.ID) require.NoError(t, err) assert.Equal(t, newStack2, actual) _, err = svc.ReadStack(ctx, platform.ID(9000)) require.Equal(t, errors2.ENotFound, errors2.ErrorCode(err)) }) t.Run("updating a stack", func(t *testing.T) { t.Run("bootstrapped updates successfully", func(t *testing.T) { stack, cleanup := newStackFn(t, pkger.StackCreate{ OrgID: l.Org.ID, Name: "first name", Description: "first desc", TemplateURLs: []string{}, }) defer cleanup() // Wait for time.Now() to move ahead of the stack's latest update time. // We do this so that on Windows (where time.Now() is updated relatively slowly) // it doesn't appear that the stack was updated at the same time it was created. for now := time.Now(); now.Equal(stack.LatestEvent().UpdatedAt); now = time.Now() { time.Sleep(time.Millisecond) } assertStack := func(t *testing.T, st pkger.Stack) { t.Helper() assert.Equal(t, stack.ID, st.ID) ev := st.LatestEvent() assert.Equal(t, "2nd name", ev.Name) assert.Equal(t, "2nd desc", ev.Description) assert.Equal(t, []string{"http://example.com"}, ev.TemplateURLs) resources := []pkger.StackResource{ { APIVersion: pkger.APIVersion, ID: 1, Kind: pkger.KindBucket, MetaName: "bucket-meta", }, } assert.Equal(t, resources, ev.Resources) assert.True(t, ev.UpdatedAt.After(stack.LatestEvent().UpdatedAt)) } updStack, err := svc.UpdateStack(ctx, pkger.StackUpdate{ ID: stack.ID, Name: strPtr("2nd name"), Description: strPtr("2nd desc"), TemplateURLs: []string{"http://example.com"}, AdditionalResources: []pkger.StackAdditionalResource{ { APIVersion: pkger.APIVersion, ID: 1, Kind: pkger.KindBucket, MetaName: "bucket-meta", }, }, }) require.NoError(t, err) assertStack(t, updStack) readStack, err := svc.ReadStack(ctx, stack.ID) require.NoError(t, err) assertStack(t, readStack) }) t.Run("associated with installed template returns valid resources", func(t *testing.T) { tmpl := newTemplate(newLabelObject("label-1", "", "", "")) impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(tmpl)) require.NoError(t, err) defer deleteStackFn(t, impact.StackID) stack, err := svc.ReadStack(ctx, impact.StackID) require.NoError(t, err) assertStack := func(t *testing.T, st pkger.Stack) { t.Helper() assert.Equal(t, stack.ID, st.ID) ev := st.LatestEvent() assert.Equal(t, "2nd name", ev.Name) resources := []pkger.StackResource{{ APIVersion: pkger.APIVersion, ID: 1, Kind: pkger.KindBucket, MetaName: "bucket-meta", }} resources = append(resources, stack.LatestEvent().Resources...) assert.Equal(t, resources, ev.Resources) } updStack, err := svc.UpdateStack(ctx, pkger.StackUpdate{ ID: stack.ID, Name: strPtr("2nd name"), AdditionalResources: []pkger.StackAdditionalResource{ { APIVersion: pkger.APIVersion, ID: 1, Kind: pkger.KindBucket, MetaName: "bucket-meta", }, }, }) require.NoError(t, err) assertStack(t, updStack) readStack, err := svc.ReadStack(ctx, stack.ID) require.NoError(t, err) assertStack(t, readStack) }) }) t.Run("apply with only a stackID succeeds when stack has URLs", func(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("https://github.com/influxdata/influxdb/issues/22474") } svr := httptest.NewServer(nethttp.HandlerFunc(func(w nethttp.ResponseWriter, r *nethttp.Request) { pkg := newTemplate(newBucketObject("bucket-0", "", "")) b, err := pkg.Encode(pkger.EncodingJSON) if err != nil { w.WriteHeader(nethttp.StatusInternalServerError) return } w.Write(b) })) defer svr.Close() f, err := os.CreateTemp("", "pkg.yml") require.NoError(t, err) defer f.Close() pkg := newTemplate(newBucketObject("bucket-1", "", "")) b, err := pkg.Encode(pkger.EncodingYAML) require.NoError(t, err) f.Write(b) require.NoError(t, f.Close()) expectedURLs := []string{ // URL for http call svr.URL + "/pkg.json", // URL for file "file://" + f.Name(), } newStack, cleanup := newStackFn(t, pkger.StackCreate{ TemplateURLs: expectedURLs, }) defer cleanup() sumEquals := func(t *testing.T, impact pkger.ImpactSummary) { t.Helper() assert.Equal(t, expectedURLs, impact.Sources) sum := impact.Summary require.Len(t, sum.Buckets, 2) assert.Equal(t, "bucket-0", sum.Buckets[0].MetaName) assert.Equal(t, "bucket-0", sum.Buckets[0].Name) assert.Equal(t, "bucket-1", sum.Buckets[1].MetaName) assert.Equal(t, "bucket-1", sum.Buckets[1].Name) } impact, err := svc.DryRun(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithStackID(newStack.ID)) require.NoError(t, err) sumEquals(t, impact) impact, err = svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithStackID(newStack.ID)) require.NoError(t, err) sumEquals(t, impact) stacks, err := svc.ListStacks(ctx, l.Org.ID, pkger.ListFilter{ StackIDs: []platform.ID{newStack.ID}, }) require.NoError(t, err) require.Len(t, stacks, 1) assert.Equal(t, expectedURLs, stacks[0].LatestEvent().Sources) }) t.Run("apply a pkg with a stack and associations", func(t *testing.T) { testLabelMappingFn := func(t *testing.T, stackID platform.ID, pkg *pkger.Template, assertAssociatedLabelsFn func(pkger.Summary, []*influxdb.Label, influxdb.ResourceType)) pkger.Summary { t.Helper() impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(pkg), pkger.ApplyWithStackID(stackID), ) require.NoError(t, err) assert.Equal(t, []string{"byte stream"}, impact.Sources) sum := impact.Summary require.Len(t, sum.Buckets, 1) assert.NotZero(t, sum.Buckets[0].ID) assert.Equal(t, "bucket", sum.Buckets[0].Name) require.Len(t, sum.Checks, 1) assert.NotZero(t, sum.Checks[0].Check.GetID()) assert.Equal(t, "check-0", sum.Checks[0].Check.GetName()) require.Len(t, sum.Dashboards, 1) assert.NotZero(t, sum.Dashboards[0].ID) assert.Equal(t, "dash-0", sum.Dashboards[0].Name) require.Len(t, sum.NotificationEndpoints, 1) assert.NotZero(t, sum.NotificationEndpoints[0].NotificationEndpoint.GetID()) assert.Equal(t, "endpoint-0", sum.NotificationEndpoints[0].NotificationEndpoint.GetName()) require.Len(t, sum.NotificationRules, 1) assert.NotZero(t, sum.NotificationRules[0].ID) assert.Equal(t, "rule-0", sum.NotificationRules[0].Name) require.Len(t, sum.Labels, 1) assert.NotZero(t, sum.Labels[0].ID) assert.Equal(t, "label 1", sum.Labels[0].Name) require.Len(t, sum.Tasks, 1) assert.NotZero(t, sum.Tasks[0].ID) assert.Equal(t, "task-0", sum.Tasks[0].Name) require.Len(t, sum.TelegrafConfigs, 1) assert.NotZero(t, sum.TelegrafConfigs[0].TelegrafConfig.ID) assert.Equal(t, "tele-0", sum.TelegrafConfigs[0].TelegrafConfig.Name) resources := []struct { resID platform.ID resourceType influxdb.ResourceType }{ {resID: platform.ID(sum.Buckets[0].ID), resourceType: influxdb.BucketsResourceType}, {resID: sum.Checks[0].Check.GetID(), resourceType: influxdb.ChecksResourceType}, {resID: platform.ID(sum.Dashboards[0].ID), resourceType: influxdb.DashboardsResourceType}, {resID: sum.NotificationEndpoints[0].NotificationEndpoint.GetID(), resourceType: influxdb.NotificationEndpointResourceType}, {resID: platform.ID(sum.NotificationRules[0].ID), resourceType: influxdb.NotificationRuleResourceType}, {resID: platform.ID(sum.Tasks[0].ID), resourceType: influxdb.TasksResourceType}, {resID: sum.TelegrafConfigs[0].TelegrafConfig.ID, resourceType: influxdb.TelegrafsResourceType}, {resID: platform.ID(sum.Variables[0].ID), resourceType: influxdb.VariablesResourceType}, } for _, res := range resources { mappedLabels, err := l.LabelService(t).FindResourceLabels(ctx, influxdb.LabelMappingFilter{ ResourceID: res.resID, ResourceType: res.resourceType, }) require.NoError(t, err, "resource_type="+res.resourceType) assertAssociatedLabelsFn(sum, mappedLabels, res.resourceType) } return sum } newObjectsFn := func() []pkger.Object { return []pkger.Object{ newBucketObject("bucket", "", ""), newCheckDeadmanObject(t, "check-0", "", time.Hour), newDashObject("dash-0", "", ""), newEndpointHTTP("endpoint-0", "", ""), newRuleObject(t, "rule-0", "", "endpoint-0", ""), newTaskObject("task-0", "", ""), newTelegrafObject("tele-0", "", ""), newVariableObject("var-0", "", ""), } } labelObj := newLabelObject("label-1", "label 1", "", "") testAssociationFn := func(t *testing.T) (pkger.Summary, pkger.Stack, func()) { t.Helper() stack, cleanup := newStackFn(t, pkger.StackCreate{}) defer func() { if t.Failed() { // if test fails in setup, then we attempt to clean it up // so it doesn't pollute other tests cleanup() } }() pkgObjects := newObjectsFn() for _, obj := range pkgObjects { obj.AddAssociations(pkger.ObjectAssociation{ Kind: pkger.KindLabel, MetaName: labelObj.Name(), }) } pkgObjects = append(pkgObjects, labelObj) pkg := newTemplate(pkgObjects...) sum := testLabelMappingFn(t, stack.ID, pkg, func(sum pkger.Summary, mappedLabels []*influxdb.Label, resType influxdb.ResourceType) { require.Len(t, mappedLabels, 1, "resource_type="+resType) assert.Equal(t, sum.Labels[0].ID, pkger.SafeID(mappedLabels[0].ID)) }) return sum, stack, cleanup } t.Run("should associate resources with labels", func(t *testing.T) { _, _, cleanup := testAssociationFn(t) cleanup() }) t.Run("should rollback to previous state when errors in creation", func(t *testing.T) { sum, stack, cleanup := testAssociationFn(t) defer cleanup() logger := l.log.With(zap.String("service", "pkger")) var svc pkger.SVC = pkger.NewService( pkger.WithLogger(logger), pkger.WithBucketSVC(l.BucketService(t)), pkger.WithDashboardSVC(l.DashboardService(t)), pkger.WithCheckSVC(l.CheckService()), pkger.WithLabelSVC(&fakeLabelSVC{ // can't use the LabelService HTTP client b/c it doesn't cover the // all the resources pkger supports... :sadpanda: LabelService: l.LabelService(t), createKillCount: -1, deleteKillCount: 3, }), pkger.WithNotificationEndpointSVC(l.NotificationEndpointService(t)), pkger.WithNotificationRuleSVC(l.NotificationRuleService(t)), pkger.WithStore(pkger.NewStoreKV(l.Launcher.kvStore)), pkger.WithTaskSVC(l.TaskServiceKV(t)), pkger.WithTelegrafSVC(l.TelegrafService(t)), pkger.WithVariableSVC(l.VariableService(t)), ) svc = pkger.MWLogging(logger)(svc) pkg := newTemplate(append(newObjectsFn(), labelObj)...) _, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(pkg), pkger.ApplyWithStackID(stack.ID), ) require.Error(t, err) resources := []struct { resID platform.ID resourceType influxdb.ResourceType }{ {resID: platform.ID(sum.Buckets[0].ID), resourceType: influxdb.BucketsResourceType}, {resID: sum.Checks[0].Check.GetID(), resourceType: influxdb.ChecksResourceType}, {resID: platform.ID(sum.Dashboards[0].ID), resourceType: influxdb.DashboardsResourceType}, {resID: sum.NotificationEndpoints[0].NotificationEndpoint.GetID(), resourceType: influxdb.NotificationEndpointResourceType}, {resID: platform.ID(sum.NotificationRules[0].ID), resourceType: influxdb.NotificationRuleResourceType}, {resID: platform.ID(sum.Tasks[0].ID), resourceType: influxdb.TasksResourceType}, {resID: sum.TelegrafConfigs[0].TelegrafConfig.ID, resourceType: influxdb.TelegrafsResourceType}, {resID: platform.ID(sum.Variables[0].ID), resourceType: influxdb.VariablesResourceType}, } for _, res := range resources { mappedLabels, err := l.LabelService(t).FindResourceLabels(ctx, influxdb.LabelMappingFilter{ ResourceID: res.resID, ResourceType: res.resourceType, }) require.NoError(t, err) assert.Len(t, mappedLabels, 1, res.resourceType) if len(mappedLabels) == 1 { assert.Equal(t, sum.Labels[0].ID, pkger.SafeID(mappedLabels[0].ID)) } } }) t.Run("should unassociate resources from label when removing association in pkg", func(t *testing.T) { _, stack, cleanup := testAssociationFn(t) defer cleanup() objects := newObjectsFn() pkg := newTemplate(append(objects, labelObj)...) testLabelMappingFn(t, stack.ID, pkg, func(sum pkger.Summary, mappedLabels []*influxdb.Label, resType influxdb.ResourceType) { assert.Empty(t, mappedLabels, "res_type="+resType) }) }) }) t.Run("apply a pkg with a stack and all resources", func(t *testing.T) { testStackApplyFn := func(t *testing.T) (pkger.Summary, pkger.Stack, func()) { t.Helper() stack, cleanup := newStackFn(t, pkger.StackCreate{}) defer func() { if t.Failed() { cleanup() } }() var ( initialBucketPkgName = "rucketeer-1" initialCheckPkgName = "checkers" initialDashPkgName = "dash-of-salt" initialEndpointPkgName = "endzo" initialLabelPkgName = "labelino" initialRulePkgName = "oh-doyle-rules" initialTaskPkgName = "tap" initialTelegrafPkgName = "teletype" initialVariablePkgName = "laces-out-dan" ) initialPkg := newTemplate( newBucketObject(initialBucketPkgName, "display name", "init desc"), newCheckDeadmanObject(t, initialCheckPkgName, "check_0", time.Minute), newDashObject(initialDashPkgName, "dash_0", "init desc"), newEndpointHTTP(initialEndpointPkgName, "endpoint_0", "init desc"), newLabelObject(initialLabelPkgName, "label 1", "init desc", "#222eee"), newRuleObject(t, initialRulePkgName, "rule_0", initialEndpointPkgName, "init desc"), newTaskObject(initialTaskPkgName, "task_0", "init desc"), newTelegrafObject(initialTelegrafPkgName, "tele_0", "init desc"), newVariableObject(initialVariablePkgName, "var char", "init desc"), ) impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(initialPkg), pkger.ApplyWithStackID(stack.ID), ) require.NoError(t, err) summary := impact.Summary require.Len(t, summary.Buckets, 1) assert.NotZero(t, summary.Buckets[0].ID) assert.Equal(t, "display name", summary.Buckets[0].Name) assert.Equal(t, "init desc", summary.Buckets[0].Description) require.Len(t, summary.Checks, 1) assert.NotZero(t, summary.Checks[0].Check.GetID()) assert.Equal(t, "check_0", summary.Checks[0].Check.GetName()) require.Len(t, summary.Dashboards, 1) assert.NotZero(t, summary.Dashboards[0].ID) assert.Equal(t, "dash_0", summary.Dashboards[0].Name) require.Len(t, summary.NotificationEndpoints, 1) assert.NotZero(t, summary.NotificationEndpoints[0].NotificationEndpoint.GetID()) assert.Equal(t, "endpoint_0", summary.NotificationEndpoints[0].NotificationEndpoint.GetName()) require.Len(t, summary.Labels, 1) assert.NotZero(t, summary.Labels[0].ID) assert.Equal(t, "label 1", summary.Labels[0].Name) assert.Equal(t, "init desc", summary.Labels[0].Properties.Description) assert.Equal(t, "#222eee", summary.Labels[0].Properties.Color) require.Len(t, summary.NotificationRules, 1) assert.NotZero(t, summary.NotificationRules[0].ID) assert.Equal(t, "rule_0", summary.NotificationRules[0].Name) assert.Equal(t, initialEndpointPkgName, summary.NotificationRules[0].EndpointMetaName) assert.Equal(t, "init desc", summary.NotificationRules[0].Description) require.Len(t, summary.Tasks, 1) assert.NotZero(t, summary.Tasks[0].ID) assert.Equal(t, "task_0", summary.Tasks[0].Name) assert.Equal(t, "init desc", summary.Tasks[0].Description) require.Len(t, summary.TelegrafConfigs, 1) assert.NotZero(t, summary.TelegrafConfigs[0].TelegrafConfig.ID) assert.Equal(t, "tele_0", summary.TelegrafConfigs[0].TelegrafConfig.Name) assert.Equal(t, "init desc", summary.TelegrafConfigs[0].TelegrafConfig.Description) require.Len(t, summary.Variables, 1) assert.NotZero(t, summary.Variables[0].ID) assert.Equal(t, "var char", summary.Variables[0].Name) assert.Equal(t, "init desc", summary.Variables[0].Description) t.Log("\tverify changes reflected in platform") { actualBkt := resourceCheck.mustGetBucket(t, byName("display name")) assert.Equal(t, summary.Buckets[0].ID, pkger.SafeID(actualBkt.ID)) actualCheck := resourceCheck.mustGetCheck(t, byName("check_0")) assert.Equal(t, summary.Checks[0].Check.GetID(), actualCheck.GetID()) actualDash := resourceCheck.mustGetDashboard(t, byName("dash_0")) assert.Equal(t, summary.Dashboards[0].ID, pkger.SafeID(actualDash.ID)) actualEndpint := resourceCheck.mustGetEndpoint(t, byName("endpoint_0")) assert.Equal(t, summary.NotificationEndpoints[0].NotificationEndpoint.GetID(), actualEndpint.GetID()) actualLabel := resourceCheck.mustGetLabel(t, byName("label 1")) assert.Equal(t, summary.Labels[0].ID, pkger.SafeID(actualLabel.ID)) actualRule := resourceCheck.mustGetRule(t, byName("rule_0")) assert.Equal(t, summary.NotificationRules[0].ID, pkger.SafeID(actualRule.GetID())) actualTask := resourceCheck.mustGetTask(t, byName("task_0")) assert.Equal(t, summary.Tasks[0].ID, pkger.SafeID(actualTask.ID)) actualTele := resourceCheck.mustGetTelegrafConfig(t, byName("tele_0")) assert.Equal(t, summary.TelegrafConfigs[0].TelegrafConfig.ID, actualTele.ID) actualVar := resourceCheck.mustGetVariable(t, byName("var char")) assert.Equal(t, summary.Variables[0].ID, pkger.SafeID(actualVar.ID)) } return summary, stack, cleanup } t.Run("apply pkg with stack id", func(t *testing.T) { _, _, cleanup := testStackApplyFn(t) cleanup() }) t.Run("apply pkg with stack id where resources change", func(t *testing.T) { initialSum, stack, cleanup := testStackApplyFn(t) defer cleanup() var ( updateBucketName = "new bucket" updateCheckName = "new check" updateDashName = "new dash" updateEndpointName = "new endpoint" updateLabelName = "new label" updateRuleName = "new rule" updateTaskName = "new task" updateTelegrafName = "new telegraf" updateVariableName = "new variable" ) updatedPkg := newTemplate( newBucketObject(initialSum.Buckets[0].MetaName, updateBucketName, ""), newCheckDeadmanObject(t, initialSum.Checks[0].MetaName, updateCheckName, time.Hour), newDashObject(initialSum.Dashboards[0].MetaName, updateDashName, ""), newEndpointHTTP(initialSum.NotificationEndpoints[0].MetaName, updateEndpointName, ""), newLabelObject(initialSum.Labels[0].MetaName, updateLabelName, "", ""), newRuleObject( t, initialSum.NotificationRules[0].MetaName, updateRuleName, initialSum.NotificationEndpoints[0].MetaName, "", ), newTaskObject(initialSum.Tasks[0].MetaName, updateTaskName, ""), newTelegrafObject(initialSum.TelegrafConfigs[0].MetaName, updateTelegrafName, ""), newVariableObject(initialSum.Variables[0].MetaName, updateVariableName, ""), ) impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(updatedPkg), pkger.ApplyWithStackID(stack.ID), ) require.NoError(t, err) sum := impact.Summary require.Len(t, sum.Buckets, 1) assert.Equal(t, initialSum.Buckets[0].ID, sum.Buckets[0].ID) assert.Equal(t, updateBucketName, sum.Buckets[0].Name) require.Len(t, sum.Checks, 1) assert.Equal(t, initialSum.Checks[0].Check.GetID(), sum.Checks[0].Check.GetID()) assert.Equal(t, updateCheckName, sum.Checks[0].Check.GetName()) require.Len(t, sum.Dashboards, 1) assert.Equal(t, initialSum.Dashboards[0].ID, sum.Dashboards[0].ID) assert.Equal(t, updateDashName, sum.Dashboards[0].Name) require.Len(t, sum.NotificationEndpoints, 1) sumEndpoint := sum.NotificationEndpoints[0].NotificationEndpoint assert.Equal(t, initialSum.NotificationEndpoints[0].NotificationEndpoint.GetID(), sumEndpoint.GetID()) assert.Equal(t, updateEndpointName, sumEndpoint.GetName()) require.Len(t, sum.NotificationRules, 1) sumRule := sum.NotificationRules[0] assert.Equal(t, initialSum.NotificationRules[0].ID, sumRule.ID) assert.Equal(t, updateRuleName, sumRule.Name) require.Len(t, sum.Labels, 1) assert.Equal(t, initialSum.Labels[0].ID, sum.Labels[0].ID) assert.Equal(t, updateLabelName, sum.Labels[0].Name) require.Len(t, sum.Tasks, 1) assert.Equal(t, initialSum.Tasks[0].ID, sum.Tasks[0].ID) assert.Equal(t, updateTaskName, sum.Tasks[0].Name) require.Len(t, sum.TelegrafConfigs, 1) updatedTele := sum.TelegrafConfigs[0].TelegrafConfig assert.Equal(t, initialSum.TelegrafConfigs[0].TelegrafConfig.ID, updatedTele.ID) assert.Equal(t, updateTelegrafName, updatedTele.Name) require.Len(t, sum.Variables, 1) assert.Equal(t, initialSum.Variables[0].ID, sum.Variables[0].ID) assert.Equal(t, updateVariableName, sum.Variables[0].Name) t.Log("\tverify changes reflected in platform") { actualBkt := resourceCheck.mustGetBucket(t, byName(updateBucketName)) require.Equal(t, initialSum.Buckets[0].ID, pkger.SafeID(actualBkt.ID)) actualCheck := resourceCheck.mustGetCheck(t, byName(updateCheckName)) require.Equal(t, initialSum.Checks[0].Check.GetID(), actualCheck.GetID()) actualDash := resourceCheck.mustGetDashboard(t, byName(updateDashName)) require.Equal(t, initialSum.Dashboards[0].ID, pkger.SafeID(actualDash.ID)) actualEndpoint := resourceCheck.mustGetEndpoint(t, byName(updateEndpointName)) assert.Equal(t, sumEndpoint.GetID(), actualEndpoint.GetID()) actualLabel := resourceCheck.mustGetLabel(t, byName(updateLabelName)) require.Equal(t, initialSum.Labels[0].ID, pkger.SafeID(actualLabel.ID)) actualRule := resourceCheck.mustGetRule(t, byName(updateRuleName)) require.Equal(t, initialSum.NotificationRules[0].ID, pkger.SafeID(actualRule.GetID())) actualTask := resourceCheck.mustGetTask(t, byName(updateTaskName)) require.Equal(t, initialSum.Tasks[0].ID, pkger.SafeID(actualTask.ID)) actualTelegraf := resourceCheck.mustGetTelegrafConfig(t, byName(updateTelegrafName)) require.Equal(t, initialSum.TelegrafConfigs[0].TelegrafConfig.ID, actualTelegraf.ID) actualVar := resourceCheck.mustGetVariable(t, byName(updateVariableName)) assert.Equal(t, sum.Variables[0].ID, pkger.SafeID(actualVar.ID)) } }) t.Run("an error during application roles back resources to previous state", func(t *testing.T) { initialSum, stack, cleanup := testStackApplyFn(t) defer cleanup() logger := l.log.With(zap.String("service", "pkger")) var svc pkger.SVC = pkger.NewService( pkger.WithLogger(logger), pkger.WithBucketSVC(l.BucketService(t)), pkger.WithDashboardSVC(l.DashboardService(t)), pkger.WithCheckSVC(l.CheckService()), pkger.WithLabelSVC(l.LabelService(t)), pkger.WithNotificationEndpointSVC(l.NotificationEndpointService(t)), pkger.WithNotificationRuleSVC(&fakeRuleStore{ NotificationRuleStore: l.NotificationRuleService(t), createKillCount: 2, }), pkger.WithStore(pkger.NewStoreKV(l.Launcher.kvStore)), pkger.WithTaskSVC(l.TaskServiceKV(t)), pkger.WithTelegrafSVC(l.TelegrafService(t)), pkger.WithVariableSVC(l.VariableService(t)), ) svc = pkger.MWLogging(logger)(svc) endpointPkgName := "z-endpoint-rolls-back" pkgWithDelete := newTemplate( newBucketObject("z-roll-me-back", "", ""), newBucketObject("z-rolls-back-too", "", ""), newDashObject("z-rolls-dash", "", ""), newLabelObject("z-label-roller", "", "", ""), newCheckDeadmanObject(t, "z-check", "", time.Hour), newEndpointHTTP(endpointPkgName, "", ""), newRuleObject(t, "z-rules-back", "", endpointPkgName, ""), newRuleObject(t, "z-rules-back-2", "", endpointPkgName, ""), newRuleObject(t, "z-rules-back-3", "", endpointPkgName, ""), newTaskObject("z-task-rolls-back", "", ""), newTelegrafObject("z-telegraf-rolls-back", "", ""), newVariableObject("z-var-rolls-back", "", ""), ) _, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(pkgWithDelete), pkger.ApplyWithStackID(stack.ID), ) require.Error(t, err) t.Log("validate all resources are rolled back") { actualBkt := resourceCheck.mustGetBucket(t, byName(initialSum.Buckets[0].Name)) assert.NotEqual(t, initialSum.Buckets[0].ID, pkger.SafeID(actualBkt.ID)) actualCheck := resourceCheck.mustGetCheck(t, byName(initialSum.Checks[0].Check.GetName())) assert.NotEqual(t, initialSum.Checks[0].Check.GetID(), actualCheck.GetID()) actualDash := resourceCheck.mustGetDashboard(t, byName(initialSum.Dashboards[0].Name)) assert.NotEqual(t, initialSum.Dashboards[0].ID, pkger.SafeID(actualDash.ID)) actualEndpoint := resourceCheck.mustGetEndpoint(t, byName(initialSum.NotificationEndpoints[0].NotificationEndpoint.GetName())) assert.NotEqual(t, initialSum.NotificationEndpoints[0].NotificationEndpoint.GetID(), actualEndpoint.GetID()) actualRule := resourceCheck.mustGetRule(t, byName(initialSum.NotificationRules[0].Name)) assert.NotEqual(t, initialSum.NotificationRules[0].ID, pkger.SafeID(actualRule.GetID())) actualLabel := resourceCheck.mustGetLabel(t, byName(initialSum.Labels[0].Name)) assert.NotEqual(t, initialSum.Labels[0].ID, pkger.SafeID(actualLabel.ID)) actualTask := resourceCheck.mustGetTask(t, byName(initialSum.Tasks[0].Name)) assert.NotEqual(t, initialSum.Tasks[0].ID, pkger.SafeID(actualTask.ID)) actualTelegraf := resourceCheck.mustGetTelegrafConfig(t, byName(initialSum.TelegrafConfigs[0].TelegrafConfig.Name)) assert.NotEqual(t, initialSum.TelegrafConfigs[0].TelegrafConfig.ID, actualTelegraf.ID) actualVariable := resourceCheck.mustGetVariable(t, byName(initialSum.Variables[0].Name)) assert.NotEqual(t, initialSum.Variables[0].ID, pkger.SafeID(actualVariable.ID)) } t.Log("validate all changes do not persist") { for _, name := range []string{"z-roll-me-back", "z-rolls-back-too"} { _, err := resourceCheck.getBucket(t, byName(name)) assert.Error(t, err) } for _, name := range []string{"z-rules-back", "z-rules-back-2", "z-rules-back-3"} { _, err = resourceCheck.getRule(t, byName(name)) assert.Error(t, err) } _, err := resourceCheck.getCheck(t, byName("z-check")) assert.Error(t, err) _, err = resourceCheck.getDashboard(t, byName("z-rolls_dash")) assert.Error(t, err) _, err = resourceCheck.getEndpoint(t, byName("z-endpoint-rolls-back")) assert.Error(t, err) _, err = resourceCheck.getLabel(t, byName("z-label-roller")) assert.Error(t, err) _, err = resourceCheck.getTelegrafConfig(t, byName("z-telegraf-rolls-back")) assert.Error(t, err) _, err = resourceCheck.getVariable(t, byName("z-var-rolls-back")) assert.Error(t, err) } }) t.Run("apply pkg with stack id where resources have been removed since last run", func(t *testing.T) { initialSum, stack, cleanup := testStackApplyFn(t) defer cleanup() newEndpointPkgName := "non-existent-endpoint" allNewResourcesPkg := newTemplate( newBucketObject("non-existent-bucket", "", ""), newCheckDeadmanObject(t, "non-existent-check", "", time.Minute), newDashObject("non-existent-dash", "", ""), newEndpointHTTP(newEndpointPkgName, "", ""), newLabelObject("non-existent-label", "", "", ""), newRuleObject(t, "non-existent-rule", "", newEndpointPkgName, ""), newTaskObject("non-existent-task", "", ""), newTelegrafObject("non-existent-tele", "", ""), newVariableObject("non-existent-var", "", ""), ) impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(allNewResourcesPkg), pkger.ApplyWithStackID(stack.ID), ) require.NoError(t, err) sum := impact.Summary require.Len(t, sum.Buckets, 1) assert.NotEqual(t, initialSum.Buckets[0].ID, sum.Buckets[0].ID) assert.NotZero(t, sum.Buckets[0].ID) assert.Equal(t, "non-existent-bucket", sum.Buckets[0].Name) require.Len(t, sum.Checks, 1) assert.NotEqual(t, initialSum.Checks[0].Check.GetID(), sum.Checks[0].Check.GetID()) assert.NotZero(t, sum.Checks[0].Check.GetID()) assert.Equal(t, "non-existent-check", sum.Checks[0].Check.GetName()) require.Len(t, sum.Dashboards, 1) assert.NotEqual(t, initialSum.Dashboards[0].ID, sum.Dashboards[0].ID) assert.NotZero(t, sum.Dashboards[0].ID) assert.Equal(t, "non-existent-dash", sum.Dashboards[0].Name) require.Len(t, sum.NotificationEndpoints, 1) sumEndpoint := sum.NotificationEndpoints[0].NotificationEndpoint assert.NotEqual(t, initialSum.NotificationEndpoints[0].NotificationEndpoint.GetID(), sumEndpoint.GetID()) assert.NotZero(t, sumEndpoint.GetID()) assert.Equal(t, newEndpointPkgName, sumEndpoint.GetName()) require.Len(t, sum.NotificationRules, 1) sumRule := sum.NotificationRules[0] assert.NotEqual(t, initialSum.NotificationRules[0].ID, sumRule.ID) assert.NotZero(t, sumRule.ID) assert.Equal(t, "non-existent-rule", sumRule.Name) require.Len(t, sum.Labels, 1) assert.NotEqual(t, initialSum.Labels[0].ID, sum.Labels[0].ID) assert.NotZero(t, sum.Labels[0].ID) assert.Equal(t, "non-existent-label", sum.Labels[0].Name) require.Len(t, sum.Tasks, 1) assert.NotEqual(t, initialSum.Tasks[0].ID, sum.Tasks[0].ID) assert.NotZero(t, sum.Tasks[0].ID) assert.Equal(t, "non-existent-task", sum.Tasks[0].Name) require.Len(t, sum.TelegrafConfigs, 1) newTele := sum.TelegrafConfigs[0].TelegrafConfig assert.NotEqual(t, initialSum.TelegrafConfigs[0].TelegrafConfig.ID, newTele.ID) assert.NotZero(t, newTele.ID) assert.Equal(t, "non-existent-tele", newTele.Name) require.Len(t, sum.Variables, 1) assert.NotEqual(t, initialSum.Variables[0].ID, sum.Variables[0].ID) assert.NotZero(t, sum.Variables[0].ID) assert.Equal(t, "non-existent-var", sum.Variables[0].Name) t.Log("\tvalidate all resources are created") { bkt := resourceCheck.mustGetBucket(t, byName("non-existent-bucket")) assert.Equal(t, pkger.SafeID(bkt.ID), sum.Buckets[0].ID) chk := resourceCheck.mustGetCheck(t, byName("non-existent-check")) assert.Equal(t, chk.GetID(), sum.Checks[0].Check.GetID()) endpoint := resourceCheck.mustGetEndpoint(t, byName(newEndpointPkgName)) assert.Equal(t, endpoint.GetID(), sum.NotificationEndpoints[0].NotificationEndpoint.GetID()) label := resourceCheck.mustGetLabel(t, byName("non-existent-label")) assert.Equal(t, pkger.SafeID(label.ID), sum.Labels[0].ID) actualRule := resourceCheck.mustGetRule(t, byName("non-existent-rule")) assert.Equal(t, pkger.SafeID(actualRule.GetID()), sum.NotificationRules[0].ID) task := resourceCheck.mustGetTask(t, byName("non-existent-task")) assert.Equal(t, pkger.SafeID(task.ID), sum.Tasks[0].ID) tele := resourceCheck.mustGetTelegrafConfig(t, byName("non-existent-tele")) assert.Equal(t, tele.ID, sum.TelegrafConfigs[0].TelegrafConfig.ID) variable := resourceCheck.mustGetVariable(t, byName("non-existent-var")) assert.Equal(t, pkger.SafeID(variable.ID), sum.Variables[0].ID) } t.Log("\tvalidate all previous resources are removed") { _, err = resourceCheck.getBucket(t, byName(initialSum.Buckets[0].Name)) require.Error(t, err) _, err = resourceCheck.getCheck(t, byName(initialSum.Checks[0].Check.GetName())) require.Error(t, err) _, err = resourceCheck.getEndpoint(t, byName(initialSum.NotificationEndpoints[0].NotificationEndpoint.GetName())) require.Error(t, err) _, err = resourceCheck.getLabel(t, byName(initialSum.Labels[0].Name)) require.Error(t, err) _, err = resourceCheck.getTask(t, byName(initialSum.Tasks[0].Name)) require.Error(t, err) _, err = resourceCheck.getTelegrafConfig(t, byName(initialSum.TelegrafConfigs[0].TelegrafConfig.Name)) require.Error(t, err) _, err = resourceCheck.getVariable(t, byName(initialSum.Variables[0].Name)) require.Error(t, err) } }) }) t.Run("apply should handle cases where users have changed platform data", func(t *testing.T) { initializeStackPkg := func(t *testing.T, pkg *pkger.Template) (platform.ID, func(), pkger.Summary) { t.Helper() stack, cleanup := newStackFn(t, pkger.StackCreate{}) defer func() { if t.Failed() { cleanup() } }() impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(pkg), pkger.ApplyWithStackID(stack.ID), ) require.NoError(t, err) return stack.ID, cleanup, impact.Summary } testValidRemoval := func(t *testing.T, stackID platform.ID) { t.Helper() _, err := svc.Apply( ctx, l.Org.ID, l.User.ID, pkger.ApplyWithStackID(stackID), ) require.NoError(t, err) } t.Run("when a user has deleted a variable that was previously created by a stack", func(t *testing.T) { testUserDeletedVariable := func(t *testing.T, actionFn func(t *testing.T, stackID platform.ID, initialVarObj pkger.Object, initialSum pkger.Summary)) { t.Helper() obj := newVariableObject("var-1", "", "") stackID, cleanup, initialSum := initializeStackPkg(t, newTemplate(obj)) defer cleanup() require.Len(t, initialSum.Variables, 1) require.NotZero(t, initialSum.Variables[0].ID) resourceCheck.mustDeleteVariable(t, platform.ID(initialSum.Variables[0].ID)) actionFn(t, stackID, obj, initialSum) } t.Run("should create new resource when attempting to update", func(t *testing.T) { testUserDeletedVariable(t, func(t *testing.T, stackID platform.ID, initialVarObj pkger.Object, initialSum pkger.Summary) { pkg := newTemplate(initialVarObj) impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(pkg), pkger.ApplyWithStackID(stackID)) require.NoError(t, err) updateSum := impact.Summary require.Len(t, updateSum.Variables, 1) initVar, updateVar := initialSum.Variables[0], updateSum.Variables[0] assert.NotEqual(t, initVar.ID, updateVar.ID) initVar.ID, updateVar.ID = 0, 0 assert.Equal(t, initVar, updateVar) }) }) t.Run("should not error when attempting to remove", func(t *testing.T) { testUserDeletedVariable(t, func(t *testing.T, stackID platform.ID, initialVarObj pkger.Object, initialSum pkger.Summary) { testValidRemoval(t, stackID) }) }) }) t.Run("when a user has deleted a bucket that was previously created by a stack", func(t *testing.T) { testUserDeletedBucket := func(t *testing.T, actionFn func(t *testing.T, stackID platform.ID, initialObj pkger.Object, initialSum pkger.Summary)) { t.Helper() obj := newBucketObject("bucket-1", "", "") stackID, cleanup, initialSum := initializeStackPkg(t, newTemplate(obj)) defer cleanup() require.Len(t, initialSum.Buckets, 1) require.NotZero(t, initialSum.Buckets[0].ID) resourceCheck.mustDeleteBucket(t, platform.ID(initialSum.Buckets[0].ID)) actionFn(t, stackID, obj, initialSum) } t.Run("should create new resource when attempting to update", func(t *testing.T) { testUserDeletedBucket(t, func(t *testing.T, stackID platform.ID, initialObj pkger.Object, initialSum pkger.Summary) { pkg := newTemplate(initialObj) impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(pkg), pkger.ApplyWithStackID(stackID)) require.NoError(t, err) updateSum := impact.Summary require.Len(t, updateSum.Buckets, 1) intial, updated := initialSum.Buckets[0], updateSum.Buckets[0] assert.NotEqual(t, intial.ID, updated.ID) intial.ID, updated.ID = 0, 0 assert.Equal(t, intial, updated) }) }) t.Run("should not error when attempting to remove", func(t *testing.T) { testUserDeletedBucket(t, func(t *testing.T, stackID platform.ID, initialObj pkger.Object, initialSum pkger.Summary) { testValidRemoval(t, stackID) }) }) }) t.Run("when a user has deleted a check that was previously created by a stack", func(t *testing.T) { testUserDeletedCheck := func(t *testing.T, actionFn func(t *testing.T, stackID platform.ID, initialObj pkger.Object, initialSum pkger.Summary)) { t.Helper() obj := newCheckDeadmanObject(t, "check-1", "", time.Hour) stackID, cleanup, initialSum := initializeStackPkg(t, newTemplate(obj)) defer cleanup() require.Len(t, initialSum.Checks, 1) require.NotZero(t, initialSum.Checks[0].Check.GetID()) resourceCheck.mustDeleteCheck(t, initialSum.Checks[0].Check.GetID()) actionFn(t, stackID, obj, initialSum) } t.Run("should create new resource when attempting to update", func(t *testing.T) { testUserDeletedCheck(t, func(t *testing.T, stackID platform.ID, initialObj pkger.Object, initialSum pkger.Summary) { pkg := newTemplate(initialObj) impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(pkg), pkger.ApplyWithStackID(stackID)) require.NoError(t, err) updateSum := impact.Summary require.Len(t, updateSum.Checks, 1) intial, updated := initialSum.Checks[0].Check, updateSum.Checks[0].Check assert.NotEqual(t, intial.GetID(), updated.GetID()) intial.SetID(0) updated.SetID(0) assert.Equal(t, intial, updated) }) }) t.Run("should not error when attempting to remove", func(t *testing.T) { testUserDeletedCheck(t, func(t *testing.T, stackID platform.ID, initialObj pkger.Object, initialSum pkger.Summary) { testValidRemoval(t, stackID) }) }) }) t.Run("when a user has deleted a dashboard that was previously created by a stack", func(t *testing.T) { testUserDeletedDashboard := func(t *testing.T, actionFn func(t *testing.T, stackID platform.ID, initialObj pkger.Object, initialSum pkger.Summary)) { t.Helper() obj := newDashObject("dash-1", "", "") stackID, cleanup, initialSum := initializeStackPkg(t, newTemplate(obj)) defer cleanup() require.Len(t, initialSum.Dashboards, 1) require.NotZero(t, initialSum.Dashboards[0].ID) resourceCheck.mustDeleteDashboard(t, platform.ID(initialSum.Dashboards[0].ID)) actionFn(t, stackID, obj, initialSum) } t.Run("should create new resource when attempting to update", func(t *testing.T) { testUserDeletedDashboard(t, func(t *testing.T, stackID platform.ID, initialObj pkger.Object, initialSum pkger.Summary) { pkg := newTemplate(initialObj) impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(pkg), pkger.ApplyWithStackID(stackID)) require.NoError(t, err) updateSum := impact.Summary require.Len(t, updateSum.Dashboards, 1) initial, updated := initialSum.Dashboards[0], updateSum.Dashboards[0] assert.NotEqual(t, initial.ID, updated.ID) initial.ID, updated.ID = 0, 0 assert.Equal(t, initial, updated) }) }) t.Run("should not error when attempting to remove", func(t *testing.T) { testUserDeletedDashboard(t, func(t *testing.T, stackID platform.ID, initialObj pkger.Object, initialSum pkger.Summary) { testValidRemoval(t, stackID) }) }) }) t.Run("when a user has deleted a label that was previously created by a stack", func(t *testing.T) { testUserDeletedLabel := func(t *testing.T, actionFn func(t *testing.T, stackID platform.ID, initialObj pkger.Object, initialSum pkger.Summary)) { t.Helper() obj := newLabelObject("label-1", "", "", "") stackID, cleanup, initialSum := initializeStackPkg(t, newTemplate(obj)) defer cleanup() require.Len(t, initialSum.Labels, 1) require.NotZero(t, initialSum.Labels[0].ID) resourceCheck.mustDeleteLabel(t, platform.ID(initialSum.Labels[0].ID)) actionFn(t, stackID, obj, initialSum) } t.Run("should create new resource when attempting to update", func(t *testing.T) { testUserDeletedLabel(t, func(t *testing.T, stackID platform.ID, initialObj pkger.Object, initialSum pkger.Summary) { pkg := newTemplate(initialObj) impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(pkg), pkger.ApplyWithStackID(stackID)) require.NoError(t, err) updateSum := impact.Summary require.Len(t, updateSum.Labels, 1) initial, updated := initialSum.Labels[0], updateSum.Labels[0] assert.NotEqual(t, initial.ID, updated.ID, "label ID should be different") initial.ID, updated.ID = 0, 0 assert.Equal(t, initial, updated) }) }) t.Run("should not error when attempting to remove", func(t *testing.T) { testUserDeletedLabel(t, func(t *testing.T, stackID platform.ID, initialObj pkger.Object, initialSum pkger.Summary) { testValidRemoval(t, stackID) }) }) }) t.Run("when a user has deleted a notification endpoint that was previously created by a stack", func(t *testing.T) { testUserDeletedEndpoint := func(t *testing.T, actionFn func(t *testing.T, stackID platform.ID, initialObj pkger.Object, initialSum pkger.Summary)) { t.Helper() obj := newEndpointHTTP("endpoint-1", "", "") stackID, cleanup, initialSum := initializeStackPkg(t, newTemplate(obj)) defer cleanup() require.Len(t, initialSum.NotificationEndpoints, 1) require.NotZero(t, initialSum.NotificationEndpoints[0].NotificationEndpoint.GetID()) resourceCheck.mustDeleteEndpoint(t, initialSum.NotificationEndpoints[0].NotificationEndpoint.GetID()) actionFn(t, stackID, obj, initialSum) } t.Run("should create new resource when attempting to update", func(t *testing.T) { testUserDeletedEndpoint(t, func(t *testing.T, stackID platform.ID, initialObj pkger.Object, initialSum pkger.Summary) { pkg := newTemplate(initialObj) impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(pkg), pkger.ApplyWithStackID(stackID)) require.NoError(t, err) updateSum := impact.Summary require.Len(t, updateSum.NotificationEndpoints, 1) initial, updated := initialSum.NotificationEndpoints[0].NotificationEndpoint, updateSum.NotificationEndpoints[0].NotificationEndpoint assert.NotEqual(t, initial.GetID(), updated.GetID()) initial.SetID(0) updated.SetID(0) assert.Equal(t, initial, updated) }) }) t.Run("should not error when attempting to remove", func(t *testing.T) { testUserDeletedEndpoint(t, func(t *testing.T, stackID platform.ID, initialObj pkger.Object, initialSum pkger.Summary) { testValidRemoval(t, stackID) }) }) }) t.Run("when a user has deleted a notification rule that was previously created by a stack", func(t *testing.T) { testUserDeletedRule := func(t *testing.T, actionFn func(t *testing.T, stackID platform.ID, initialObjects []pkger.Object, initialSum pkger.Summary)) { t.Helper() endpointObj := newEndpointHTTP("endpoint-1", "", "") ruleObj := newRuleObject(t, "rule-0", "", endpointObj.Name(), "") stackID, cleanup, initialSum := initializeStackPkg(t, newTemplate(endpointObj, ruleObj)) defer cleanup() require.Len(t, initialSum.NotificationEndpoints, 1) require.NotZero(t, initialSum.NotificationEndpoints[0].NotificationEndpoint.GetID()) require.Len(t, initialSum.NotificationRules, 1) require.NotZero(t, initialSum.NotificationRules[0].ID) resourceCheck.mustDeleteRule(t, platform.ID(initialSum.NotificationRules[0].ID)) actionFn(t, stackID, []pkger.Object{ruleObj, endpointObj}, initialSum) } t.Run("should create new resource when attempting to update", func(t *testing.T) { testUserDeletedRule(t, func(t *testing.T, stackID platform.ID, initialObjects []pkger.Object, initialSum pkger.Summary) { pkg := newTemplate(initialObjects...) impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(pkg), pkger.ApplyWithStackID(stackID)) require.NoError(t, err) updateSum := impact.Summary require.Len(t, updateSum.NotificationRules, 1) initial, updated := initialSum.NotificationRules[0], updateSum.NotificationRules[0] assert.NotEqual(t, initial.ID, updated.ID) initial.ID, updated.ID = 0, 0 assert.Equal(t, initial, updated) }) }) t.Run("should not error when attempting to remove", func(t *testing.T) { testUserDeletedRule(t, func(t *testing.T, stackID platform.ID, _ []pkger.Object, _ pkger.Summary) { testValidRemoval(t, stackID) }) }) }) t.Run("when a user has deleted a task that was previously created by a stack", func(t *testing.T) { testUserDeletedTask := func(t *testing.T, actionFn func(t *testing.T, stackID platform.ID, initialObj pkger.Object, initialSum pkger.Summary)) { t.Helper() obj := newTaskObject("task-1", "", "") stackID, cleanup, initialSum := initializeStackPkg(t, newTemplate(obj)) defer cleanup() require.Len(t, initialSum.Tasks, 1) require.NotZero(t, initialSum.Tasks[0].ID) resourceCheck.mustDeleteTask(t, platform.ID(initialSum.Tasks[0].ID)) actionFn(t, stackID, obj, initialSum) } t.Run("should create new resource when attempting to update", func(t *testing.T) { testUserDeletedTask(t, func(t *testing.T, stackID platform.ID, initialObj pkger.Object, initialSum pkger.Summary) { pkg := newTemplate(initialObj) impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(pkg), pkger.ApplyWithStackID(stackID)) require.NoError(t, err) updateSum := impact.Summary require.Len(t, updateSum.Tasks, 1) initial, updated := initialSum.Tasks[0], updateSum.Tasks[0] assert.NotEqual(t, initial.ID, updated.ID) initial.ID, updated.ID = 0, 0 assert.Equal(t, initial, updated) }) }) t.Run("should not error when attempting to remove", func(t *testing.T) { testUserDeletedTask(t, func(t *testing.T, stackID platform.ID, _ pkger.Object, _ pkger.Summary) { testValidRemoval(t, stackID) }) }) }) t.Run("when a user has deleted a telegraf config that was previously created by a stack", func(t *testing.T) { testUserDeletedTelegraf := func(t *testing.T, actionFn func(t *testing.T, stackID platform.ID, initialObj pkger.Object, initialSum pkger.Summary)) { t.Helper() obj := newTelegrafObject("tele-1", "", "") stackID, cleanup, initialSum := initializeStackPkg(t, newTemplate(obj)) defer cleanup() require.Len(t, initialSum.TelegrafConfigs, 1) require.NotZero(t, initialSum.TelegrafConfigs[0].TelegrafConfig.ID) resourceCheck.mustDeleteTelegrafConfig(t, initialSum.TelegrafConfigs[0].TelegrafConfig.ID) actionFn(t, stackID, obj, initialSum) } t.Run("should create new resource when attempting to update", func(t *testing.T) { testUserDeletedTelegraf(t, func(t *testing.T, stackID platform.ID, initialObj pkger.Object, initialSum pkger.Summary) { pkg := newTemplate(initialObj) impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(pkg), pkger.ApplyWithStackID(stackID)) require.NoError(t, err) updateSum := impact.Summary require.Len(t, updateSum.TelegrafConfigs, 1) initial, updated := initialSum.TelegrafConfigs[0].TelegrafConfig, updateSum.TelegrafConfigs[0].TelegrafConfig assert.NotEqual(t, initial.ID, updated.ID) initial.ID, updated.ID = 0, 0 assert.Equal(t, initial, updated) }) }) t.Run("should not error when attempting to remove", func(t *testing.T) { testUserDeletedTelegraf(t, func(t *testing.T, stackID platform.ID, _ pkger.Object, _ pkger.Summary) { testValidRemoval(t, stackID) }) }) }) }) t.Run("applying updates to existing variable should be successful", func(t *testing.T) { stack, cleanup := newStackFn(t, pkger.StackCreate{}) defer cleanup() impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithStackID(stack.ID), pkger.ApplyWithTemplate(newTemplate(newVariableObject("var", "", ""))), ) require.NoError(t, err) vars := impact.Summary.Variables require.Len(t, vars, 1) v := resourceCheck.mustGetVariable(t, byID(platform.ID(vars[0].ID))) assert.Empty(t, v.Selected) impact, err = svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithStackID(stack.ID), pkger.ApplyWithTemplate(newTemplate(newVariableObject("var", "", "", "selected"))), ) require.NoError(t, err) vars = impact.Summary.Variables require.Len(t, vars, 1) v = resourceCheck.mustGetVariable(t, byID(platform.ID(vars[0].ID))) assert.Equal(t, []string{"selected"}, v.Selected) }) t.Run("apply with actions", func(t *testing.T) { var ( bucketPkgName = "rucketeer-1" checkPkgName = "checkers" dashPkgName = "dash-of-salt" endpointPkgName = "endzo" labelPkgName = "labelino" rulePkgName = "oh-doyle-rules" taskPkgName = "tap" telegrafPkgName = "teletype" variablePkgName = "laces-out-dan" ) defaultPkgFn := func(*testing.T) *pkger.Template { return newTemplate( newBucketObject(bucketPkgName, "", ""), newCheckDeadmanObject(t, checkPkgName, "", time.Hour), newDashObject(dashPkgName, "", ""), newEndpointHTTP(endpointPkgName, "", ""), newLabelObject(labelPkgName, "", "", ""), newRuleObject(t, rulePkgName, "", endpointPkgName, ""), newTaskObject(taskPkgName, "", ""), newTelegrafObject(telegrafPkgName, "", ""), newVariableObject(variablePkgName, "", ""), ) } tests := []struct { name string pkgFn func(t *testing.T) *pkger.Template applyOpts []pkger.ApplyOptFn assertFn func(t *testing.T, impact pkger.ImpactSummary) }{ { name: "skip resource", pkgFn: defaultPkgFn, applyOpts: []pkger.ApplyOptFn{ pkger.ApplyWithResourceSkip(pkger.ActionSkipResource{ Kind: pkger.KindBucket, MetaName: bucketPkgName, }), pkger.ApplyWithResourceSkip(pkger.ActionSkipResource{ Kind: pkger.KindCheckDeadman, MetaName: checkPkgName, }), pkger.ApplyWithResourceSkip(pkger.ActionSkipResource{ Kind: pkger.KindDashboard, MetaName: dashPkgName, }), pkger.ApplyWithResourceSkip(pkger.ActionSkipResource{ Kind: pkger.KindNotificationEndpointHTTP, MetaName: endpointPkgName, }), pkger.ApplyWithResourceSkip(pkger.ActionSkipResource{ Kind: pkger.KindLabel, MetaName: labelPkgName, }), pkger.ApplyWithResourceSkip(pkger.ActionSkipResource{ Kind: pkger.KindNotificationRule, MetaName: rulePkgName, }), pkger.ApplyWithResourceSkip(pkger.ActionSkipResource{ Kind: pkger.KindTask, MetaName: taskPkgName, }), pkger.ApplyWithResourceSkip(pkger.ActionSkipResource{ Kind: pkger.KindTelegraf, MetaName: telegrafPkgName, }), pkger.ApplyWithResourceSkip(pkger.ActionSkipResource{ Kind: pkger.KindVariable, MetaName: variablePkgName, }), }, assertFn: func(t *testing.T, impact pkger.ImpactSummary) { summary := impact.Summary assert.Empty(t, summary.Buckets) assert.Empty(t, summary.Checks) assert.Empty(t, summary.Dashboards) assert.Empty(t, summary.NotificationEndpoints) assert.Empty(t, summary.Labels) assert.Empty(t, summary.NotificationRules, 0) assert.Empty(t, summary.Tasks) assert.Empty(t, summary.TelegrafConfigs) assert.Empty(t, summary.Variables) }, }, { name: "skip kind", pkgFn: defaultPkgFn, applyOpts: []pkger.ApplyOptFn{ pkger.ApplyWithKindSkip(pkger.ActionSkipKind{ Kind: pkger.KindBucket, }), pkger.ApplyWithKindSkip(pkger.ActionSkipKind{ Kind: pkger.KindCheckDeadman, }), pkger.ApplyWithKindSkip(pkger.ActionSkipKind{ Kind: pkger.KindDashboard, }), pkger.ApplyWithKindSkip(pkger.ActionSkipKind{ Kind: pkger.KindNotificationEndpointHTTP, }), pkger.ApplyWithKindSkip(pkger.ActionSkipKind{ Kind: pkger.KindLabel, }), pkger.ApplyWithKindSkip(pkger.ActionSkipKind{ Kind: pkger.KindNotificationRule, }), pkger.ApplyWithKindSkip(pkger.ActionSkipKind{ Kind: pkger.KindTask, }), pkger.ApplyWithKindSkip(pkger.ActionSkipKind{ Kind: pkger.KindTelegraf, }), pkger.ApplyWithKindSkip(pkger.ActionSkipKind{ Kind: pkger.KindVariable, }), }, assertFn: func(t *testing.T, impact pkger.ImpactSummary) { summary := impact.Summary assert.Empty(t, summary.Buckets) assert.Empty(t, summary.Checks) assert.Empty(t, summary.Dashboards) assert.Empty(t, summary.NotificationEndpoints) assert.Empty(t, summary.Labels) assert.Empty(t, summary.NotificationRules, 0) assert.Empty(t, summary.Tasks) assert.Empty(t, summary.TelegrafConfigs) assert.Empty(t, summary.Variables) }, }, { name: "skip label and assoications should be dropped", pkgFn: func(t *testing.T) *pkger.Template { objs := []pkger.Object{ newBucketObject(bucketPkgName, "", ""), newCheckDeadmanObject(t, checkPkgName, "", time.Hour), newDashObject(dashPkgName, "", ""), newEndpointHTTP(endpointPkgName, "", ""), newRuleObject(t, rulePkgName, "", endpointPkgName, ""), newTaskObject(taskPkgName, "", ""), newTelegrafObject(telegrafPkgName, "", ""), newVariableObject(variablePkgName, "", ""), } for _, obj := range objs { obj.AddAssociations(pkger.ObjectAssociation{ Kind: pkger.KindLabel, MetaName: labelPkgName, }) } objs = append(objs, newLabelObject(labelPkgName, "", "", "")) return newTemplate(objs...) }, applyOpts: []pkger.ApplyOptFn{ pkger.ApplyWithKindSkip(pkger.ActionSkipKind{ Kind: pkger.KindLabel, }), }, assertFn: func(t *testing.T, impact pkger.ImpactSummary) { summary := impact.Summary assert.Empty(t, summary.Labels, 0) assert.Empty(t, summary.LabelMappings) assert.Len(t, summary.Buckets, 1) assert.Empty(t, summary.Buckets[0].LabelAssociations) assert.Len(t, summary.Checks, 1) assert.Empty(t, summary.Checks[0].LabelAssociations) assert.Len(t, summary.Dashboards, 1) assert.Empty(t, summary.Dashboards[0].LabelAssociations) assert.Len(t, summary.NotificationEndpoints, 1) assert.Empty(t, summary.NotificationEndpoints[0].LabelAssociations) assert.Len(t, summary.NotificationRules, 1) assert.Empty(t, summary.NotificationRules[0].LabelAssociations) assert.Len(t, summary.Tasks, 1) assert.Empty(t, summary.Tasks[0].LabelAssociations) assert.Len(t, summary.TelegrafConfigs, 1) assert.Empty(t, summary.TelegrafConfigs[0].LabelAssociations) assert.Len(t, summary.Variables, 1) assert.Empty(t, summary.Variables[0].LabelAssociations) }, }, } for _, tt := range tests { fn := func(t *testing.T) { stack, cleanup := newStackFn(t, pkger.StackCreate{}) defer cleanup() impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID, append( tt.applyOpts, pkger.ApplyWithTemplate(tt.pkgFn(t)), pkger.ApplyWithStackID(stack.ID), )..., ) require.NoError(t, err) tt.assertFn(t, impact) } t.Run(tt.name, fn) } }) t.Run("exporting the existing state of stack resources as template", func(t *testing.T) { testStackApplyFn := func(t *testing.T) (pkger.Summary, pkger.Stack, func()) { t.Helper() stack, cleanup := newStackFn(t, pkger.StackCreate{}) defer func() { if t.Failed() { cleanup() } }() var ( initialBucketPkgName = "rucketeer-1" initialCheckPkgName = "checkers" initialDashPkgName = "dash-of-salt" initialEndpointPkgName = "endzo" initialLabelPkgName = "labelino" initialRulePkgName = "oh-doyle-rules" initialTaskPkgName = "tap" initialTelegrafPkgName = "teletype" initialVariablePkgName = "laces-out-dan" ) labelObj := newLabelObject(initialLabelPkgName, "label 1", "init desc", "#222eee") setAssociation := func(o pkger.Object) pkger.Object { o.AddAssociations(pkger.ObjectAssociation{ Kind: pkger.KindLabel, MetaName: labelObj.Name(), }) return o } initialPkg := newTemplate( setAssociation(newBucketObject(initialBucketPkgName, "display name", "init desc")), setAssociation(newCheckDeadmanObject(t, initialCheckPkgName, "check_0", time.Minute)), setAssociation(newDashObject(initialDashPkgName, "dash_0", "init desc")), setAssociation(newEndpointHTTP(initialEndpointPkgName, "endpoint_0", "init desc")), labelObj, setAssociation(newRuleObject(t, initialRulePkgName, "rule_0", initialEndpointPkgName, "init desc")), setAssociation(newTaskObject(initialTaskPkgName, "task_0", "init desc")), setAssociation(newTelegrafObject(initialTelegrafPkgName, "tele_0", "init desc")), setAssociation(newVariableObject(initialVariablePkgName, "var char", "init desc")), ) impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(initialPkg), pkger.ApplyWithStackID(stack.ID), ) require.NoError(t, err) summary := impact.Summary hasAssociation := func(t *testing.T, actual []pkger.SummaryLabel) { t.Helper() require.Len(t, actual, 1, "unexpected number of label mappings") assert.Equal(t, actual[0].MetaName, labelObj.Name()) } require.Len(t, summary.Buckets, 1) assert.NotZero(t, summary.Buckets[0].ID) assert.Equal(t, "display name", summary.Buckets[0].Name) assert.Equal(t, "init desc", summary.Buckets[0].Description) hasAssociation(t, summary.Buckets[0].LabelAssociations) require.Len(t, summary.Checks, 1) assert.NotZero(t, summary.Checks[0].Check.GetID()) assert.Equal(t, "check_0", summary.Checks[0].Check.GetName()) hasAssociation(t, summary.Checks[0].LabelAssociations) require.Len(t, summary.Dashboards, 1) assert.NotZero(t, summary.Dashboards[0].ID) assert.Equal(t, "dash_0", summary.Dashboards[0].Name) hasAssociation(t, summary.Dashboards[0].LabelAssociations) require.Len(t, summary.NotificationEndpoints, 1) assert.NotZero(t, summary.NotificationEndpoints[0].NotificationEndpoint.GetID()) assert.Equal(t, "endpoint_0", summary.NotificationEndpoints[0].NotificationEndpoint.GetName()) hasAssociation(t, summary.NotificationEndpoints[0].LabelAssociations) require.Len(t, summary.Labels, 1) assert.NotZero(t, summary.Labels[0].ID) assert.Equal(t, "label 1", summary.Labels[0].Name) assert.Equal(t, "init desc", summary.Labels[0].Properties.Description) assert.Equal(t, "#222eee", summary.Labels[0].Properties.Color) require.Len(t, summary.NotificationRules, 1) assert.NotZero(t, summary.NotificationRules[0].ID) assert.Equal(t, "rule_0", summary.NotificationRules[0].Name) assert.Equal(t, initialEndpointPkgName, summary.NotificationRules[0].EndpointMetaName) assert.Equal(t, "init desc", summary.NotificationRules[0].Description) hasAssociation(t, summary.NotificationRules[0].LabelAssociations) require.Len(t, summary.Tasks, 1) assert.NotZero(t, summary.Tasks[0].ID) assert.Equal(t, "task_0", summary.Tasks[0].Name) assert.Equal(t, "init desc", summary.Tasks[0].Description) hasAssociation(t, summary.Tasks[0].LabelAssociations) require.Len(t, summary.TelegrafConfigs, 1) assert.NotZero(t, summary.TelegrafConfigs[0].TelegrafConfig.ID) assert.Equal(t, "tele_0", summary.TelegrafConfigs[0].TelegrafConfig.Name) assert.Equal(t, "init desc", summary.TelegrafConfigs[0].TelegrafConfig.Description) hasAssociation(t, summary.TelegrafConfigs[0].LabelAssociations) require.Len(t, summary.Variables, 1) assert.NotZero(t, summary.Variables[0].ID) assert.Equal(t, "var char", summary.Variables[0].Name) assert.Equal(t, "init desc", summary.Variables[0].Description) hasAssociation(t, summary.Variables[0].LabelAssociations) // verify changes reflected in platform { actualBkt := resourceCheck.mustGetBucket(t, byName("display name")) assert.Equal(t, summary.Buckets[0].ID, pkger.SafeID(actualBkt.ID)) actualCheck := resourceCheck.mustGetCheck(t, byName("check_0")) assert.Equal(t, summary.Checks[0].Check.GetID(), actualCheck.GetID()) actualDash := resourceCheck.mustGetDashboard(t, byName("dash_0")) assert.Equal(t, summary.Dashboards[0].ID, pkger.SafeID(actualDash.ID)) actualEndpint := resourceCheck.mustGetEndpoint(t, byName("endpoint_0")) assert.Equal(t, summary.NotificationEndpoints[0].NotificationEndpoint.GetID(), actualEndpint.GetID()) actualLabel := resourceCheck.mustGetLabel(t, byName("label 1")) assert.Equal(t, summary.Labels[0].ID, pkger.SafeID(actualLabel.ID)) actualRule := resourceCheck.mustGetRule(t, byName("rule_0")) assert.Equal(t, summary.NotificationRules[0].ID, pkger.SafeID(actualRule.GetID())) actualTask := resourceCheck.mustGetTask(t, byName("task_0")) assert.Equal(t, summary.Tasks[0].ID, pkger.SafeID(actualTask.ID)) actualTele := resourceCheck.mustGetTelegrafConfig(t, byName("tele_0")) assert.Equal(t, summary.TelegrafConfigs[0].TelegrafConfig.ID, actualTele.ID) actualVar := resourceCheck.mustGetVariable(t, byName("var char")) assert.Equal(t, summary.Variables[0].ID, pkger.SafeID(actualVar.ID)) } return summary, stack, cleanup } t.Run("should be return pkg matching source pkg when all resources are unchanged", func(t *testing.T) { initialSum, stack, cleanup := testStackApplyFn(t) defer cleanup() exportedTemplate, err := svc.Export(ctx, pkger.ExportWithStackID(stack.ID)) require.NoError(t, err) hasAssociation := func(t *testing.T, actual []pkger.SummaryLabel) { t.Helper() assert.Len(t, actual, 1, "unexpected number of label mappings") if len(actual) != 1 { return } assert.Equal(t, actual[0].MetaName, initialSum.Labels[0].MetaName) } sum := exportedTemplate.Summary() require.Len(t, sum.Buckets, 1, "missing required buckets") assert.Equal(t, initialSum.Buckets[0].MetaName, sum.Buckets[0].MetaName) assert.Equal(t, initialSum.Buckets[0].Name, sum.Buckets[0].Name) hasAssociation(t, sum.Buckets[0].LabelAssociations) require.Len(t, sum.Checks, 1, "missing required checks") assert.Equal(t, initialSum.Checks[0].MetaName, sum.Checks[0].MetaName) assert.Equal(t, initialSum.Checks[0].Check.GetName(), sum.Checks[0].Check.GetName()) hasAssociation(t, sum.Checks[0].LabelAssociations) require.Len(t, sum.Dashboards, 1, "missing required dashboards") assert.Equal(t, initialSum.Dashboards[0].MetaName, sum.Dashboards[0].MetaName) assert.Equal(t, initialSum.Dashboards[0].Name, sum.Dashboards[0].Name) hasAssociation(t, sum.Dashboards[0].LabelAssociations) require.Len(t, sum.Labels, 1, "missing required labels") assert.Equal(t, initialSum.Labels[0].MetaName, sum.Labels[0].MetaName) assert.Equal(t, initialSum.Labels[0].Name, sum.Labels[0].Name) require.Len(t, sum.NotificationRules, 1, "missing required rules") assert.Equal(t, initialSum.NotificationRules[0].MetaName, sum.NotificationRules[0].MetaName) assert.Equal(t, initialSum.NotificationRules[0].Name, sum.NotificationRules[0].Name) assert.Equal(t, initialSum.NotificationRules[0].EndpointMetaName, sum.NotificationRules[0].EndpointMetaName) assert.Equal(t, initialSum.NotificationRules[0].EndpointType, sum.NotificationRules[0].EndpointType) hasAssociation(t, sum.NotificationRules[0].LabelAssociations) require.Len(t, sum.Tasks, 1, "missing required tasks") assert.Equal(t, initialSum.Tasks[0].MetaName, sum.Tasks[0].MetaName) assert.Equal(t, initialSum.Tasks[0].Name, sum.Tasks[0].Name) hasAssociation(t, sum.Tasks[0].LabelAssociations) require.Len(t, sum.TelegrafConfigs, 1, "missing required telegraf configs") assert.Equal(t, initialSum.TelegrafConfigs[0].MetaName, sum.TelegrafConfigs[0].MetaName) assert.Equal(t, initialSum.TelegrafConfigs[0].TelegrafConfig.Name, sum.TelegrafConfigs[0].TelegrafConfig.Name) hasAssociation(t, sum.TelegrafConfigs[0].LabelAssociations) require.Len(t, sum.Variables, 1, "missing required variables") assert.Equal(t, initialSum.Variables[0].MetaName, sum.Variables[0].MetaName) assert.Equal(t, initialSum.Variables[0].Name, sum.Variables[0].Name) hasAssociation(t, sum.Variables[0].LabelAssociations) }) t.Run("should return all associated dashboards in full", func(t *testing.T) { dash := influxdb.Dashboard{ Name: "dasher", Cells: []*influxdb.Cell{ { ID: 1, CellProperty: influxdb.CellProperty{ H: 1, W: 2, }, View: &influxdb.View{ ViewContents: influxdb.ViewContents{ Name: "name", }, Properties: influxdb.MarkdownViewProperties{ Type: influxdb.ViewPropertyTypeMarkdown, Note: "the markdown", }, }, }, }, } pkg := newTemplate(pkger.DashboardToObject("", dash)) impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(pkg)) require.NoError(t, err) defer deleteStackFn(t, impact.StackID) require.Len(t, impact.Summary.Dashboards, 1) exportedTemplate, err := svc.Export(ctx, pkger.ExportWithStackID(impact.StackID)) require.NoError(t, err) summary := exportedTemplate.Summary() require.Len(t, summary.Dashboards, 1) exportedDash := summary.Dashboards[0] require.Len(t, exportedDash.Charts, 1) expectedChartProps := dash.Cells[0].View.Properties assert.Equal(t, expectedChartProps, exportedDash.Charts[0].Properties) }) t.Run("when label associations have changed", func(t *testing.T) { newLabelAssociationTestFn := func(t *testing.T) (pkger.Stack, pkger.Summary, func()) { t.Helper() stack, cleanup := newStackFn(t, pkger.StackCreate{}) defer func() { if t.Failed() { cleanup() } }() labelObj := newLabelObject("test-label", "", "", "") bktObj := newBucketObject("test-bucket", "", "") bktObj.AddAssociations(pkger.ObjectAssociation{ Kind: pkger.KindLabel, MetaName: labelObj.Name(), }) pkg := newTemplate(bktObj, labelObj) impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(pkg), pkger.ApplyWithStackID(stack.ID)) require.NoError(t, err) require.Len(t, impact.Summary.Labels, 1) require.Len(t, impact.Summary.Buckets, 1) require.Len(t, impact.Summary.Buckets[0].LabelAssociations, 1) return stack, impact.Summary, cleanup } t.Run("should not export associations removed in platform", func(t *testing.T) { stack, initialSummary, cleanup := newLabelAssociationTestFn(t) defer cleanup() err := l.LabelService(t).DeleteLabelMapping(ctx, &influxdb.LabelMapping{ LabelID: platform.ID(initialSummary.Labels[0].ID), ResourceID: platform.ID(initialSummary.Buckets[0].ID), ResourceType: influxdb.BucketsResourceType, }) require.NoError(t, err) exportedTemplate, err := svc.Export(ctx, pkger.ExportWithStackID(stack.ID)) require.NoError(t, err) exportedSum := exportedTemplate.Summary() require.Len(t, exportedSum.Labels, 1) require.Len(t, exportedSum.Buckets, 1) require.Empty(t, exportedSum.Buckets[0].LabelAssociations, "received unexpected label associations") }) t.Run("should export associations platform resources not associated with stack", func(t *testing.T) { stack, initialSummary, cleanup := newLabelAssociationTestFn(t) defer cleanup() newLabel := &influxdb.Label{ OrgID: l.Org.ID, Name: "test-label-2", } require.NoError(t, l.LabelService(t).CreateLabel(ctx, newLabel)) defer resourceCheck.mustDeleteLabel(t, newLabel.ID) err := l.LabelService(t).CreateLabelMapping(ctx, &influxdb.LabelMapping{ LabelID: newLabel.ID, ResourceID: platform.ID(initialSummary.Buckets[0].ID), ResourceType: influxdb.BucketsResourceType, }) require.NoError(t, err) exportedTemplate, err := svc.Export(ctx, pkger.ExportWithStackID(stack.ID)) require.NoError(t, err) exportedSum := exportedTemplate.Summary() assert.Len(t, exportedSum.Labels, 2) require.Len(t, exportedSum.Buckets, 1) require.Len(t, exportedSum.Buckets[0].LabelAssociations, 2) expectedAssociation := initialSummary.Labels[0] expectedAssociation.ID, expectedAssociation.OrgID = 0, 0 assert.Contains(t, exportedSum.Buckets[0].LabelAssociations, expectedAssociation) }) }) }) }) t.Run("errors incurred during application of template rolls back to state before template", func(t *testing.T) { stacks, err := svc.ListStacks(ctx, l.Org.ID, pkger.ListFilter{}) require.NoError(t, err) require.Empty(t, stacks) svc := pkger.NewService( pkger.WithBucketSVC(l.BucketService(t)), pkger.WithDashboardSVC(l.DashboardService(t)), pkger.WithCheckSVC(l.CheckService()), pkger.WithLabelSVC(&fakeLabelSVC{ LabelService: l.LabelService(t), createKillCount: 2, // hits error on 3rd attempt at creating a mapping }), pkger.WithNotificationEndpointSVC(l.NotificationEndpointService(t)), pkger.WithNotificationRuleSVC(l.NotificationRuleService(t)), pkger.WithOrganizationService(l.OrganizationService()), pkger.WithStore(pkger.NewStoreKV(l.kvStore)), pkger.WithTaskSVC(l.TaskServiceKV(t)), pkger.WithTelegrafSVC(l.TelegrafService(t)), pkger.WithVariableSVC(l.VariableService(t)), ) _, err = svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(newCompletePkg(t))) require.Error(t, err) bkts, _, err := l.BucketService(t).FindBuckets(ctx, influxdb.BucketFilter{OrganizationID: &l.Org.ID}) require.NoError(t, err) for _, b := range bkts { if influxdb.BucketTypeSystem == b.Type { continue } // verify system buckets and org bucket are the buckets available assert.Equal(t, l.Bucket.Name, b.Name) } labels, err := l.LabelService(t).FindLabels(ctx, influxdb.LabelFilter{OrgID: &l.Org.ID}) require.NoError(t, err) assert.Empty(t, labels) dashs, _, err := l.DashboardService(t).FindDashboards(ctx, influxdb.DashboardFilter{ OrganizationID: &l.Org.ID, }, influxdb.DefaultDashboardFindOptions) require.NoError(t, err) assert.Empty(t, dashs) endpoints, _, err := l.NotificationEndpointService(t).FindNotificationEndpoints(ctx, influxdb.NotificationEndpointFilter{ OrgID: &l.Org.ID, }) require.NoError(t, err) assert.Empty(t, endpoints) rules, _, err := l.NotificationRuleService(t).FindNotificationRules(ctx, influxdb.NotificationRuleFilter{ OrgID: &l.Org.ID, }) require.NoError(t, err) assert.Empty(t, rules) tasks, _, err := l.TaskServiceKV(t).FindTasks(ctx, taskmodel.TaskFilter{ OrganizationID: &l.Org.ID, }) require.NoError(t, err) assert.Empty(t, tasks) teles, _, err := l.TelegrafService(t).FindTelegrafConfigs(ctx, influxdb.TelegrafConfigFilter{ OrgID: &l.Org.ID, }) require.NoError(t, err) assert.Empty(t, teles) vars, err := l.VariableService(t).FindVariables(ctx, influxdb.VariableFilter{OrganizationID: &l.Org.ID}) require.NoError(t, err) assert.Empty(t, vars) afterStacks, err := svc.ListStacks(ctx, l.Org.ID, pkger.ListFilter{}) require.NoError(t, err) require.Empty(t, afterStacks) }) hasLabelAssociations := func(t *testing.T, associations []pkger.SummaryLabel, numAss int, expectedNames ...string) { t.Helper() hasAss := func(t *testing.T, expected string) { t.Helper() for _, ass := range associations { if ass.Name == expected { return } } require.FailNow(t, "did not find expected association: "+expected) } require.Len(t, associations, numAss) for _, expected := range expectedNames { hasAss(t, expected) } } t.Run("dry run", func(t *testing.T) { t.Run("template with no existing resources", func(t *testing.T) { impact, err := svc.DryRun(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(newCompletePkg(t))) require.NoError(t, err) sum, diff := impact.Summary, impact.Diff require.Len(t, diff.Buckets, 1) assert.True(t, diff.Buckets[0].IsNew()) require.Len(t, diff.Checks, 2) for _, ch := range diff.Checks { assert.True(t, ch.IsNew()) } require.Len(t, diff.Labels, 2) assert.True(t, diff.Labels[0].IsNew()) assert.True(t, diff.Labels[1].IsNew()) require.Len(t, diff.Variables, 1) assert.True(t, diff.Variables[0].IsNew()) require.Len(t, diff.NotificationRules, 1) // the pkg being run here has a relationship with the rule and the endpoint within the pkg. assert.Equal(t, "http", diff.NotificationRules[0].New.EndpointType) require.Len(t, diff.Dashboards, 1) require.Len(t, diff.NotificationEndpoints, 1) require.Len(t, diff.Tasks, 1) require.Len(t, diff.Telegrafs, 1) labels := sum.Labels require.Len(t, labels, 2) assert.Equal(t, "label-1", labels[0].Name) assert.Equal(t, "the 2nd label", labels[1].Name) bkts := sum.Buckets require.Len(t, bkts, 1) assert.Equal(t, "rucketeer", bkts[0].Name) hasLabelAssociations(t, bkts[0].LabelAssociations, 2, "label-1", "the 2nd label") checks := sum.Checks require.Len(t, checks, 2) assert.Equal(t, "check 0 name", checks[0].Check.GetName()) hasLabelAssociations(t, checks[0].LabelAssociations, 1, "label-1") assert.Equal(t, "check-1", checks[1].Check.GetName()) hasLabelAssociations(t, checks[1].LabelAssociations, 1, "label-1") dashs := sum.Dashboards require.Len(t, dashs, 1) assert.Equal(t, "dash_1", dashs[0].Name) assert.Equal(t, "desc1", dashs[0].Description) hasLabelAssociations(t, dashs[0].LabelAssociations, 2, "label-1", "the 2nd label") endpoints := sum.NotificationEndpoints require.Len(t, endpoints, 1) assert.Equal(t, "no auth endpoint", endpoints[0].NotificationEndpoint.GetName()) assert.Equal(t, "http none auth desc", endpoints[0].NotificationEndpoint.GetDescription()) hasLabelAssociations(t, endpoints[0].LabelAssociations, 1, "label-1") require.Len(t, sum.Tasks, 1) task := sum.Tasks[0] assert.Equal(t, "task_1", task.Name) assert.Equal(t, "desc_1", task.Description) assert.Equal(t, "15 * * * *", task.Cron) hasLabelAssociations(t, task.LabelAssociations, 1, "label-1") teles := sum.TelegrafConfigs require.Len(t, teles, 1) assert.Equal(t, "first tele config", teles[0].TelegrafConfig.Name) assert.Equal(t, "desc", teles[0].TelegrafConfig.Description) hasLabelAssociations(t, teles[0].LabelAssociations, 1, "label-1") vars := sum.Variables require.Len(t, vars, 1) assert.Equal(t, "query var", vars[0].Name) hasLabelAssociations(t, vars[0].LabelAssociations, 1, "label-1") varArgs := vars[0].Arguments require.NotNil(t, varArgs) assert.Equal(t, "query", varArgs.Type) assert.Equal(t, influxdb.VariableQueryValues{ Query: "buckets() |> filter(fn: (r) => r.name !~ /^_/) |> rename(columns: {name: \"_value\"}) |> keep(columns: [\"_value\"])", Language: "flux", }, varArgs.Values) }) t.Run("template with env ref", func(t *testing.T) { pkgStr := fmt.Sprintf(` apiVersion: %[1]s kind: Label metadata: name: envRef: key: label-1-name-ref spec: --- apiVersion: %[1]s kind: Bucket metadata: name: envRef: key: bkt-1-name-ref spec: associations: - kind: Label name: envRef: key: label-1-name-ref `, pkger.APIVersion) pkg, err := pkger.Parse(pkger.EncodingYAML, pkger.FromString(pkgStr)) require.NoError(t, err) impact, err := svc.DryRun(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(pkg), pkger.ApplyWithEnvRefs(map[string]interface{}{ "bkt-1-name-ref": "new-bkt-name", "label-1-name-ref": "new-label-name", }), ) require.NoError(t, err) sum := impact.Summary require.Len(t, sum.Buckets, 1) assert.Equal(t, "new-bkt-name", sum.Buckets[0].Name) require.Len(t, sum.Labels, 1) assert.Equal(t, "new-label-name", sum.Labels[0].Name) }) t.Run("bucket", func(t *testing.T) { template := newTemplate( newBucketObject("foo", "", ""), ) impact, err := svc.DryRun(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(template)) require.NoError(t, err) require.Len(t, impact.Diff.Buckets, 1) assert.Equal(t, pkger.KindBucket, impact.Diff.Buckets[0].Kind) }) t.Run("check", func(t *testing.T) { template := newTemplate( newCheckDeadmanObject(t, "check1", "", time.Hour), newCheckThresholdObject(t, "check2", "", time.Hour), ) impact, err := svc.DryRun(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(template)) require.NoError(t, err) require.Len(t, impact.Diff.Checks, 2) assert.Equal(t, pkger.KindCheckDeadman, impact.Diff.Checks[0].Kind) assert.Equal(t, pkger.KindCheckThreshold, impact.Diff.Checks[1].Kind) }) t.Run("dashboards", func(t *testing.T) { newQuery := func() influxdb.DashboardQuery { return influxdb.DashboardQuery{ BuilderConfig: influxdb.BuilderConfig{ Buckets: []string{}, Tags: []struct { Key string `json:"key"` Values []string `json:"values"` AggregateFunctionType string `json:"aggregateFunctionType"` }{}, Functions: []struct { Name string `json:"name"` }{}, }, Text: "from(v.bucket) |> count()", EditMode: "advanced", } } newAxes := func() map[string]influxdb.Axis { return map[string]influxdb.Axis{ "x": { Bounds: []string{}, Label: "labx", Prefix: "pre", Suffix: "suf", Base: "base", Scale: "linear", }, "y": { Bounds: []string{}, Label: "laby", Prefix: "pre", Suffix: "suf", Base: "base", Scale: "linear", }, } } newColors := func(types ...string) []influxdb.ViewColor { var out []influxdb.ViewColor for _, t := range types { out = append(out, influxdb.ViewColor{ Type: t, Hex: time.Now().Format(time.RFC3339), Name: time.Now().Format(time.RFC3339), Value: float64(time.Now().Unix()), }) } return out } tests := []struct { name string props influxdb.ViewProperties }{ { name: "gauge", props: influxdb.GaugeViewProperties{ Type: influxdb.ViewPropertyTypeGauge, DecimalPlaces: influxdb.DecimalPlaces{IsEnforced: true, Digits: 1}, Note: "a note", Prefix: "pre", TickPrefix: "true", Suffix: "suf", TickSuffix: "false", Queries: []influxdb.DashboardQuery{newQuery()}, ShowNoteWhenEmpty: true, ViewColors: newColors("min", "max", "threshold"), }, }, { name: "heatmap", props: influxdb.HeatmapViewProperties{ Type: influxdb.ViewPropertyTypeHeatMap, Note: "a note", Queries: []influxdb.DashboardQuery{newQuery()}, ShowNoteWhenEmpty: true, ViewColors: []string{"#8F8AF4", "#8F8AF4", "#8F8AF4"}, XColumn: "x", YColumn: "y", XDomain: []float64{0, 10}, YDomain: []float64{0, 100}, XAxisLabel: "x_label", XPrefix: "x_prefix", XSuffix: "x_suffix", YAxisLabel: "y_label", YPrefix: "y_prefix", YSuffix: "y_suffix", BinSize: 10, TimeFormat: "", }, }, { name: "histogram", props: influxdb.HistogramViewProperties{ Type: influxdb.ViewPropertyTypeHistogram, Note: "a note", Queries: []influxdb.DashboardQuery{newQuery()}, ShowNoteWhenEmpty: true, ViewColors: []influxdb.ViewColor{{Type: "scale", Hex: "#8F8AF4", Value: 0}, {Type: "scale", Hex: "#8F8AF4", Value: 0}, {Type: "scale", Hex: "#8F8AF4", Value: 0}}, FillColumns: []string{"a", "b"}, XColumn: "_value", XDomain: []float64{0, 10}, XAxisLabel: "x_label", BinCount: 30, Position: "stacked", }, }, { name: "markdown", props: influxdb.MarkdownViewProperties{ Type: influxdb.ViewPropertyTypeMarkdown, Note: "the note is here with **markdown**", }, }, { name: "scatter", props: influxdb.ScatterViewProperties{ Type: influxdb.ViewPropertyTypeScatter, Note: "a note", Queries: []influxdb.DashboardQuery{newQuery()}, ShowNoteWhenEmpty: true, ViewColors: []string{"#8F8AF4", "#8F8AF4", "#8F8AF4"}, XColumn: "x", YColumn: "y", XDomain: []float64{0, 10}, YDomain: []float64{0, 100}, XAxisLabel: "x_label", XPrefix: "x_prefix", XSuffix: "x_suffix", YAxisLabel: "y_label", YPrefix: "y_prefix", YSuffix: "y_suffix", TimeFormat: "", }, }, { name: "single stat", props: influxdb.SingleStatViewProperties{ Type: influxdb.ViewPropertyTypeSingleStat, DecimalPlaces: influxdb.DecimalPlaces{IsEnforced: true, Digits: 1}, Note: "a note", Queries: []influxdb.DashboardQuery{newQuery()}, Prefix: "pre", TickPrefix: "false", ShowNoteWhenEmpty: true, Suffix: "suf", TickSuffix: "true", ViewColors: []influxdb.ViewColor{{Type: "text", Hex: "red"}}, }, }, { name: "single stat plus line", props: influxdb.LinePlusSingleStatProperties{ Type: influxdb.ViewPropertyTypeSingleStatPlusLine, Axes: newAxes(), DecimalPlaces: influxdb.DecimalPlaces{IsEnforced: true, Digits: 1}, Note: "a note", Prefix: "pre", Suffix: "suf", Queries: []influxdb.DashboardQuery{newQuery()}, ShadeBelow: true, HoverDimension: "y", ShowNoteWhenEmpty: true, ViewColors: []influxdb.ViewColor{{Type: "text", Hex: "red"}}, XColumn: "x", YColumn: "y", Position: "stacked", }, }, { name: "table", props: influxdb.TableViewProperties{ Type: influxdb.ViewPropertyTypeTable, Note: "a note", ShowNoteWhenEmpty: true, Queries: []influxdb.DashboardQuery{newQuery()}, ViewColors: []influxdb.ViewColor{{Type: "scale", Hex: "#8F8AF4", Value: 0}, {Type: "scale", Hex: "#8F8AF4", Value: 0}, {Type: "scale", Hex: "#8F8AF4", Value: 0}}, TableOptions: influxdb.TableOptions{ VerticalTimeAxis: true, SortBy: influxdb.RenamableField{ InternalName: "_time", }, Wrapping: "truncate", FixFirstColumn: true, }, FieldOptions: []influxdb.RenamableField{ { InternalName: "_time", DisplayName: "time (ms)", Visible: true, }, }, TimeFormat: "YYYY:MM:DD", DecimalPlaces: influxdb.DecimalPlaces{ IsEnforced: true, Digits: 1, }, }, }, { name: "xy", props: influxdb.XYViewProperties{ Type: influxdb.ViewPropertyTypeXY, Axes: newAxes(), Geom: "step", Note: "a note", Queries: []influxdb.DashboardQuery{newQuery()}, ShadeBelow: true, HoverDimension: "y", ShowNoteWhenEmpty: true, ViewColors: []influxdb.ViewColor{{Type: "text", Hex: "red"}}, XColumn: "x", YColumn: "y", Position: "overlaid", TimeFormat: "", }, }, } for _, tt := range tests { fn := func(t *testing.T) { obj := pkger.DashboardToObject("", influxdb.Dashboard{ OrganizationID: l.Org.ID, Name: tt.name, Cells: []*influxdb.Cell{ { CellProperty: influxdb.CellProperty{ X: 2, Y: 2, H: 5, W: 5, }, View: &influxdb.View{Properties: tt.props}, }, }, }) template := newTemplate(obj) impact, err := svc.DryRun(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(template)) require.NoError(t, err) diff := impact.Diff.Dashboards require.Len(t, diff, 1) actual := diff[0] assert.Equal(t, pkger.KindDashboard, actual.Kind) assert.Equal(t, tt.name, actual.New.Name) charts := actual.New.Charts require.Len(t, charts, 1) require.NotNil(t, charts[0].Properties) assert.Equal(t, tt.props, charts[0].Properties) } t.Run(tt.name, fn) } }) t.Run("labels", func(t *testing.T) { template := newTemplate( newLabelObject("label", "", "", ""), ) impact, err := svc.DryRun(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(template)) require.NoError(t, err) require.Len(t, impact.Diff.Labels, 1) assert.Equal(t, pkger.KindLabel, impact.Diff.Labels[0].Kind) }) t.Run("endpoints", func(t *testing.T) { template := newTemplate( newEndpointHTTP("http", "", ""), newEndpointPagerDuty("pager", "", ""), newEndpointSlack("slack", "", ""), ) impact, err := svc.DryRun(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(template)) require.NoError(t, err) require.Len(t, impact.Diff.NotificationEndpoints, 3) assert.Equal(t, pkger.KindNotificationEndpointHTTP, impact.Diff.NotificationEndpoints[0].Kind) assert.Equal(t, pkger.KindNotificationEndpointPagerDuty, impact.Diff.NotificationEndpoints[1].Kind) assert.Equal(t, pkger.KindNotificationEndpointSlack, impact.Diff.NotificationEndpoints[2].Kind) }) t.Run("rules", func(t *testing.T) { template := newTemplate( newEndpointHTTP("http", "", ""), newRuleObject(t, "rule", "", "http", ""), ) impact, err := svc.DryRun(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(template)) require.NoError(t, err) require.Len(t, impact.Diff.NotificationRules, 1) assert.Equal(t, pkger.KindNotificationRule, impact.Diff.NotificationRules[0].Kind) }) t.Run("tasks", func(t *testing.T) { template := newTemplate( newTaskObject("task", "", ""), ) impact, err := svc.DryRun(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(template)) require.NoError(t, err) require.Len(t, impact.Diff.Tasks, 1) assert.Equal(t, pkger.KindTask, impact.Diff.Tasks[0].Kind) }) t.Run("telegraf configs", func(t *testing.T) { template := newTemplate( newTelegrafObject("tele", "", ""), ) impact, err := svc.DryRun(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(template)) require.NoError(t, err) require.Len(t, impact.Diff.Telegrafs, 1) assert.Equal(t, pkger.KindTelegraf, impact.Diff.Telegrafs[0].Kind) }) t.Run("variables", func(t *testing.T) { template := newTemplate( newVariableObject("var", "", ""), ) impact, err := svc.DryRun(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(template)) require.NoError(t, err) require.Len(t, impact.Diff.Variables, 1) assert.Equal(t, pkger.KindVariable, impact.Diff.Variables[0].Kind) }) }) t.Run("apply a template of all new resources", func(t *testing.T) { // this initial test is also setup for the sub tests impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(newCompletePkg(t))) require.NoError(t, err) defer deleteStackFn(t, impact.StackID) assert.NotZero(t, impact.StackID) sum1 := impact.Summary labels := sum1.Labels require.Len(t, labels, 2) assert.Equal(t, pkger.KindLabel, labels[0].Kind) assert.NotZero(t, labels[0].ID) assert.Equal(t, "label-1", labels[0].Name) assert.Equal(t, "the 2nd label", labels[1].Name) bkts := sum1.Buckets require.Len(t, bkts, 1) assert.Equal(t, pkger.KindBucket, bkts[0].Kind) assert.NotZero(t, bkts[0].ID) assert.NotEmpty(t, bkts[0].MetaName) assert.Equal(t, "rucketeer", bkts[0].Name) hasLabelAssociations(t, bkts[0].LabelAssociations, 2, "label-1", "the 2nd label") checks := sum1.Checks require.Len(t, checks, 2) assert.Equal(t, "check 0 name", checks[0].Check.GetName()) assert.Equal(t, pkger.KindCheckThreshold, checks[0].Kind) hasLabelAssociations(t, checks[0].LabelAssociations, 1, "label-1") assert.Equal(t, "check-1", checks[1].Check.GetName()) assert.Equal(t, pkger.KindCheckDeadman, checks[1].Kind) hasLabelAssociations(t, checks[1].LabelAssociations, 1, "label-1") for _, ch := range checks { assert.NotZero(t, ch.Check.GetID()) } dashs := sum1.Dashboards require.Len(t, dashs, 1) assert.Equal(t, pkger.KindDashboard, dashs[0].Kind) assert.NotZero(t, dashs[0].ID) assert.NotEmpty(t, dashs[0].Name) assert.Equal(t, "dash_1", dashs[0].Name) assert.Equal(t, "desc1", dashs[0].Description) hasLabelAssociations(t, dashs[0].LabelAssociations, 2, "label-1", "the 2nd label") require.Len(t, dashs[0].Charts, 1) assert.Equal(t, influxdb.ViewPropertyTypeSingleStat, dashs[0].Charts[0].Properties.GetType()) endpoints := sum1.NotificationEndpoints require.Len(t, endpoints, 1) assert.Equal(t, pkger.KindNotificationEndpointHTTP, endpoints[0].Kind) assert.NotZero(t, endpoints[0].NotificationEndpoint.GetID()) assert.Equal(t, "no auth endpoint", endpoints[0].NotificationEndpoint.GetName()) assert.Equal(t, "http none auth desc", endpoints[0].NotificationEndpoint.GetDescription()) assert.Equal(t, taskmodel.TaskStatusInactive, string(endpoints[0].NotificationEndpoint.GetStatus())) hasLabelAssociations(t, endpoints[0].LabelAssociations, 1, "label-1") require.Len(t, sum1.NotificationRules, 1) rule := sum1.NotificationRules[0] assert.Equal(t, pkger.KindNotificationRule, rule.Kind) assert.NotZero(t, rule.ID) assert.Equal(t, "rule_0", rule.Name) assert.Equal(t, pkger.SafeID(endpoints[0].NotificationEndpoint.GetID()), rule.EndpointID) assert.Equal(t, "http-none-auth-notification-endpoint", rule.EndpointMetaName) assert.Equalf(t, "http", rule.EndpointType, "rule: %+v", rule) require.Len(t, sum1.Tasks, 1) task := sum1.Tasks[0] assert.Equal(t, pkger.KindTask, task.Kind) assert.NotZero(t, task.ID) assert.Equal(t, "task_1", task.Name) assert.Equal(t, "desc_1", task.Description) teles := sum1.TelegrafConfigs require.Len(t, teles, 1) assert.Equal(t, pkger.KindTelegraf, teles[0].Kind) assert.NotZero(t, teles[0].TelegrafConfig.ID) assert.Equal(t, l.Org.ID, teles[0].TelegrafConfig.OrgID) assert.Equal(t, "first tele config", teles[0].TelegrafConfig.Name) assert.Equal(t, "desc", teles[0].TelegrafConfig.Description) assert.Equal(t, telegrafCfg, teles[0].TelegrafConfig.Config) vars := sum1.Variables require.Len(t, vars, 1) assert.Equal(t, pkger.KindVariable, vars[0].Kind) assert.NotZero(t, vars[0].ID) assert.Equal(t, "query var", vars[0].Name) assert.Equal(t, []string{"rucketeer"}, vars[0].Selected) hasLabelAssociations(t, vars[0].LabelAssociations, 1, "label-1") varArgs := vars[0].Arguments require.NotNil(t, varArgs) assert.Equal(t, "query", varArgs.Type) assert.Equal(t, influxdb.VariableQueryValues{ Query: "buckets() |> filter(fn: (r) => r.name !~ /^_/) |> rename(columns: {name: \"_value\"}) |> keep(columns: [\"_value\"])", Language: "flux", }, varArgs.Values) platformVar := resourceCheck.mustGetVariable(t, byID(platform.ID(vars[0].ID))) assert.Equal(t, []string{"rucketeer"}, platformVar.Selected) newSumMapping := func(id pkger.SafeID, pkgName, name string, rt influxdb.ResourceType) pkger.SummaryLabelMapping { return pkger.SummaryLabelMapping{ Status: pkger.StateStatusNew, ResourceID: id, ResourceType: rt, ResourceMetaName: pkgName, ResourceName: name, LabelMetaName: labels[0].MetaName, LabelName: labels[0].Name, LabelID: labels[0].ID, } } mappings := sum1.LabelMappings mappingsContain := func(t *testing.T, id pkger.SafeID, pkgName, name string, resourceType influxdb.ResourceType) { t.Helper() assert.Contains(t, mappings, newSumMapping(id, pkgName, name, resourceType)) } require.Len(t, mappings, 11) mappingsContain(t, bkts[0].ID, bkts[0].MetaName, bkts[0].Name, influxdb.BucketsResourceType) mappingsContain(t, pkger.SafeID(checks[0].Check.GetID()), checks[0].MetaName, checks[0].Check.GetName(), influxdb.ChecksResourceType) mappingsContain(t, dashs[0].ID, dashs[0].MetaName, dashs[0].Name, influxdb.DashboardsResourceType) mappingsContain(t, pkger.SafeID(endpoints[0].NotificationEndpoint.GetID()), endpoints[0].MetaName, endpoints[0].NotificationEndpoint.GetName(), influxdb.NotificationEndpointResourceType) mappingsContain(t, rule.ID, rule.MetaName, rule.Name, influxdb.NotificationRuleResourceType) mappingsContain(t, task.ID, task.MetaName, task.Name, influxdb.TasksResourceType) mappingsContain(t, pkger.SafeID(teles[0].TelegrafConfig.ID), teles[0].MetaName, teles[0].TelegrafConfig.Name, influxdb.TelegrafsResourceType) mappingsContain(t, vars[0].ID, vars[0].MetaName, vars[0].Name, influxdb.VariablesResourceType) var ( // used in dependent subtests sum1Bkts = sum1.Buckets sum1Checks = sum1.Checks sum1Dashs = sum1.Dashboards sum1Endpoints = sum1.NotificationEndpoints sum1Labels = sum1.Labels sum1Rules = sum1.NotificationRules sum1Tasks = sum1.Tasks sum1Teles = sum1.TelegrafConfigs sum1Vars = sum1.Variables ) t.Run("exporting all resources for an org", func(t *testing.T) { t.Run("getting everything", func(t *testing.T) { newPkg, err := svc.Export(ctx, pkger.ExportWithAllOrgResources( pkger.ExportByOrgIDOpt{ OrgID: l.Org.ID, }, )) require.NoError(t, err) sum := newPkg.Summary() labels := sum.Labels require.Len(t, labels, 2) sortLabels(labels) assert.Equal(t, "label-1", labels[0].Name) assert.Equal(t, "the 2nd label", labels[1].Name) bkts := sum.Buckets require.Len(t, bkts, 1) assert.NotEmpty(t, bkts[0].MetaName) assert.Equal(t, "rucketeer", bkts[0].Name) hasLabelAssociations(t, bkts[0].LabelAssociations, 2, "label-1", "the 2nd label") checks := sum.Checks require.Len(t, checks, 2) sortChecks(checks) assert.Equal(t, "check 0 name", checks[0].Check.GetName()) hasLabelAssociations(t, checks[0].LabelAssociations, 1, "label-1") assert.Equal(t, "check-1", checks[1].Check.GetName()) hasLabelAssociations(t, checks[1].LabelAssociations, 1, "label-1") dashs := sum.Dashboards require.Len(t, dashs, 1) assert.NotEmpty(t, dashs[0].Name) assert.Equal(t, "dash_1", dashs[0].Name) assert.Equal(t, "desc1", dashs[0].Description) hasLabelAssociations(t, dashs[0].LabelAssociations, 2, "label-1", "the 2nd label") require.Len(t, dashs[0].Charts, 1) assert.Equal(t, influxdb.ViewPropertyTypeSingleStat, dashs[0].Charts[0].Properties.GetType()) endpoints := sum.NotificationEndpoints require.Len(t, endpoints, 1) assert.Equal(t, "no auth endpoint", endpoints[0].NotificationEndpoint.GetName()) assert.Equal(t, "http none auth desc", endpoints[0].NotificationEndpoint.GetDescription()) assert.Equal(t, taskmodel.TaskStatusInactive, string(endpoints[0].NotificationEndpoint.GetStatus())) hasLabelAssociations(t, endpoints[0].LabelAssociations, 1, "label-1") require.Len(t, sum.NotificationRules, 1) rule := sum.NotificationRules[0] assert.Equal(t, "rule_0", rule.Name) assert.Equal(t, pkger.SafeID(endpoints[0].NotificationEndpoint.GetID()), rule.EndpointID) assert.NotEmpty(t, rule.EndpointMetaName) require.Len(t, sum.Tasks, 1) task := sum.Tasks[0] assert.Equal(t, "task_1", task.Name) assert.Equal(t, "desc_1", task.Description) teles := sum.TelegrafConfigs require.Len(t, teles, 1) assert.Equal(t, "first tele config", teles[0].TelegrafConfig.Name) assert.Equal(t, "desc", teles[0].TelegrafConfig.Description) assert.Equal(t, telegrafCfg, teles[0].TelegrafConfig.Config) vars := sum.Variables require.Len(t, vars, 1) assert.Equal(t, "query var", vars[0].Name) hasLabelAssociations(t, vars[0].LabelAssociations, 1, "label-1") varArgs := vars[0].Arguments require.NotNil(t, varArgs) assert.Equal(t, "query", varArgs.Type) assert.Equal(t, influxdb.VariableQueryValues{ Query: "buckets() |> filter(fn: (r) => r.name !~ /^_/) |> rename(columns: {name: \"_value\"}) |> keep(columns: [\"_value\"])", Language: "flux", }, varArgs.Values) newSumMapping := func(id pkger.SafeID, pkgName, name string, rt influxdb.ResourceType) pkger.SummaryLabelMapping { return pkger.SummaryLabelMapping{ Status: pkger.StateStatusNew, ResourceID: id, ResourceType: rt, ResourceMetaName: pkgName, ResourceName: name, LabelMetaName: labels[0].MetaName, LabelName: labels[0].Name, LabelID: labels[0].ID, } } mappings := sum.LabelMappings require.Len(t, mappings, 11) assert.Contains(t, mappings, newSumMapping(bkts[0].ID, bkts[0].MetaName, bkts[0].Name, influxdb.BucketsResourceType)) ch0 := checks[0] assert.Contains(t, mappings, newSumMapping(pkger.SafeID(ch0.Check.GetID()), ch0.MetaName, ch0.Check.GetName(), influxdb.ChecksResourceType)) ch1 := checks[0] assert.Contains(t, mappings, newSumMapping(pkger.SafeID(ch1.Check.GetID()), ch1.MetaName, ch1.Check.GetName(), influxdb.ChecksResourceType)) ne := endpoints[0] assert.Contains(t, mappings, newSumMapping(pkger.SafeID(ne.NotificationEndpoint.GetID()), ne.MetaName, ne.NotificationEndpoint.GetName(), influxdb.NotificationEndpointResourceType)) assert.Contains(t, mappings, newSumMapping(dashs[0].ID, dashs[0].MetaName, dashs[0].Name, influxdb.DashboardsResourceType)) assert.Contains(t, mappings, newSumMapping(rule.ID, rule.MetaName, rule.Name, influxdb.NotificationRuleResourceType)) assert.Contains(t, mappings, newSumMapping(task.ID, task.MetaName, task.Name, influxdb.TasksResourceType)) assert.Contains(t, mappings, newSumMapping(pkger.SafeID(teles[0].TelegrafConfig.ID), teles[0].MetaName, teles[0].TelegrafConfig.Name, influxdb.TelegrafsResourceType)) assert.Contains(t, mappings, newSumMapping(vars[0].ID, vars[0].MetaName, vars[0].Name, influxdb.VariablesResourceType)) }) t.Run("filtered by resource types", func(t *testing.T) { newPkg, err := svc.Export(ctx, pkger.ExportWithAllOrgResources( pkger.ExportByOrgIDOpt{ OrgID: l.Org.ID, ResourceKinds: []pkger.Kind{pkger.KindCheck, pkger.KindTask}, }, )) require.NoError(t, err) newSum := newPkg.Summary() assert.NotEmpty(t, newSum.Checks) assert.NotEmpty(t, newSum.Labels) assert.NotEmpty(t, newSum.Tasks) assert.Empty(t, newSum.Buckets) assert.Empty(t, newSum.Dashboards) assert.Empty(t, newSum.NotificationEndpoints) assert.Empty(t, newSum.NotificationRules) assert.Empty(t, newSum.TelegrafConfigs) assert.Empty(t, newSum.Variables) }) t.Run("filtered by label resource type", func(t *testing.T) { newPkg, err := svc.Export(ctx, pkger.ExportWithAllOrgResources( pkger.ExportByOrgIDOpt{ OrgID: l.Org.ID, ResourceKinds: []pkger.Kind{pkger.KindLabel}, }, )) require.NoError(t, err) newSum := newPkg.Summary() assert.NotEmpty(t, newSum.Labels) assert.Empty(t, newSum.Buckets) assert.Empty(t, newSum.Checks) assert.Empty(t, newSum.Dashboards) assert.Empty(t, newSum.NotificationEndpoints) assert.Empty(t, newSum.NotificationRules) assert.Empty(t, newSum.Tasks) assert.Empty(t, newSum.TelegrafConfigs) assert.Empty(t, newSum.Variables) }) t.Run("filtered by label name", func(t *testing.T) { newPkg, err := svc.Export(ctx, pkger.ExportWithAllOrgResources( pkger.ExportByOrgIDOpt{ OrgID: l.Org.ID, LabelNames: []string{"the 2nd label"}, }, )) require.NoError(t, err) newSum := newPkg.Summary() assert.NotEmpty(t, newSum.Buckets) assert.NotEmpty(t, newSum.Dashboards) assert.NotEmpty(t, newSum.Labels) assert.Empty(t, newSum.Checks) assert.Empty(t, newSum.NotificationEndpoints) assert.Empty(t, newSum.NotificationRules) assert.Empty(t, newSum.Tasks) assert.Empty(t, newSum.Variables) }) t.Run("filtered by label name and resource type", func(t *testing.T) { newPkg, err := svc.Export(ctx, pkger.ExportWithAllOrgResources( pkger.ExportByOrgIDOpt{ OrgID: l.Org.ID, LabelNames: []string{"the 2nd label"}, ResourceKinds: []pkger.Kind{pkger.KindDashboard}, }, )) require.NoError(t, err) newSum := newPkg.Summary() assert.NotEmpty(t, newSum.Dashboards) assert.NotEmpty(t, newSum.Labels) assert.Empty(t, newSum.Buckets) assert.Empty(t, newSum.Checks) assert.Empty(t, newSum.NotificationEndpoints) assert.Empty(t, newSum.NotificationRules) assert.Empty(t, newSum.Tasks) assert.Empty(t, newSum.Variables) }) }) t.Run("pkg with same bkt-var-label does nto create new resources for them", func(t *testing.T) { // validate the new template doesn't create new resources for bkts/labels/vars // since names collide. impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(newCompletePkg(t))) require.NoError(t, err) sum2 := impact.Summary require.Equal(t, sum1.Buckets, sum2.Buckets) require.Equal(t, sum1.Labels, sum2.Labels) require.Equal(t, sum1.NotificationEndpoints, sum2.NotificationEndpoints) require.Equal(t, sum1.Variables, sum2.Variables) // dashboards should be new require.NotEqual(t, sum1.Dashboards, sum2.Dashboards) }) t.Run("referenced secret values provided do not create new secrets", func(t *testing.T) { applyPkgStr := func(t *testing.T, pkgStr string) pkger.Summary { t.Helper() pkg, err := pkger.Parse(pkger.EncodingYAML, pkger.FromString(pkgStr)) require.NoError(t, err) impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(pkg)) require.NoError(t, err) return impact.Summary } pkgWithSecretRaw := fmt.Sprintf(` apiVersion: %[1]s kind: NotificationEndpointPagerDuty metadata: name: pager-duty-notification-endpoint spec: url: http://localhost:8080/orgs/7167eb6719fa34e5/alert-history routingKey: secret-sauce `, pkger.APIVersion) secretSum := applyPkgStr(t, pkgWithSecretRaw) require.Len(t, secretSum.NotificationEndpoints, 1) id := secretSum.NotificationEndpoints[0].NotificationEndpoint.GetID() expected := influxdb.SecretField{ Key: id.String() + "-routing-key", } secrets := secretSum.NotificationEndpoints[0].NotificationEndpoint.SecretFields() require.Len(t, secrets, 1) assert.Equal(t, expected, secrets[0]) const pkgWithSecretRef = ` apiVersion: %[1]s kind: NotificationEndpointPagerDuty metadata: name: pager-duty-notification-endpoint spec: url: http://localhost:8080/orgs/7167eb6719fa34e5/alert-history routingKey: secretRef: key: %s-routing-key ` secretSum = applyPkgStr(t, fmt.Sprintf(pkgWithSecretRef, pkger.APIVersion, id.String())) require.Len(t, secretSum.NotificationEndpoints, 1) expected = influxdb.SecretField{ Key: id.String() + "-routing-key", } secrets = secretSum.NotificationEndpoints[0].NotificationEndpoint.SecretFields() require.Len(t, secrets, 1) assert.Equal(t, expected, secrets[0]) }) t.Run("exporting resources with existing ids should return a valid pkg", func(t *testing.T) { resToClone := []pkger.ResourceToClone{ { Kind: pkger.KindBucket, ID: platform.ID(sum1Bkts[0].ID), }, { Kind: pkger.KindCheck, ID: sum1Checks[0].Check.GetID(), }, { Kind: pkger.KindCheck, ID: sum1Checks[1].Check.GetID(), }, { Kind: pkger.KindDashboard, ID: platform.ID(sum1Dashs[0].ID), }, { Kind: pkger.KindLabel, ID: platform.ID(sum1Labels[0].ID), }, { Kind: pkger.KindNotificationEndpoint, ID: sum1Endpoints[0].NotificationEndpoint.GetID(), }, { Kind: pkger.KindTask, ID: platform.ID(sum1Tasks[0].ID), }, { Kind: pkger.KindTelegraf, ID: sum1Teles[0].TelegrafConfig.ID, }, } resWithNewName := []pkger.ResourceToClone{ { Kind: pkger.KindNotificationRule, Name: "new rule name", ID: platform.ID(sum1Rules[0].ID), }, { Kind: pkger.KindVariable, Name: "new name", ID: platform.ID(sum1Vars[0].ID), }, } newPkg, err := svc.Export(ctx, pkger.ExportWithExistingResources(append(resToClone, resWithNewName...)...), ) require.NoError(t, err) newSum := newPkg.Summary() labels := newSum.Labels require.Len(t, labels, 2) sortLabels(labels) assert.Zero(t, labels[0].ID) assert.Equal(t, "label-1", labels[0].Name) assert.Zero(t, labels[1].ID) assert.Equal(t, "the 2nd label", labels[1].Name) bkts := newSum.Buckets require.Len(t, bkts, 1) assert.Zero(t, bkts[0].ID) assert.Equal(t, "rucketeer", bkts[0].Name) hasLabelAssociations(t, bkts[0].LabelAssociations, 2, "label-1", "the 2nd label") checks := newSum.Checks require.Len(t, checks, 2) sortChecks(checks) assert.Equal(t, "check 0 name", checks[0].Check.GetName()) hasLabelAssociations(t, checks[0].LabelAssociations, 1, "label-1") assert.Equal(t, "check-1", checks[1].Check.GetName()) hasLabelAssociations(t, checks[1].LabelAssociations, 1, "label-1") dashs := newSum.Dashboards require.Len(t, dashs, 1) assert.Zero(t, dashs[0].ID) assert.Equal(t, "dash_1", dashs[0].Name) assert.Equal(t, "desc1", dashs[0].Description) hasLabelAssociations(t, dashs[0].LabelAssociations, 2, "label-1", "the 2nd label") require.Len(t, dashs[0].Charts, 1) assert.Equal(t, influxdb.ViewPropertyTypeSingleStat, dashs[0].Charts[0].Properties.GetType()) newEndpoints := newSum.NotificationEndpoints require.Len(t, newEndpoints, 1) assert.Equal(t, sum1Endpoints[0].NotificationEndpoint.GetName(), newEndpoints[0].NotificationEndpoint.GetName()) assert.Equal(t, sum1Endpoints[0].NotificationEndpoint.GetDescription(), newEndpoints[0].NotificationEndpoint.GetDescription()) hasLabelAssociations(t, newEndpoints[0].LabelAssociations, 1, "label-1") require.Len(t, newSum.NotificationRules, 1) newRule := newSum.NotificationRules[0] assert.Equal(t, "new rule name", newRule.Name) assert.Zero(t, newRule.EndpointID) assert.NotEmpty(t, newRule.EndpointMetaName) hasLabelAssociations(t, newRule.LabelAssociations, 1, "label-1") require.Len(t, newSum.Tasks, 1) newTask := newSum.Tasks[0] assert.Equal(t, sum1Tasks[0].Name, newTask.Name) assert.Equal(t, sum1Tasks[0].Description, newTask.Description) assert.Equal(t, sum1Tasks[0].Cron, newTask.Cron) assert.Equal(t, sum1Tasks[0].Every, newTask.Every) assert.Equal(t, sum1Tasks[0].Offset, newTask.Offset) assert.Equal(t, sum1Tasks[0].Query, newTask.Query) assert.Equal(t, sum1Tasks[0].Status, newTask.Status) require.Len(t, newSum.TelegrafConfigs, 1) assert.Equal(t, sum1Teles[0].TelegrafConfig.Name, newSum.TelegrafConfigs[0].TelegrafConfig.Name) assert.Equal(t, sum1Teles[0].TelegrafConfig.Description, newSum.TelegrafConfigs[0].TelegrafConfig.Description) hasLabelAssociations(t, newSum.TelegrafConfigs[0].LabelAssociations, 1, "label-1") vars := newSum.Variables require.Len(t, vars, 1) assert.Zero(t, vars[0].ID) assert.Equal(t, "new name", vars[0].Name) // new name hasLabelAssociations(t, vars[0].LabelAssociations, 1, "label-1") varArgs := vars[0].Arguments require.NotNil(t, varArgs) assert.Equal(t, "query", varArgs.Type) assert.Equal(t, influxdb.VariableQueryValues{ Query: "buckets() |> filter(fn: (r) => r.name !~ /^_/) |> rename(columns: {name: \"_value\"}) |> keep(columns: [\"_value\"])", Language: "flux", }, varArgs.Values) }) t.Run("error incurred during template application when resources already exist rollsback to prev state", func(t *testing.T) { updatePkg, err := pkger.Parse(pkger.EncodingYAML, pkger.FromString(updatePkgYMLStr)) require.NoError(t, err) svc := pkger.NewService( pkger.WithBucketSVC(&fakeBucketSVC{ BucketService: l.BucketService(t), updateKillCount: 0, // kill on first update for bucket }), pkger.WithCheckSVC(l.CheckService()), pkger.WithDashboardSVC(l.DashboardService(t)), pkger.WithLabelSVC(l.LabelService(t)), pkger.WithNotificationEndpointSVC(l.NotificationEndpointService(t)), pkger.WithNotificationRuleSVC(l.NotificationRuleService(t)), pkger.WithOrganizationService(l.OrganizationService()), pkger.WithStore(pkger.NewStoreKV(l.kvStore)), pkger.WithTaskSVC(l.TaskServiceKV(t)), pkger.WithTelegrafSVC(l.TelegrafService(t)), pkger.WithVariableSVC(l.VariableService(t)), ) _, err = svc.Apply(ctx, l.Org.ID, 0, pkger.ApplyWithTemplate(updatePkg)) require.Error(t, err) bkt, err := l.BucketService(t).FindBucketByID(ctx, platform.ID(sum1Bkts[0].ID)) require.NoError(t, err) // make sure the desc change is not applied and is rolled back to prev desc assert.Equal(t, sum1Bkts[0].Description, bkt.Description) ch, err := l.CheckService().FindCheckByID(ctx, sum1Checks[0].Check.GetID()) require.NoError(t, err) ch.SetOwnerID(0) deadman, ok := ch.(*check.Threshold) require.True(t, ok) // validate the change to query is not persisting returned to previous state. // not checking entire bits, b/c we dont' save userID and so forth and makes a // direct comparison very annoying... assert.Equal(t, sum1Checks[0].Check.(*check.Threshold).Query.Text, deadman.Query.Text) label, err := l.LabelService(t).FindLabelByID(ctx, platform.ID(sum1Labels[0].ID)) require.NoError(t, err) assert.Equal(t, sum1Labels[0].Properties.Description, label.Properties["description"]) endpoint, err := l.NotificationEndpointService(t).FindNotificationEndpointByID(ctx, sum1Endpoints[0].NotificationEndpoint.GetID()) require.NoError(t, err) assert.Equal(t, sum1Endpoints[0].NotificationEndpoint.GetDescription(), endpoint.GetDescription()) v, err := l.VariableService(t).FindVariableByID(ctx, platform.ID(sum1Vars[0].ID)) require.NoError(t, err) assert.Equal(t, sum1Vars[0].Description, v.Description) }) }) t.Run("apply a task template with a complex query", func(t *testing.T) { // validates bug: https://github.com/influxdata/influxdb/issues/17069 pkgStr := fmt.Sprintf(` apiVersion: %[1]s kind: Task metadata: name: http-post-synthetic spec: name: Http.POST Synthetic (POST) every: 5m query: |- import "strings" import "csv" import "http" import "system" timeDiff = (t1, t2) => { return duration(v: uint(v: t2) - uint(v: t1)) } timeDiffNum = (t1, t2) => { return uint(v: t2) - uint(v: t1) } urlToPost = "http://www.duckduckgo.com" timeBeforeCall = system.time() responseCode = http.post(url: urlToPost, data: bytes(v: "influxdata")) timeAfterCall = system.time() responseTime = timeDiff(t1: timeBeforeCall, t2: timeAfterCall) responseTimeNum = timeDiffNum(t1: timeBeforeCall, t2: timeAfterCall) data = "#group,false,false,true,true,true,true,true,true #datatype,string,long,string,string,string,string,string,string #default,mean,,,,,,, ,result,table,service,response_code,time_before,time_after,response_time_duration,response_time_ns ,,0,http_post_ping,${string(v: responseCode)},${string(v: timeBeforeCall)},${string(v: timeAfterCall)},${string(v: responseTime)},${string(v: responseTimeNum)}" theTable = csv.from(csv: data) theTable |> map(fn: (r) => ({r with _time: now()})) |> map(fn: (r) => ({r with _measurement: "PingService", url: urlToPost, method: "POST"})) |> drop(columns: ["time_before", "time_after", "response_time_duration"]) |> to(bucket: "Pingpire", orgID: "039346c3777a1000", fieldFn: (r) => ({"responseCode": r.response_code, "responseTime": int(v: r.response_time_ns)})) `, pkger.APIVersion) pkg, err := pkger.Parse(pkger.EncodingYAML, pkger.FromString(pkgStr)) require.NoError(t, err) impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(pkg)) require.NoError(t, err) assert.NotZero(t, impact.StackID) require.Len(t, impact.Summary.Tasks, 1) }) t.Run("applying a template with an invalid template returns parser errors", func(t *testing.T) { stack, cleanup := newStackFn(t, pkger.StackCreate{}) defer cleanup() template := newTemplate(newTelegrafObject("with_underscore-is-bad", "", "")) _, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(template), pkger.ApplyWithStackID(stack.ID), ) require.Error(t, err) require.True(t, pkger.IsParseErr(err)) vErrs := err.(pkger.ParseError).ValidationErrs() require.Len(t, vErrs, 1) // this check is to make sure we aren't creating duplicate error messages like // was witnessed from CLI recently. require.Equal(t, 1, strings.Count(err.Error(), "DNS-1123")) }) t.Run("applying a pkg without a stack will have a stack created for it", func(t *testing.T) { pkg := newTemplate(newBucketObject("room", "for", "more")) impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(pkg)) require.NoError(t, err) require.NotZero(t, impact.StackID) defer deleteStackFn(t, impact.StackID) require.Len(t, impact.Summary.Buckets, 1) require.NotZero(t, impact.Summary.Buckets[0].ID) stacks, err := svc.ListStacks(ctx, l.Org.ID, pkger.ListFilter{ StackIDs: []platform.ID{impact.StackID}, }) require.NoError(t, err) require.Len(t, stacks, 1) ev := stacks[0].LatestEvent() require.Len(t, ev.Resources, 1) assert.Equal(t, ev.Resources[0].MetaName, "room") assert.Equal(t, platform.ID(impact.Summary.Buckets[0].ID), ev.Resources[0].ID) }) t.Run("apply a template with env refs", func(t *testing.T) { pkgStr := fmt.Sprintf(` apiVersion: %[1]s kind: Bucket metadata: name: envRef: key: "bkt-1-name-ref" spec: associations: - kind: Label name: envRef: key: label-1-name-ref --- apiVersion: %[1]s kind: Label metadata: name: envRef: key: "label-1-name-ref" spec: --- apiVersion: influxdata.com/v2alpha1 kind: CheckDeadman metadata: name: envRef: key: check-1-name-ref spec: every: 5m level: cRiT query: > from(bucket: "rucket_1") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) statusMessageTemplate: "Check: ${ r._check_name } is: ${ r._level }" --- apiVersion: influxdata.com/v2alpha1 kind: Dashboard metadata: name: envRef: key: dash-1-name-ref spec: --- apiVersion: influxdata.com/v2alpha1 kind: NotificationEndpointSlack metadata: name: envRef: key: endpoint-1-name-ref spec: url: https://hooks.slack.com/services/bip/piddy/boppidy --- apiVersion: influxdata.com/v2alpha1 kind: NotificationRule metadata: name: envRef: key: rule-1-name-ref spec: endpointName: envRef: key: endpoint-1-name-ref every: 10m messageTemplate: "Notification Rule: ${ r._notification_rule_name } triggered by check: ${ r._check_name }: ${ r._message }" statusRules: - currentLevel: WARN --- apiVersion: influxdata.com/v2alpha1 kind: Telegraf metadata: name: envRef: key: telegraf-1-name-ref spec: config: | [agent] interval = "10s" --- apiVersion: influxdata.com/v2alpha1 kind: Task metadata: name: envRef: key: task-1-name-ref spec: cron: 15 * * * * query: > from(bucket: "rucket_1") --- apiVersion: influxdata.com/v2alpha1 kind: Variable metadata: name: envRef: key: var-1-name-ref spec: type: constant values: [first val] `, pkger.APIVersion) pkg, err := pkger.Parse(pkger.EncodingYAML, pkger.FromString(pkgStr)) require.NoError(t, err) impact, err := svc.DryRun(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(pkg)) require.NoError(t, err) assert.Zero(t, impact.StackID) sum := impact.Summary require.Len(t, sum.Buckets, 1) assert.Equal(t, "env-bkt-1-name-ref", sum.Buckets[0].Name) assert.Len(t, sum.Buckets[0].LabelAssociations, 1) require.Len(t, sum.Checks, 1) assert.Equal(t, "env-check-1-name-ref", sum.Checks[0].Check.GetName()) require.Len(t, sum.Dashboards, 1) assert.Equal(t, "env-dash-1-name-ref", sum.Dashboards[0].Name) require.Len(t, sum.Labels, 1) assert.Equal(t, "env-label-1-name-ref", sum.Labels[0].Name) require.Len(t, sum.NotificationEndpoints, 1) assert.Equal(t, "env-endpoint-1-name-ref", sum.NotificationEndpoints[0].NotificationEndpoint.GetName()) require.Len(t, sum.NotificationRules, 1) assert.Equal(t, "env-rule-1-name-ref", sum.NotificationRules[0].Name) require.Len(t, sum.TelegrafConfigs, 1) assert.Equal(t, "env-task-1-name-ref", sum.Tasks[0].Name) require.Len(t, sum.TelegrafConfigs, 1) assert.Equal(t, "env-telegraf-1-name-ref", sum.TelegrafConfigs[0].TelegrafConfig.Name) require.Len(t, sum.Variables, 1) assert.Equal(t, "env-var-1-name-ref", sum.Variables[0].Name) expectedMissingEnvs := []string{ "bkt-1-name-ref", "check-1-name-ref", "dash-1-name-ref", "endpoint-1-name-ref", "label-1-name-ref", "rule-1-name-ref", "task-1-name-ref", "telegraf-1-name-ref", "var-1-name-ref", } assert.Equal(t, expectedMissingEnvs, sum.MissingEnvs) impact, err = svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(pkg), pkger.ApplyWithEnvRefs(map[string]interface{}{ "bkt-1-name-ref": "rucket_threeve", "check-1-name-ref": "check_threeve", "dash-1-name-ref": "dash_threeve", "endpoint-1-name-ref": "endpoint_threeve", "label-1-name-ref": "label_threeve", "rule-1-name-ref": "rule_threeve", "telegraf-1-name-ref": "telegraf_threeve", "task-1-name-ref": "task_threeve", "var-1-name-ref": "var_threeve", }), ) require.NoError(t, err) assert.NotZero(t, impact.StackID) sum = impact.Summary assert.Equal(t, "rucket_threeve", sum.Buckets[0].Name) assert.Equal(t, "check_threeve", sum.Checks[0].Check.GetName()) assert.Equal(t, "dash_threeve", sum.Dashboards[0].Name) assert.Equal(t, "endpoint_threeve", sum.NotificationEndpoints[0].NotificationEndpoint.GetName()) assert.Equal(t, "label_threeve", sum.Labels[0].Name) assert.Equal(t, "rule_threeve", sum.NotificationRules[0].Name) assert.Equal(t, "endpoint_threeve", sum.NotificationRules[0].EndpointMetaName) assert.Equal(t, "telegraf_threeve", sum.TelegrafConfigs[0].TelegrafConfig.Name) assert.Equal(t, "task_threeve", sum.Tasks[0].Name) assert.Equal(t, "var_threeve", sum.Variables[0].Name) assert.Empty(t, sum.MissingEnvs) }) t.Run("apply a template with query refs", func(t *testing.T) { t.Run("dashboard", func(t *testing.T) { dashName := "dash-1" newDashTmpl := func(t *testing.T) *pkger.Template { t.Helper() tmplStr := ` apiVersion: influxdata.com/v2alpha1 kind: Dashboard metadata: name: %s spec: charts: - kind: Single_Stat name: single stat xPos: 1 yPos: 2 width: 6 height: 3 queries: - query: | option params = { bucket: "foo", start: -1d, stop: now(), name: "max", floatVal: 1.0, minVal: 10 } from(bucket: params.bucket) |> range(start: params.start, end: params.stop) |> filter(fn: (r) => r._measurement == "processes") |> filter(fn: (r) => r.floater == params.floatVal) |> filter(fn: (r) => r._value > params.minVal) |> aggregateWindow(every: v.windowPeriod, fn: max) |> yield(name: params.name) params: - key: bucket default: "bar" type: string - key: start type: duration - key: stop type: time - key: floatVal default: 37.2 type: float - key: minVal type: int - key: name # infer type colors: - name: laser type: text hex: "#8F8AF4" value: 3` tmplStr = fmt.Sprintf(tmplStr, dashName) template, err := pkger.Parse(pkger.EncodingYAML, pkger.FromString(tmplStr)) require.NoError(t, err) return template } isExpectedQuery := func(t *testing.T, actual pkger.SummaryDashboard, expectedParams string) { t.Helper() require.Len(t, actual.Charts, 1) props, ok := actual.Charts[0].Properties.(influxdb.SingleStatViewProperties) require.True(t, ok, "unexpected chart properties") require.Len(t, props.Queries, 1) expectedQuery := expectedParams + ` from(bucket: params.bucket) |> range(start: params.start, end: params.stop) |> filter(fn: (r) => r._measurement == "processes") |> filter(fn: (r) => r.floater == params.floatVal) |> filter(fn: (r) => r._value > params.minVal) |> aggregateWindow(every: v.windowPeriod, fn: max) |> yield(name: params.name) ` assert.Equal(t, expectedQuery, props.Queries[0].Text) assert.Equal(t, "advanced", props.Queries[0].EditMode) } envKey := func(paramKey string) string { return fmt.Sprintf( "dashboards[%s].spec.charts[0].queries[0].params.%s", dashName, paramKey, ) } t.Run("using default values", func(t *testing.T) { stack, cleanup := newStackFn(t, pkger.StackCreate{}) defer cleanup() impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithStackID(stack.ID), pkger.ApplyWithTemplate(newDashTmpl(t)), ) require.NoError(t, err) require.Len(t, impact.Summary.Dashboards, 1) actual := impact.Summary.Dashboards[0] expectedParams := `option params = { bucket: "bar", start: -24h0m0s, stop: now(), name: "max", floatVal: 37.2, minVal: 10, }` isExpectedQuery(t, actual, expectedParams) require.Len(t, actual.EnvReferences, 6) expectedRefs := []pkger.SummaryReference{ { Field: "spec.charts[0].queries[0].params.bucket", EnvRefKey: `dashboards[dash-1].spec.charts[0].queries[0].params.bucket`, ValType: "string", DefaultValue: "bar", }, { Field: "spec.charts[0].queries[0].params.floatVal", EnvRefKey: `dashboards[dash-1].spec.charts[0].queries[0].params.floatVal`, ValType: "float", DefaultValue: 37.2, }, } assert.Equal(t, expectedRefs, actual.EnvReferences[:len(expectedRefs)]) // check necessary since json can flip int to float type and fail assertions // in a flakey manner expectedIntRef := pkger.SummaryReference{ Field: "spec.charts[0].queries[0].params.minVal", EnvRefKey: `dashboards[dash-1].spec.charts[0].queries[0].params.minVal`, ValType: "integer", DefaultValue: int64(10), } actualIntRef := actual.EnvReferences[len(expectedRefs)] if f, ok := actualIntRef.DefaultValue.(float64); ok { actualIntRef.DefaultValue = int64(f) } assert.Equal(t, expectedIntRef, actualIntRef) expectedRefs = []pkger.SummaryReference{ { Field: "spec.charts[0].queries[0].params.name", EnvRefKey: `dashboards[dash-1].spec.charts[0].queries[0].params.name`, ValType: "string", DefaultValue: "max", }, { Field: "spec.charts[0].queries[0].params.start", EnvRefKey: `dashboards[dash-1].spec.charts[0].queries[0].params.start`, ValType: "duration", DefaultValue: "-24h0m0s", }, { Field: "spec.charts[0].queries[0].params.stop", EnvRefKey: `dashboards[dash-1].spec.charts[0].queries[0].params.stop`, ValType: "time", DefaultValue: "now()", }, } assert.Equal(t, expectedRefs, actual.EnvReferences[3:]) }) t.Run("with user provided values", func(t *testing.T) { stack, cleanup := newStackFn(t, pkger.StackCreate{}) defer cleanup() impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithStackID(stack.ID), pkger.ApplyWithTemplate(newDashTmpl(t)), pkger.ApplyWithEnvRefs(map[string]interface{}{ envKey("bucket"): "foobar", envKey("name"): "min", envKey("start"): "-5d", envKey("floatVal"): 33.3, envKey("minVal"): 3, }), ) require.NoError(t, err) require.Len(t, impact.Summary.Dashboards, 1) actual := impact.Summary.Dashboards[0] expectedParams := `option params = { bucket: "foobar", start: -5d, stop: now(), name: "min", floatVal: 33.3, minVal: 3, }` isExpectedQuery(t, actual, expectedParams) require.Len(t, actual.EnvReferences, 6) expectedRefs := []pkger.SummaryReference{ { Field: "spec.charts[0].queries[0].params.bucket", EnvRefKey: `dashboards[dash-1].spec.charts[0].queries[0].params.bucket`, ValType: "string", Value: "foobar", DefaultValue: "bar", }, { Field: "spec.charts[0].queries[0].params.floatVal", EnvRefKey: `dashboards[dash-1].spec.charts[0].queries[0].params.floatVal`, ValType: "float", Value: 33.3, DefaultValue: 37.2, }, } assert.Equal(t, expectedRefs, actual.EnvReferences[:len(expectedRefs)]) // check necessary since json can flip int to float type and fail assertions // in a flakey manner expectedIntRef := pkger.SummaryReference{ Field: "spec.charts[0].queries[0].params.minVal", EnvRefKey: `dashboards[dash-1].spec.charts[0].queries[0].params.minVal`, ValType: "integer", Value: int64(3), DefaultValue: int64(10), } actualIntRef := actual.EnvReferences[len(expectedRefs)] if f, ok := actualIntRef.DefaultValue.(float64); ok { actualIntRef.DefaultValue = int64(f) } if f, ok := actualIntRef.Value.(float64); ok { actualIntRef.Value = int64(f) } assert.Equal(t, expectedIntRef, actualIntRef) expectedRefs = []pkger.SummaryReference{ { Field: "spec.charts[0].queries[0].params.name", EnvRefKey: `dashboards[dash-1].spec.charts[0].queries[0].params.name`, ValType: "string", Value: "min", DefaultValue: "max", }, { Field: "spec.charts[0].queries[0].params.start", EnvRefKey: `dashboards[dash-1].spec.charts[0].queries[0].params.start`, ValType: "duration", Value: "-5d", DefaultValue: "-24h0m0s", }, { Field: "spec.charts[0].queries[0].params.stop", EnvRefKey: `dashboards[dash-1].spec.charts[0].queries[0].params.stop`, ValType: "time", DefaultValue: "now()", }, } assert.Equal(t, expectedRefs, actual.EnvReferences[3:]) }) }) t.Run("task", func(t *testing.T) { taskName := "task-1" newDashTmpl := func(t *testing.T) *pkger.Template { t.Helper() tmplStr := ` apiVersion: influxdata.com/v2alpha1 kind: Task metadata: name: %s spec: every: 10m query: | option params = { bucket: "foo", start: -1d, stop: now(), name: "max", floatVal: 1.0, minVal: 10 } from(bucket: params.bucket) |> range(start: params.start, stop: params.stop) |> filter(fn: (r) => r._measurement == "processes") |> filter(fn: (r) => r.floater == params.floatVal) |> filter(fn: (r) => r._value > params.minVal) |> aggregateWindow(every: 1m, fn: max) |> yield(name: params.name) params: - key: bucket default: "bar" type: string - key: start type: duration - key: stop type: time - key: floatVal default: 37.2 type: float - key: minVal type: int - key: name # infer type` tmplStr = fmt.Sprintf(tmplStr, taskName) template, err := pkger.Parse(pkger.EncodingYAML, pkger.FromString(tmplStr)) require.NoError(t, err) return template } isExpectedQuery := func(t *testing.T, actual pkger.SummaryTask, expectedParams string) { t.Helper() expectedQuery := expectedParams + ` from(bucket: params.bucket) |> range(start: params.start, stop: params.stop) |> filter(fn: (r) => r._measurement == "processes") |> filter(fn: (r) => r.floater == params.floatVal) |> filter(fn: (r) => r._value > params.minVal) |> aggregateWindow(every: 1m, fn: max) |> yield(name: params.name) ` assert.Equal(t, expectedQuery, actual.Query) } envKey := func(paramKey string) string { return fmt.Sprintf( "tasks[%s].spec.params.%s", taskName, paramKey, ) } t.Run("using default values", func(t *testing.T) { stack, cleanup := newStackFn(t, pkger.StackCreate{}) defer cleanup() impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithStackID(stack.ID), pkger.ApplyWithTemplate(newDashTmpl(t)), ) require.NoError(t, err) require.Len(t, impact.Summary.Tasks, 1) actual := impact.Summary.Tasks[0] expectedParams := `option params = { bucket: "bar", start: -24h0m0s, stop: now(), name: "max", floatVal: 37.2, minVal: 10, }` isExpectedQuery(t, actual, expectedParams) require.Len(t, actual.EnvReferences, 6) expectedRefs := []pkger.SummaryReference{ { Field: "spec.params.bucket", EnvRefKey: `tasks[task-1].spec.params.bucket`, ValType: "string", DefaultValue: "bar", }, { Field: "spec.params.floatVal", EnvRefKey: `tasks[task-1].spec.params.floatVal`, ValType: "float", DefaultValue: 37.2, }, } assert.Equal(t, expectedRefs, actual.EnvReferences[:len(expectedRefs)]) // check necessary since json can flip int to float type and fail assertions // in a flakey manner expectedIntRef := pkger.SummaryReference{ Field: "spec.params.minVal", EnvRefKey: `tasks[task-1].spec.params.minVal`, ValType: "integer", DefaultValue: int64(10), } actualIntRef := actual.EnvReferences[len(expectedRefs)] if f, ok := actualIntRef.DefaultValue.(float64); ok { actualIntRef.DefaultValue = int64(f) } assert.Equal(t, expectedIntRef, actualIntRef) expectedRefs = []pkger.SummaryReference{ { Field: "spec.params.name", EnvRefKey: `tasks[task-1].spec.params.name`, ValType: "string", DefaultValue: "max", }, { Field: "spec.params.start", EnvRefKey: `tasks[task-1].spec.params.start`, ValType: "duration", DefaultValue: "-24h0m0s", }, { Field: "spec.params.stop", EnvRefKey: `tasks[task-1].spec.params.stop`, ValType: "time", DefaultValue: "now()", }, } assert.Equal(t, expectedRefs, actual.EnvReferences[3:]) }) t.Run("with user provided values", func(t *testing.T) { stack, cleanup := newStackFn(t, pkger.StackCreate{}) defer cleanup() impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithStackID(stack.ID), pkger.ApplyWithTemplate(newDashTmpl(t)), pkger.ApplyWithEnvRefs(map[string]interface{}{ envKey("bucket"): "foobar", envKey("name"): "min", envKey("start"): "-5d", envKey("floatVal"): 33.3, envKey("minVal"): 3, }), ) require.NoError(t, err) require.Len(t, impact.Summary.Tasks, 1) actual := impact.Summary.Tasks[0] expectedParams := `option params = { bucket: "foobar", start: -5d, stop: now(), name: "min", floatVal: 33.3, minVal: 3, }` isExpectedQuery(t, actual, expectedParams) require.Len(t, actual.EnvReferences, 6) expectedRefs := []pkger.SummaryReference{ { Field: "spec.params.bucket", EnvRefKey: `tasks[task-1].spec.params.bucket`, ValType: "string", Value: "foobar", DefaultValue: "bar", }, { Field: "spec.params.floatVal", EnvRefKey: `tasks[task-1].spec.params.floatVal`, ValType: "float", Value: 33.3, DefaultValue: 37.2, }, } assert.Equal(t, expectedRefs, actual.EnvReferences[:len(expectedRefs)]) // check necessary since json can flip int to float type and fail assertions // in a flakey manner expectedIntRef := pkger.SummaryReference{ Field: "spec.params.minVal", EnvRefKey: `tasks[task-1].spec.params.minVal`, ValType: "integer", Value: int64(3), DefaultValue: int64(10), } actualIntRef := actual.EnvReferences[len(expectedRefs)] if f, ok := actualIntRef.DefaultValue.(float64); ok { actualIntRef.DefaultValue = int64(f) } if f, ok := actualIntRef.Value.(float64); ok { actualIntRef.Value = int64(f) } assert.Equal(t, expectedIntRef, actualIntRef) expectedRefs = []pkger.SummaryReference{ { Field: "spec.params.name", EnvRefKey: `tasks[task-1].spec.params.name`, ValType: "string", Value: "min", DefaultValue: "max", }, { Field: "spec.params.start", EnvRefKey: `tasks[task-1].spec.params.start`, ValType: "duration", Value: "-5d", DefaultValue: "-24h0m0s", }, { Field: "spec.params.stop", EnvRefKey: `tasks[task-1].spec.params.stop`, ValType: "time", DefaultValue: "now()", }, } assert.Equal(t, expectedRefs, actual.EnvReferences[3:]) }) }) }) } func newCompletePkg(t *testing.T) *pkger.Template { t.Helper() pkg, err := pkger.Parse(pkger.EncodingYAML, pkger.FromString(pkgYMLStr)) require.NoError(t, err) return pkg } const telegrafCfg = `[agent] interval = "10s" metric_batch_size = 1000 metric_buffer_limit = 10000 collection_jitter = "0s" flush_interval = "10s" [[outputs.influxdb_v2]] urls = ["http://localhost:8086"] token = "$INFLUX_TOKEN" organization = "rg" bucket = "rucket_3" [[inputs.cpu]] percpu = true ` var pkgYMLStr = fmt.Sprintf(` apiVersion: %[1]s kind: Label metadata: name: label-1 --- apiVersion: %[1]s kind: Label metadata: name: the-2nd-label spec: name: the 2nd label --- apiVersion: %[1]s kind: Bucket metadata: name: rucket-1 spec: name: rucketeer associations: - kind: Label name: label-1 - kind: Label name: the-2nd-label --- apiVersion: %[1]s kind: Dashboard metadata: name: dash-uuid spec: name: dash_1 description: desc1 charts: - kind: Single_Stat name: single stat suffix: days width: 6 height: 3 shade: true queries: - query: > from(bucket: v.bucket) |> range(start: v.timeRangeStart) |> filter(fn: (r) => r._measurement == "system") |> filter(fn: (r) => r._field == "uptime") |> last() |> map(fn: (r) => ({r with _value: r._value / 86400})) |> yield(name: "last") colors: - name: laser type: text hex: "#8F8AF4" associations: - kind: Label name: label-1 - kind: Label name: the-2nd-label --- apiVersion: %[1]s kind: Variable metadata: name: var-query-1 spec: name: query var description: var_query_1 desc type: query language: flux query: | buckets() |> filter(fn: (r) => r.name !~ /^_/) |> rename(columns: {name: "_value"}) |> keep(columns: ["_value"]) selected: - rucketeer associations: - kind: Label name: label-1 --- apiVersion: %[1]s kind: Telegraf metadata: name: first-tele-config spec: name: first tele config description: desc associations: - kind: Label name: label-1 config: %+q --- apiVersion: %[1]s kind: NotificationEndpointHTTP metadata: name: http-none-auth-notification-endpoint # on export of resource created from this, will not be same name as this spec: name: no auth endpoint type: none description: http none auth desc method: GET url: https://www.example.com/endpoint/noneauth status: inactive associations: - kind: Label name: label-1 --- apiVersion: %[1]s kind: CheckThreshold metadata: name: check-0 spec: name: check 0 name every: 1m query: > from(bucket: "rucket_1") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn: (r) => r._measurement == "cpu") |> filter(fn: (r) => r._field == "usage_idle") |> aggregateWindow(every: 1m, fn: mean) |> yield(name: "mean") statusMessageTemplate: "Check: ${ r._check_name } is: ${ r._level }" tags: - key: tag_1 value: val_1 thresholds: - type: inside_range level: INfO min: 30.0 max: 45.0 - type: outside_range level: WARN min: 60.0 max: 70.0 - type: greater level: CRIT val: 80 - type: lesser level: OK val: 30 associations: - kind: Label name: label-1 --- apiVersion: %[1]s kind: CheckDeadman metadata: name: check-1 spec: description: desc_1 every: 5m level: cRiT offset: 10s query: > from(bucket: "rucket_1") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn: (r) => r._measurement == "cpu") |> filter(fn: (r) => r._field == "usage_idle") |> aggregateWindow(every: 1m, fn: mean) |> yield(name: "mean") reportZero: true staleTime: 10m statusMessageTemplate: "Check: ${ r._check_name } is: ${ r._level }" timeSince: 90s associations: - kind: Label name: label-1 --- apiVersion: %[1]s kind: NotificationRule metadata: name: rule-uuid spec: name: rule_0 description: desc_0 endpointName: http-none-auth-notification-endpoint every: 10m offset: 30s messageTemplate: "Notification Rule: ${ r._notification_rule_name } triggered by check: ${ r._check_name }: ${ r._message }" status: active statusRules: - currentLevel: WARN - currentLevel: CRIT previousLevel: OK tagRules: - key: k1 value: v2 operator: eQuAl - key: k1 value: v1 operator: eQuAl associations: - kind: Label name: label-1 --- apiVersion: %[1]s kind: Task metadata: name: task-uuid spec: name: task_1 description: desc_1 cron: 15 * * * * query: > from(bucket: "rucket_1") |> yield() associations: - kind: Label name: label-1 `, pkger.APIVersion, telegrafCfg) var updatePkgYMLStr = fmt.Sprintf(` apiVersion: %[1]s kind: Label metadata: name: label-1 spec: descriptin: new desc --- apiVersion: %[1]s kind: Bucket metadata: name: rucket-1 spec: descriptin: new desc associations: - kind: Label name: label-1 --- apiVersion: %[1]s kind: Variable metadata: name: var-query-1 spec: description: new desc type: query language: flux query: | buckets() |> filter(fn: (r) => r.name !~ /^_/) |> rename(columns: {name: "_value"}) |> keep(columns: ["_value"]) associations: - kind: Label name: label-1 --- apiVersion: %[1]s kind: NotificationEndpointHTTP metadata: name: http-none-auth-notification-endpoint spec: name: no auth endpoint type: none description: new desc method: GET url: https://www.example.com/endpoint/noneauth status: active --- apiVersion: %[1]s kind: CheckThreshold metadata: name: check-0 spec: every: 1m query: > from("rucket1") |> yield() statusMessageTemplate: "Check: ${ r._check_name } is: ${ r._level }" thresholds: - type: inside_range level: INfO min: 30.0 max: 45.0 `, pkger.APIVersion) type fakeBucketSVC struct { influxdb.BucketService createCallCount mock.SafeCount createKillCount int updateCallCount mock.SafeCount updateKillCount int } func (f *fakeBucketSVC) CreateBucket(ctx context.Context, b *influxdb.Bucket) error { defer f.createCallCount.IncrFn()() if f.createCallCount.Count() == f.createKillCount { return errors.New("reached kill count") } return f.BucketService.CreateBucket(ctx, b) } func (f *fakeBucketSVC) UpdateBucket(ctx context.Context, id platform.ID, upd influxdb.BucketUpdate) (*influxdb.Bucket, error) { defer f.updateCallCount.IncrFn()() if f.updateCallCount.Count() == f.updateKillCount { return nil, errors.New("reached kill count") } return f.BucketService.UpdateBucket(ctx, id, upd) } type fakeLabelSVC struct { influxdb.LabelService createCallCount mock.SafeCount createKillCount int deleteCallCount mock.SafeCount deleteKillCount int } func (f *fakeLabelSVC) CreateLabelMapping(ctx context.Context, m *influxdb.LabelMapping) error { defer f.createCallCount.IncrFn()() if f.createCallCount.Count() == f.createKillCount { return errors.New("reached kill count") } return f.LabelService.CreateLabelMapping(ctx, m) } func (f *fakeLabelSVC) DeleteLabelMapping(ctx context.Context, m *influxdb.LabelMapping) error { defer f.deleteCallCount.IncrFn()() if f.deleteCallCount.Count() == f.deleteKillCount { return errors.New("reached kill count") } return f.LabelService.DeleteLabelMapping(ctx, m) } type fakeRuleStore struct { influxdb.NotificationRuleStore createCallCount mock.SafeCount createKillCount int } func (f *fakeRuleStore) CreateNotificationRule(ctx context.Context, nr influxdb.NotificationRuleCreate, userID platform.ID) error { defer f.createCallCount.IncrFn()() if f.createCallCount.Count() == f.createKillCount { return errors.New("reached kill count") } return f.NotificationRuleStore.CreateNotificationRule(ctx, nr, userID) } func assertErrorCode(t *testing.T, expected string, err error) { t.Helper() assert.Error(t, err) assert.Equal(t, expected, errors2.ErrorCode(err)) } type resourceChecker struct { tl *TestLauncher } func newResourceChecker(tl *TestLauncher) resourceChecker { return resourceChecker{tl: tl} } type ( getResourceOpt struct { id platform.ID name string } getResourceOptFn func() getResourceOpt ) func byID(id platform.ID) getResourceOptFn { return func() getResourceOpt { return getResourceOpt{id: id} } } func byName(name string) getResourceOptFn { return func() getResourceOpt { return getResourceOpt{name: name} } } func bySafeID(id pkger.SafeID) getResourceOptFn { return func() getResourceOpt { return getResourceOpt{id: platform.ID(id)} } } func (r resourceChecker) getBucket(t *testing.T, getOpt getResourceOptFn) (influxdb.Bucket, error) { t.Helper() bktSVC := r.tl.BucketService(t) var ( bkt *influxdb.Bucket err error ) switch opt := getOpt(); { case opt.name != "": bkt, err = bktSVC.FindBucketByName(ctx, r.tl.Org.ID, opt.name) case opt.id != 0: bkt, err = bktSVC.FindBucketByID(ctx, opt.id) default: require.Fail(t, "did not provide any get option") } if err != nil { return influxdb.Bucket{}, err } return *bkt, nil } func (r resourceChecker) mustGetBucket(t *testing.T, getOpt getResourceOptFn) influxdb.Bucket { t.Helper() bkt, err := r.getBucket(t, getOpt) require.NoError(t, err) return bkt } func (r resourceChecker) mustDeleteBucket(t *testing.T, id platform.ID) { t.Helper() require.NoError(t, r.tl.BucketService(t).DeleteBucket(ctx, id)) } func (r resourceChecker) getCheck(t *testing.T, getOpt getResourceOptFn) (influxdb.Check, error) { t.Helper() checkSVC := r.tl.CheckService() var ( ch influxdb.Check err error ) switch opt := getOpt(); { case opt.name != "": ch, err = checkSVC.FindCheck(ctx, influxdb.CheckFilter{ Name: &opt.name, OrgID: &r.tl.Org.ID, }) case opt.id != 0: ch, err = checkSVC.FindCheckByID(ctx, opt.id) default: require.Fail(t, "did not provide any get option") } return ch, err } func (r resourceChecker) mustGetCheck(t *testing.T, getOpt getResourceOptFn) influxdb.Check { t.Helper() c, err := r.getCheck(t, getOpt) require.NoError(t, err) return c } func (r resourceChecker) mustDeleteCheck(t *testing.T, id platform.ID) { t.Helper() require.NoError(t, r.tl.CheckService().DeleteCheck(ctx, id)) } func (r resourceChecker) getDashboard(t *testing.T, getOpt getResourceOptFn) (influxdb.Dashboard, error) { t.Helper() dashSVC := r.tl.DashboardService(t) var ( dashboard *influxdb.Dashboard err error ) opt := getOpt() switch { case opt.name != "": dashs, _, err := dashSVC.FindDashboards(ctx, influxdb.DashboardFilter{}, influxdb.DefaultDashboardFindOptions) if err != nil { return influxdb.Dashboard{}, err } for _, d := range dashs { if d.Name == opt.name { dashboard = d break } } case opt.id != 0: dashboard, err = dashSVC.FindDashboardByID(ctx, opt.id) default: require.Fail(t, "did not provide any get option") } if err != nil { return influxdb.Dashboard{}, err } if dashboard == nil { return influxdb.Dashboard{}, fmt.Errorf("failed to find desired dashboard with opts: %+v", opt) } return *dashboard, nil } func (r resourceChecker) mustGetDashboard(t *testing.T, getOpt getResourceOptFn) influxdb.Dashboard { t.Helper() dash, err := r.getDashboard(t, getOpt) require.NoError(t, err) return dash } func (r resourceChecker) mustDeleteDashboard(t *testing.T, id platform.ID) { t.Helper() require.NoError(t, r.tl.DashboardService(t).DeleteDashboard(ctx, id)) } func (r resourceChecker) getEndpoint(t *testing.T, getOpt getResourceOptFn) (influxdb.NotificationEndpoint, error) { t.Helper() endpointSVC := r.tl.NotificationEndpointService(t) var ( e influxdb.NotificationEndpoint err error ) switch opt := getOpt(); { case opt.name != "": var endpoints []influxdb.NotificationEndpoint endpoints, _, err = endpointSVC.FindNotificationEndpoints(ctx, influxdb.NotificationEndpointFilter{ OrgID: &r.tl.Org.ID, }) for _, existing := range endpoints { if existing.GetName() == opt.name { e = existing break } } case opt.id != 0: e, err = endpointSVC.FindNotificationEndpointByID(ctx, opt.id) default: require.Fail(t, "did not provide any get option") } if e == nil { return nil, errors.New("did not find endpoint") } return e, err } func (r resourceChecker) mustGetEndpoint(t *testing.T, getOpt getResourceOptFn) influxdb.NotificationEndpoint { t.Helper() e, err := r.getEndpoint(t, getOpt) require.NoError(t, err) return e } func (r resourceChecker) mustDeleteEndpoint(t *testing.T, id platform.ID) { t.Helper() _, _, err := r.tl.NotificationEndpointService(t).DeleteNotificationEndpoint(ctx, id) require.NoError(t, err) } func (r resourceChecker) getLabel(t *testing.T, getOpt getResourceOptFn) (influxdb.Label, error) { t.Helper() labelSVC := r.tl.LabelService(t) var ( label *influxdb.Label err error ) switch opt := getOpt(); { case opt.name != "": labels, err := labelSVC.FindLabels( ctx, influxdb.LabelFilter{ Name: opt.name, OrgID: &r.tl.Org.ID, }, influxdb.FindOptions{Limit: 1}, ) if err != nil { return influxdb.Label{}, err } if len(labels) == 0 { return influxdb.Label{}, errors.New("did not find label: " + opt.name) } label = labels[0] case opt.id != 0: label, err = labelSVC.FindLabelByID(ctx, opt.id) default: require.Fail(t, "did not provide any get option") } if err != nil { return influxdb.Label{}, err } return *label, nil } func (r resourceChecker) mustGetLabel(t *testing.T, getOpt getResourceOptFn) influxdb.Label { t.Helper() l, err := r.getLabel(t, getOpt) require.NoError(t, err) return l } func (r resourceChecker) mustDeleteLabel(t *testing.T, id platform.ID) { t.Helper() require.NoError(t, r.tl.LabelService(t).DeleteLabel(ctx, id)) } func (r resourceChecker) getRule(t *testing.T, getOpt getResourceOptFn) (influxdb.NotificationRule, error) { t.Helper() ruleSVC := r.tl.NotificationRuleService(t) var ( rule influxdb.NotificationRule err error ) switch opt := getOpt(); { case opt.name != "": var rules []influxdb.NotificationRule rules, _, err = ruleSVC.FindNotificationRules(ctx, influxdb.NotificationRuleFilter{ OrgID: &r.tl.Org.ID, }) for _, existing := range rules { if existing.GetName() == opt.name { rule = existing break } } case opt.id != 0: rule, err = ruleSVC.FindNotificationRuleByID(ctx, opt.id) default: require.Fail(t, "did not provide any get option") } if rule == nil { return nil, errors.New("did not find rule") } return rule, err } func (r resourceChecker) mustGetRule(t *testing.T, getOpt getResourceOptFn) influxdb.NotificationRule { t.Helper() rule, err := r.getRule(t, getOpt) require.NoError(t, err) return rule } func (r resourceChecker) mustDeleteRule(t *testing.T, id platform.ID) { t.Helper() require.NoError(t, r.tl.NotificationRuleService(t).DeleteNotificationRule(ctx, id)) } func (r resourceChecker) getTask(t *testing.T, getOpt getResourceOptFn) (taskmodel.Task, error) { t.Helper() taskSVC := r.tl.TaskService(t) var ( task *taskmodel.Task err error ) switch opt := getOpt(); { case opt.name != "": tasks, _, err := taskSVC.FindTasks(ctx, taskmodel.TaskFilter{ Name: &opt.name, OrganizationID: &r.tl.Org.ID, }) if err != nil { return taskmodel.Task{}, err } for _, tt := range tasks { if tt.Name == opt.name { task = tasks[0] break } } case opt.id != 0: task, err = taskSVC.FindTaskByID(ctx, opt.id) default: require.Fail(t, "did not provide a valid get option") } if task == nil { return taskmodel.Task{}, errors.New("did not find expected task by name") } return *task, err } func (r resourceChecker) mustGetTask(t *testing.T, getOpt getResourceOptFn) taskmodel.Task { t.Helper() task, err := r.getTask(t, getOpt) require.NoError(t, err) return task } func (r resourceChecker) mustDeleteTask(t *testing.T, id platform.ID) { t.Helper() require.NoError(t, r.tl.TaskService(t).DeleteTask(ctx, id)) } func (r resourceChecker) getTelegrafConfig(t *testing.T, getOpt getResourceOptFn) (influxdb.TelegrafConfig, error) { t.Helper() teleSVC := r.tl.TelegrafService(t) var ( config *influxdb.TelegrafConfig err error ) switch opt := getOpt(); { case opt.name != "": teles, _, _ := teleSVC.FindTelegrafConfigs(ctx, influxdb.TelegrafConfigFilter{ OrgID: &r.tl.Org.ID, }) for _, tt := range teles { if opt.name != "" && tt.Name == opt.name { config = teles[0] break } } case opt.id != 0: config, err = teleSVC.FindTelegrafConfigByID(ctx, opt.id) default: require.Fail(t, "did not provide a valid get option") } if config == nil { return influxdb.TelegrafConfig{}, errors.New("did not find expected telegraf by name") } return *config, err } func (r resourceChecker) mustGetTelegrafConfig(t *testing.T, getOpt getResourceOptFn) influxdb.TelegrafConfig { t.Helper() tele, err := r.getTelegrafConfig(t, getOpt) require.NoError(t, err) return tele } func (r resourceChecker) mustDeleteTelegrafConfig(t *testing.T, id platform.ID) { t.Helper() require.NoError(t, r.tl.TelegrafService(t).DeleteTelegrafConfig(ctx, id)) } func (r resourceChecker) getVariable(t *testing.T, getOpt getResourceOptFn) (influxdb.Variable, error) { t.Helper() varSVC := r.tl.VariableService(t) var ( variable *influxdb.Variable err error ) switch opt := getOpt(); { case opt.name != "": vars, err := varSVC.FindVariables(ctx, influxdb.VariableFilter{ OrganizationID: &r.tl.Org.ID, }) if err != nil { return influxdb.Variable{}, err } for i := range vars { v := vars[i] if v.Name == opt.name { variable = v break } } if variable == nil { return influxdb.Variable{}, errors.New("did not find variable: " + opt.name) } case opt.id != 0: variable, err = varSVC.FindVariableByID(ctx, opt.id) default: require.Fail(t, "did not provide any get option") } if err != nil { return influxdb.Variable{}, err } return *variable, nil } func (r resourceChecker) mustGetVariable(t *testing.T, getOpt getResourceOptFn) influxdb.Variable { t.Helper() l, err := r.getVariable(t, getOpt) require.NoError(t, err) return l } func (r resourceChecker) mustDeleteVariable(t *testing.T, id platform.ID) { t.Helper() err := r.tl.VariableService(t).DeleteVariable(ctx, id) require.NoError(t, err) } func sortChecks(checks []pkger.SummaryCheck) { sort.Slice(checks, func(i, j int) bool { return checks[i].Check.GetName() < checks[j].Check.GetName() }) } func sortLabels(labels []pkger.SummaryLabel) { sort.Slice(labels, func(i, j int) bool { return labels[i].Name < labels[j].Name }) } func strPtr(s string) *string { return &s }