Skip to content

Commit

Permalink
add tier datasource, datasource filter, id string validator, tests (#219
Browse files Browse the repository at this point in the history
)

* add tier datasource, datasource filter, id string validator, tests

* update opslevel-go version, format hcl file

* set datasource Filter value field to required

* cast tier filter index int to string

* fix idStringValidator validation

* update description for idStringValidator
  • Loading branch information
davidbloss authored Mar 19, 2024
1 parent 369eba1 commit d29083a
Show file tree
Hide file tree
Showing 12 changed files with 364 additions and 279 deletions.
35 changes: 5 additions & 30 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,80 +4,55 @@ go 1.21

require (
github.com/hashicorp/terraform-plugin-framework v1.6.1
github.com/hashicorp/terraform-plugin-go v0.22.0
github.com/hashicorp/terraform-plugin-framework-validators v0.12.0
github.com/hashicorp/terraform-plugin-go v0.22.1
github.com/hashicorp/terraform-plugin-log v0.9.0
github.com/hashicorp/terraform-plugin-testing v1.7.0
github.com/opslevel/opslevel-go/v2024 v2024.2.26
github.com/opslevel/opslevel-go/v2024 v2024.3.15
)

require (
github.com/Masterminds/semver/v3 v3.2.1 // indirect
github.com/ProtonMail/go-crypto v1.1.0-alpha.0 // indirect
github.com/agext/levenshtein v1.2.2 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/cloudflare/circl v1.3.7 // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.19.0 // indirect
github.com/go-resty/resty/v2 v2.11.0 // indirect
github.com/go-resty/resty/v2 v2.12.0 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gosimple/slug v1.14.0 // indirect
github.com/gosimple/unidecode v1.0.1 // indirect
github.com/hashicorp/errwrap v1.0.0 // indirect
github.com/hashicorp/go-checkpoint v0.5.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 // indirect
github.com/hashicorp/go-hclog v1.6.2 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-plugin v1.6.0 // indirect
github.com/hashicorp/go-retryablehttp v0.7.5 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/hashicorp/go-version v1.6.0 // indirect
github.com/hashicorp/hc-install v0.6.3 // indirect
github.com/hashicorp/hcl/v2 v2.20.0 // indirect
github.com/hashicorp/logutils v1.0.0 // indirect
github.com/hashicorp/terraform-exec v0.20.0 // indirect
github.com/hashicorp/terraform-json v0.21.0 // indirect
github.com/hashicorp/terraform-plugin-sdk/v2 v2.33.0 // indirect
github.com/hashicorp/terraform-registry-address v0.2.3 // indirect
github.com/hashicorp/terraform-svchost v0.1.1 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect
github.com/hasura/go-graphql-client v0.12.1 // indirect
github.com/huandu/xstrings v1.4.0 // indirect
github.com/imdario/mergo v0.3.16 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
github.com/mitchellh/go-wordwrap v1.0.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/oklog/run v1.1.0 // indirect
github.com/opslevel/moredefaults v0.0.0-20240112142637-078c8ff8ba9c // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/relvacode/iso8601 v1.4.0 // indirect
github.com/rs/zerolog v1.32.0 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/zclconf/go-cty v1.14.3 // indirect
golang.org/x/crypto v0.21.0 // indirect
golang.org/x/mod v0.15.0 // indirect
golang.org/x/net v0.22.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.13.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240308144416-29370a3891b7 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect
google.golang.org/grpc v1.62.1 // indirect
google.golang.org/protobuf v1.33.0 // indirect
nhooyr.io/websocket v1.8.10 // indirect
Expand Down
139 changes: 15 additions & 124 deletions go.sum

Large diffs are not rendered by default.

62 changes: 36 additions & 26 deletions opslevel/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,13 @@ import (
"time"

// "strings"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"

// "github.com/mitchellh/mapstructure"
// "github.com/rs/zerolog/log"
// "github.com/hashicorp/terraform-plugin-sdk/helper/schema"
Expand Down Expand Up @@ -320,32 +325,37 @@ func timeLastUpdated() string {
// return output
// }

// func getDatasourceFilter(required bool, validFieldNames []string) *schema.Schema {
// return &schema.Schema{
// Type: schema.TypeList,
// ForceNew: true,
// Required: required,
// Optional: !required,
// MaxItems: 1,
// Elem: &schema.Resource{
// Schema: map[string]*schema.Schema{
// "field": {
// Type: schema.TypeString,
// Description: "The field of the target resource to filter upon.",
// ForceNew: true,
// Required: true,
// ValidateFunc: validation.StringInSlice(validFieldNames, false),
// },
// "value": {
// Type: schema.TypeString,
// Description: "The field value of the target resource to match.",
// ForceNew: true,
// Optional: true,
// },
// },
// },
// }
// }
type FilterModel struct {
Field types.String `tfsdk:"field"`
Value types.String `tfsdk:"value"`
}

func NewFilterModel(field string, value string) FilterModel {
return FilterModel{
Field: types.StringValue(string(field)),
Value: types.StringValue(string(value)),
}
}

// getDatasourceFilter originally had a "required" bool input parameter - no longer needed
func getDatasourceFilter(validFieldNames []string) schema.SingleNestedBlock {
return schema.SingleNestedBlock{
MarkdownDescription: "The filter of the target resource to filter upon.",
Attributes: map[string]schema.Attribute{
"field": schema.StringAttribute{
Description: "The field of the target resource to filter upon.",
Required: true,
Validators: []validator.String{
stringvalidator.OneOf(validFieldNames...),
},
},
"value": schema.StringAttribute{
Description: "The field value of the target resource to match.",
Required: true,
},
},
}
}

// func flattenTag(tag opslevel.Tag) string {
// return fmt.Sprintf("%s:%s", tag.Key, tag.Value)
Expand Down
223 changes: 133 additions & 90 deletions opslevel/datasource_opslevel_tier.go
Original file line number Diff line number Diff line change
@@ -1,94 +1,137 @@
package opslevel

// import (
// "fmt"
// "strconv"

// "github.com/opslevel/opslevel-go/v2024"

// "github.com/hashicorp/terraform-plugin-sdk/helper/schema"
// )

// func datasourceTier() *schema.Resource {
// return &schema.Resource{
// Read: wrap(datasourceTierRead),
// Schema: map[string]*schema.Schema{
// "filter": getDatasourceFilter(true, []string{"alias", "id", "index", "name"}),
// "alias": {
// Type: schema.TypeString,
// Computed: true,
// },
// "index": {
// Type: schema.TypeInt,
// Computed: true,
// },
// "name": {
// Type: schema.TypeString,
// Computed: true,
// },
// },
// }
// }
import (
"context"
"fmt"
"strconv"

"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/opslevel/opslevel-go/v2024"
)

// Ensure TierDataSource implements DataSourceWithConfigure interface
var _ datasource.DataSourceWithConfigure = &TierDataSource{}

func NewTierDataSource() datasource.DataSource {
return &TierDataSource{}
}

// TierDataSource manages a Tier data source.
type TierDataSource struct {
CommonDataSourceClient
}

// TierDataSourceModel describes the data source data model.
type TierDataSourceModel struct {
Alias types.String `tfsdk:"alias"`
Filter FilterModel `tfsdk:"filter"`
Id types.String `tfsdk:"id"`
Index types.Int64 `tfsdk:"index"`
Name types.String `tfsdk:"name"`
}

func NewTierDataSourceModel(ctx context.Context, tier opslevel.Tier, filter FilterModel) TierDataSourceModel {
return TierDataSourceModel{
Alias: types.StringValue(string(tier.Alias)),
Filter: filter,
Id: types.StringValue(string(tier.Id)),
Index: types.Int64Value(int64(tier.Index)),
Name: types.StringValue(string(tier.Name)),
}
}

func (d *TierDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_tier"
}

func (d *TierDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
validFieldNames := []string{"alias", "id", "index", "name"}
resp.Schema = schema.Schema{
// This description is used by the documentation generator and the language server.
MarkdownDescription: "Tier data source",

Attributes: map[string]schema.Attribute{
"alias": schema.StringAttribute{
MarkdownDescription: "Terraform specific identifier.",
Computed: true,
},
"id": schema.StringAttribute{
MarkdownDescription: "Terraform specific identifier.",
Computed: true,
},
"index": schema.Int64Attribute{
MarkdownDescription: "Terraform specific identifier.",
Computed: true,
},
"name": schema.StringAttribute{
Description: "The name of the domain.",
Computed: true,
},
},
Blocks: map[string]schema.Block{
"filter": getDatasourceFilter(validFieldNames),
},
}
}

func (d *TierDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
var data TierDataSourceModel

// Read Terraform configuration data into the model
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}

tiers, err := d.client.ListTiers()
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read tier datasource, got error: %s", err))
return
}

tier, err := filterTiers(tiers, data.Filter)
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to filter tier datasource, got error: %s", err))
return
}

tierDataModel := NewTierDataSourceModel(ctx, *tier, data.Filter)

// Save data into Terraform state
tflog.Trace(ctx, "read an OpsLevel Tier data source")
// resp.Diagnostics.Append(diags...)
resp.Diagnostics.Append(resp.State.Set(ctx, &tierDataModel)...)
}

// func filterTiers(levels []opslevel.Tier, field string, value string) (*opslevel.Tier, error) {
// if value == "" {
// return nil, fmt.Errorf("Please provide a non-empty value for filter's value")
// }

// var output opslevel.Tier
// found := false
// for _, item := range levels {
// switch field {
// case "alias":
// if item.Alias == value {
// output = item
// found = true
// }
// case "id":
// if string(item.Id) == value {
// output = item
// found = true
// }
// case "index":
// if v, err := strconv.Atoi(value); err == nil && item.Index == v {
// output = item
// found = true
// }
// case "name":
// if item.Name == value {
// output = item
// found = true
// }
// }
// if found {
// break
// }
// }

// if !found {
// return nil, fmt.Errorf("Unable to find tier with: %s==%s", field, value)
// }
// return &output, nil
// }

// func datasourceTierRead(d *schema.ResourceData, client *opslevel.Client) error {
// results, err := client.ListTiers()
// if err != nil {
// return err
// }

// field := d.Get("filter.0.field").(string)
// value := d.Get("filter.0.value").(string)

// item, itemErr := filterTiers(results, field, value)
// if itemErr != nil {
// return itemErr
// }

// d.SetId(string(item.Id))
// d.Set("alias", item.Alias)
// d.Set("index", item.Index)
// d.Set("name", item.Name)

// return nil
// }
func filterTiers(tiers []opslevel.Tier, filter FilterModel) (*opslevel.Tier, error) {
if filter.Value.Equal(types.StringValue("")) {
return nil, fmt.Errorf("Please provide a non-empty value for filter's value")
}
for _, tier := range tiers {
switch filter.Field.ValueString() {
case "alias":
if filter.Value.Equal(types.StringValue(tier.Alias)) {
return &tier, nil
}
case "id":
if filter.Value.Equal(types.StringValue(string(tier.Id))) {
return &tier, nil
}
case "index":
index := strconv.Itoa(int(tier.Index))
if filter.Value.Equal(types.StringValue(index)) {
return &tier, nil
}
case "name":
if filter.Value.Equal(types.StringValue(tier.Name)) {
return &tier, nil
}
}
}

return nil, fmt.Errorf("Unable to find tier with: %s==%s", filter.Field, filter.Value)
}
1 change: 1 addition & 0 deletions opslevel/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ func (p *OpslevelProvider) DataSources(context.Context) []func() datasource.Data
return []func() datasource.DataSource{
NewDomainDataSource,
NewDomainDataSourcesAll,
NewTierDataSource,
}
}

Expand Down
Loading

0 comments on commit d29083a

Please sign in to comment.