Skip to content

Commit

Permalink
PC-11815 add budget adjustments terraform support
Browse files Browse the repository at this point in the history
  • Loading branch information
kubaceg committed Feb 22, 2024
1 parent f6c9de8 commit 2eebb64
Show file tree
Hide file tree
Showing 8 changed files with 538 additions and 16 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ NAMESPACE=nobl9
NAME=nobl9
BIN_DIR=./bin
BINARY=$(BIN_DIR)/terraform-provider-$(NAME)
VERSION=0.23.0
VERSION=0.23.116
BUILD_FLAGS="-X github.com/nobl9/terraform-provider-nobl9/nobl9.Version=$(VERSION)"
OS_ARCH?=linux_amd64

Expand Down
11 changes: 6 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ require (
github.com/hashicorp/go-cty v1.4.1-0.20200723130312-85980079f637
github.com/hashicorp/terraform-plugin-docs v0.13.0
github.com/hashicorp/terraform-plugin-sdk/v2 v2.19.0
github.com/nobl9/nobl9-go v0.77.2
github.com/nobl9/nobl9-go v0.78.0-rc1.0.20240221124254-577bcbff4924
github.com/stretchr/testify v1.8.4
github.com/teambition/rrule-go v1.8.2
)

require (
Expand All @@ -19,15 +20,15 @@ require (
github.com/agext/levenshtein v1.2.3 // indirect
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
github.com/armon/go-radix v1.0.0 // indirect
github.com/aws/aws-sdk-go v1.49.16 // indirect
github.com/aws/aws-sdk-go v1.50.22 // indirect
github.com/bgentry/speakeasy v0.1.0 // indirect
github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
github.com/fatih/color v1.15.0 // indirect
github.com/go-playground/validator/v10 v10.16.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/goccy/go-yaml v1.11.2 // indirect
github.com/goccy/go-yaml v1.11.3 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/go-cmp v0.5.9 // indirect
Expand Down Expand Up @@ -72,7 +73,7 @@ require (
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/posener/complete v1.2.3 // indirect
github.com/rs/zerolog v1.31.0 // indirect
github.com/rs/zerolog v1.32.0 // indirect
github.com/russross/blackfriday v1.6.0 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/spf13/cast v1.5.0 // indirect
Expand All @@ -81,7 +82,7 @@ require (
github.com/vmihailenco/tagparser v0.1.2 // indirect
github.com/zclconf/go-cty v1.10.0 // indirect
golang.org/x/crypto v0.17.0 // indirect
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc // indirect
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect
Expand Down
22 changes: 12 additions & 10 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/aws/aws-sdk-go v1.49.16 h1:KAQwhLg296hfffRdh+itA9p7Nx/3cXS/qOa3uF9ssig=
github.com/aws/aws-sdk-go v1.49.16/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
github.com/aws/aws-sdk-go v1.50.22 h1:BUhSaO2qLk2jkcyLebcvDmbdOunVe/Wq8RsCyI8szL0=
github.com/aws/aws-sdk-go v1.50.22/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I=
Expand Down Expand Up @@ -73,8 +73,8 @@ github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-yaml v1.11.2 h1:joq77SxuyIs9zzxEjgyLBugMQ9NEgTWxXfz2wVqwAaQ=
github.com/goccy/go-yaml v1.11.2/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU=
github.com/goccy/go-yaml v1.11.3 h1:B3W9IdWbvrUu2OYQGwvU1nZtvMQJPBKgBUuweJjLj6I=
github.com/goccy/go-yaml v1.11.3/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
Expand Down Expand Up @@ -221,8 +221,8 @@ github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nobl9/nobl9-go v0.77.2 h1:EiUNXfc2QECaGZ8jisYdREk8WVksavOlaP7mNfz+IW4=
github.com/nobl9/nobl9-go v0.77.2/go.mod h1:baz6btl+crjNVUPpJB5FPosQJvkh0L3AdNXWiALdj0E=
github.com/nobl9/nobl9-go v0.78.0-rc1.0.20240221124254-577bcbff4924 h1:K94fLjS8ci/jPnV/CMMKisuyTwAJ00CzzOCVA3BkFrI=
github.com/nobl9/nobl9-go v0.78.0-rc1.0.20240221124254-577bcbff4924/go.mod h1:4FKjnTGcRyTKEuS677FyLyNxFFS2ZjauAV6aNF9EOZw=
github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce h1:RPclfga2SEJmgMmz2k+Mg7cowZ8yv4Trqw9UsJby758=
github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce/go.mod h1:uFMI8w+ref4v2r9jz+c9i1IfIttS/OkmLfrk1jne5hs=
github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA=
Expand All @@ -240,8 +240,8 @@ github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSg
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0=
github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww=
github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY=
github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4=
Expand Down Expand Up @@ -270,6 +270,8 @@ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1F
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/teambition/rrule-go v1.8.2 h1:lIjpjvWTj9fFUZCmuoVDrKVOtdiyzbzc93qTmRVe/J8=
github.com/teambition/rrule-go v1.8.2/go.mod h1:Ieq5AbrKGciP1V//Wq8ktsTXwSwJHDD5mD/wLBGl3p4=
github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
Expand All @@ -296,8 +298,8 @@ golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc h1:ao2WRsKSzW6KuUY9IWPwWahcHCgR0s52IfwutMfEbdM=
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA=
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
Expand Down
1 change: 1 addition & 0 deletions nobl9/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ func Provider() *schema.Provider {
"nobl9_project": resourceProject(),
"nobl9_role_binding": resourceRoleBinding(),
"nobl9_slo": resourceSLO(),
"nobl9_budget_adjustment": budgetAdjustment(),
},

ConfigureContextFunc: providerConfigure,
Expand Down
268 changes: 268 additions & 0 deletions nobl9/resource_budgetadjustment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
package nobl9

import (
"context"
"errors"
"fmt"
"time"

"github.com/hashicorp/go-cty/cty"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/nobl9/nobl9-go/manifest"
"github.com/nobl9/nobl9-go/manifest/v1alpha/budgetadjustment"
v1 "github.com/nobl9/nobl9-go/sdk/endpoints/objects/v1"
"github.com/teambition/rrule-go"
)

func budgetAdjustment() *schema.Resource {
return &schema.Resource{
Schema: schemaBudgetAdjustment(),
CreateContext: resourceBudgetAdjustmentApply,
UpdateContext: resourceBudgetAdjustmentApply,
DeleteContext: resourceBudgetAdjustmentDelete,
ReadContext: resourceBudgetAdjustmentRead,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Description: "[Budget adjustment configuration documentation](https://docs.nobl9.com/yaml-guide#budget-adjustment)",
}
}

func schemaBudgetAdjustment() map[string]*schema.Schema {
return map[string]*schema.Schema{
"name": schemaName(),
"display_name": schemaDisplayName(),
"description": schemaDescription(),
"first_event_start": {
Type: schema.TypeString,
Required: true,
ValidateDiagFunc: validateDateTime,
},
"duration": {
Type: schema.TypeString,
Required: true,
ValidateDiagFunc: validateDuration,
},
"rrule": {
Type: schema.TypeString,
Optional: true,
ValidateDiagFunc: validateRrule,
},
"filters": {
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"slos": {
Type: schema.TypeSet,
Required: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"slo": {
Type: schema.TypeList,
MinItems: 1,
Required: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": schemaName(),
"project": schemaProject(),
},
},
},
},
},
},
},
},
},
}
}

func validateDuration(v interface{}, path cty.Path) diag.Diagnostics {
var diags diag.Diagnostics
_, err := time.ParseDuration(v.(string))
if err != nil {
diags = append(diags, diag.Diagnostic{
Severity: diag.Error,
Summary: "Invalid duration format",
Detail: fmt.Sprintf("Invalid duration format: %s", v),
AttributePath: path,
})
}
return diags
}

func validateRrule(v interface{}, path cty.Path) diag.Diagnostics {
var diags diag.Diagnostics
_, err := rrule.StrToRRule(v.(string))
if err != nil {
diags = append(diags, diag.Diagnostic{
Severity: diag.Error,
Summary: "Invalid rrule format",
Detail: fmt.Sprintf("Invalid rrule format: %s", v),
AttributePath: path,
})
}
return diags
}

func resourceBudgetAdjustmentApply(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
config := meta.(ProviderConfig)
client, ds := getClient(config)
if ds != nil {
return ds
}

budgetAdjustment := marshalBudgetAdjustment(d)

if err := resource.RetryContext(ctx, d.Timeout(schema.TimeoutCreate)-time.Minute, func() *resource.RetryError {
err := client.Objects().V1().Apply(ctx, []manifest.Object{budgetAdjustment})
if err != nil {
if errors.Is(err, errConcurrencyIssue) {
return resource.RetryableError(err)
}
return resource.NonRetryableError(err)
}
return nil
}); err != nil {
return diag.FromErr(err)
}

d.SetId(budgetAdjustment.Metadata.Name)
return resourceBudgetAdjustmentRead(ctx, d, meta)
}

func marshalBudgetAdjustment(d *schema.ResourceData) *budgetadjustment.BudgetAdjustment {
firstEventStart, _ := time.Parse(time.RFC3339, d.Get("first_event_start").(string))

adjustment := budgetadjustment.New(
budgetadjustment.Metadata{
Name: d.Get("name").(string),
DisplayName: d.Get("display_name").(string),
},
budgetadjustment.Spec{
Description: d.Get("description").(string),
FirstEventStart: firstEventStart,
Duration: d.Get("duration").(string),
Rrule: d.Get("rrule").(string),
Filters: marshalFilters(d.Get("filters")),
})

return &adjustment
}

func marshalFilters(filters interface{}) budgetadjustment.Filters {
filtersSet := filters.(*schema.Set)
if filtersSet.Len() == 0 {
return budgetadjustment.Filters{}
}
slos := filtersSet.List()[0].(map[string]interface{})["slos"].(*schema.Set)

var sloRef []budgetadjustment.SLORef
for _, filter := range slos.List() {
f := filter.(map[string]interface{})["slo"].([]interface{})[0].(map[string]interface{})
slo := budgetadjustment.SLORef{
Name: f["name"].(string),
Project: f["project"].(string),
}
sloRef = append(sloRef, slo)
}

return budgetadjustment.Filters{
SLOs: sloRef,
}
}

func resourceBudgetAdjustmentRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
config := meta.(ProviderConfig)
client, ds := getClient(config)
if ds != nil {
return ds
}

budgetAdjustments, err := client.Objects().V1().GetBudgetAdjustments(ctx, v1.GetBudgetAdjustmentRequest{
Names: []string{d.Id()},
})
if err != nil {
return diag.FromErr(err)
}
return unmarshalBudgetAdjustment(d, budgetAdjustments)
}

func unmarshalBudgetAdjustment(d *schema.ResourceData, objects []budgetadjustment.BudgetAdjustment) diag.Diagnostics {
if len(objects) != 1 {
d.SetId("")
return nil
}
object := objects[0]
var diags diag.Diagnostics
var err error

err = d.Set("name", object.Metadata.Name)
diags = appendError(diags, err)

err = d.Set("display_name", object.Metadata.DisplayName)
diags = appendError(diags, err)

err = d.Set("description", object.Spec.Description)
diags = appendError(diags, err)

err = d.Set("first_event_start", object.Spec.FirstEventStart.Format(time.RFC3339))
diags = appendError(diags, err)

err = d.Set("duration", object.Spec.Duration)
diags = appendError(diags, err)

err = d.Set("rrule", object.Spec.Rrule)
diags = appendError(diags, err)

err = unmarshalFilters(d, object.Spec.Filters)
diags = appendError(diags, err)

return diags
}

func unmarshalFilters(d *schema.ResourceData, filters budgetadjustment.Filters) error {
var slos []map[string]interface{}
for _, slo := range filters.SLOs {
sloMap := map[string]interface{}{
"name": slo.Name,
"project": slo.Project,
}
slos = append(slos, sloMap)
}

f := map[string]interface{}{
"slos": schema.NewSet(oneElementSet, []interface{}{
map[string]interface{}{
"slo": slos,
},
}),
}

return d.Set("filters", schema.NewSet(oneElementSet, []interface{}{f}))
}

func resourceBudgetAdjustmentDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
config := meta.(ProviderConfig)
client, ds := getClient(config)
if ds != nil {
return ds
}

if err := resource.RetryContext(ctx, d.Timeout(schema.TimeoutDelete)-time.Minute, func() *resource.RetryError {
err := client.Objects().V1().DeleteByName(ctx, manifest.KindBudgetAdjustment, "", d.Id())
if err != nil {
if errors.Is(err, errConcurrencyIssue) {
return resource.RetryableError(err)
}
return resource.NonRetryableError(err)
}
return nil
}); err != nil {
return diag.FromErr(err)
}
return nil
}
Loading

0 comments on commit 2eebb64

Please sign in to comment.