Skip to content

Commit

Permalink
Db/add filter datasource (#221)
Browse files Browse the repository at this point in the history
* WIP: build does not work now, fixing soon

* WIP: cleanup

* WIP: cleanup part 2

* WIP: update to go.mod version of terraform-plugin-go fixed build issue

* type fix in make_backend_tf.sh

* WIP: minor updates, update submodule hash

* WIP: update old func

* add DomainDataSources, can now list all domains

* remove domain resource updates from feature branch

* undo updates to datasource_opslevel_domains.go

* WIP: add util funcs used by provider Configure func

* WIP: minor clean up

* require OPSLEVEL_API_TOKEN for running task test

* add domain datasource with tests (#210)

* WIP: add domain datasource, testing needs work

* update provider to only watch NewDomainDataSource

* add testing with mocked and actual provider

* move client configuration to common.go

* improve task test, generalize common setup

* drop acceptance test, finish unit test, add debug tasks

* wrap up datasource domain testing

* add missing domain datasource fields, remove comments

* fix formatting, omit tests dir from task lint

* update error message

Co-authored-by: Kyle <[email protected]>

* add NewDomainDataSourceModel, handle single Domain with identifier

---------

Co-authored-by: Kyle <[email protected]>

* fix API TOKEN setting (#216)

* fix API TOKEN setting

* fix provider accepting vars

* fix handling and processing of provider inputs

* Add list all Domain datasources (#212)

* WIP: add domains multiple resource, testing needs work

* list all domain datasources, testing needed

* added test, needs more tests

* add ObjectType for Domain, used by ListAttribute in schema

* add fixes found by nilaway

* update datasource domain test

* fix formatting

* update go.mod

* temp ignore CI cache, test against built binary, not cached binary

* build terraform plugin in CI

* rename CommonClient to CommonDataSourceClient

* rename parseAllDomains to allDomainsToListValue

* Add manage Domain resource (#211)

* WIP: add domain resource, testing needs work

* update provider to only watch NewDomainResource

* add working domain resource with tests

* update go.mod

* update comment

* remove unused test

* add NewDomainResourceModel, use in create,update terraform operations

* add tier datasource, datasource filter, id string validator, tests (#219)

* 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

* add filter datasource, with tests

* fix error message

---------

Co-authored-by: Kyle <[email protected]>
  • Loading branch information
davidbloss and rocktavious authored Mar 19, 2024
1 parent d29083a commit 43321bb
Show file tree
Hide file tree
Showing 7 changed files with 206 additions and 85 deletions.
7 changes: 4 additions & 3 deletions opslevel/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -325,13 +325,14 @@ func timeLastUpdated() string {
// return output
// }

type FilterModel struct {
// FilterBlockModel models data for a terraform block - used to filter resources
type FilterBlockModel struct {
Field types.String `tfsdk:"field"`
Value types.String `tfsdk:"value"`
}

func NewFilterModel(field string, value string) FilterModel {
return FilterModel{
func NewFilterBlockModel(field string, value string) FilterBlockModel {
return FilterBlockModel{
Field: types.StringValue(string(field)),
Value: types.StringValue(string(value)),
}
Expand Down
186 changes: 111 additions & 75 deletions opslevel/datasource_opslevel_filter.go
Original file line number Diff line number Diff line change
@@ -1,77 +1,113 @@
package opslevel

// import (
// "fmt"

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

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

// func datasourceFilter() *schema.Resource {
// return &schema.Resource{
// Read: wrap(datasourceFilterRead),
// Schema: map[string]*schema.Schema{
// "filter": getDatasourceFilter(true, []string{"id", "name"}),
// "name": {
// Type: schema.TypeString,
// Computed: true,
// },
// },
// }
// }

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

// var output opslevel.Filter
// found := false
// for _, item := range data {
// switch field {
// case "id":
// if string(item.Id) == value {
// 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 filter with: %s==%s", field, value)
// }
// return &output, nil
// }

// func datasourceFilterRead(d *schema.ResourceData, client *opslevel.Client) error {
// resp, err := client.ListFilters(nil)
// if err != nil {
// return err
// }
// if resp == nil {
// return fmt.Errorf("unexpected: listing filters returned nil")
// }
// results := resp.Nodes

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

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

// d.SetId(string(item.Id))
// d.Set("name", item.Name)

// return nil
// }
import (
"context"
"fmt"

"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 FilterDataSource implements DataSourceWithConfigure interface
var _ datasource.DataSourceWithConfigure = &FilterDataSource{}

func NewFilterDataSource() datasource.DataSource {
return &FilterDataSource{}
}

// FilterDataSource manages a Filter data source.
type FilterDataSource struct {
CommonDataSourceClient
}

// FilterDataSourceModel describes the data source data model.
type FilterDataSourceModel struct {
Filter FilterBlockModel `tfsdk:"filter"`
Id types.String `tfsdk:"id"`
Name types.String `tfsdk:"name"`
}

func NewFilterDataSourceModel(ctx context.Context, opslevelFilter opslevel.Filter, filterModel FilterBlockModel) FilterDataSourceModel {
return FilterDataSourceModel{
Name: types.StringValue(opslevelFilter.Name),
Id: types.StringValue(string(opslevelFilter.Id)),
Filter: filterModel,
}
}

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

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

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

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

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

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

opslevelFilter, err := filterOpsLevelFilters(opslevelFilters.Nodes, data.Filter)
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read filter datasource, got error: %s", err))
return
}

filterDataModel := NewFilterDataSourceModel(ctx, *opslevelFilter, data.Filter)

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

func filterOpsLevelFilters(opslevelFilters []opslevel.Filter, filter FilterBlockModel) (*opslevel.Filter, error) {
if filter.Value.Equal(types.StringValue("")) {
return nil, fmt.Errorf("please provide a non-empty value for filter's value")
}
for _, opslevelFilter := range opslevelFilters {
switch filter.Field.ValueString() {
case "id":
if filter.Value.Equal(types.StringValue(string(opslevelFilter.Id))) {
return &opslevelFilter, nil
}
case "name":
if filter.Value.Equal(types.StringValue(opslevelFilter.Name)) {
return &opslevelFilter, nil
}
}
}

return nil, fmt.Errorf("unable to find filter with: %s==%s", filter.Field, filter.Value)
}
14 changes: 7 additions & 7 deletions opslevel/datasource_opslevel_tier.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ type TierDataSource struct {

// 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"`
Alias types.String `tfsdk:"alias"`
Filter FilterBlockModel `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 {
func NewTierDataSourceModel(ctx context.Context, tier opslevel.Tier, filter FilterBlockModel) TierDataSourceModel {
return TierDataSourceModel{
Alias: types.StringValue(string(tier.Alias)),
Filter: filter,
Expand Down Expand Up @@ -107,7 +107,7 @@ func (d *TierDataSource) Read(ctx context.Context, req datasource.ReadRequest, r
}

// func filterTiers(levels []opslevel.Tier, field string, value string) (*opslevel.Tier, error) {
func filterTiers(tiers []opslevel.Tier, filter FilterModel) (*opslevel.Tier, error) {
func filterTiers(tiers []opslevel.Tier, filter FilterBlockModel) (*opslevel.Tier, error) {
if filter.Value.Equal(types.StringValue("")) {
return nil, fmt.Errorf("Please provide a non-empty value for filter's value")
}
Expand Down
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,
NewFilterDataSource,
NewTierDataSource,
}
}
Expand Down
21 changes: 21 additions & 0 deletions tests/data_sources.tf
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,27 @@ data "opslevel_domain" "mock_domain" {

data "opslevel_domains" "all" {}

data "opslevel_filter" "name_filter" {
filter {
field = "name"
value = "name-value"
}
}

data "opslevel_filter" "id_filter" {
filter {
field = "id"
value = "Z2lkOi8vb3BzbGV2ZWwvVGllci8yMTAw"
}
}

data "opslevel_filter" "mock_filter" {
filter {
field = "name"
value = "stuff"
}
}

data "opslevel_tier" "mock_tier" {
filter {
field = "alias"
Expand Down
55 changes: 55 additions & 0 deletions tests/datasource_filter.tftest.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
mock_provider "opslevel" {
alias = "fake"
source = "./mock_datasource"
}

run "datasource_filter_mocked_fields" {
providers = {
opslevel = opslevel.fake
}

assert {
condition = data.opslevel_filter.mock_filter.name == "mock-filter-name"
error_message = "wrong name in opslevel_filter mock"
}

assert {
condition = data.opslevel_filter.mock_filter.id != ""
error_message = "id in opslevel_filter mock was not set"
}

}

run "datasource_filter_filter_by_name" {
providers = {
opslevel = opslevel.fake
}

assert {
condition = data.opslevel_filter.name_filter.filter.field == "name"
error_message = "filter field for opslevel_filter.name_filter should be name"
}

assert {
condition = data.opslevel_filter.name_filter.filter.value == "name-value"
error_message = "filter value for opslevel_filter.name_filter should be name-value"
}

}

run "datasource_filter_filter_by_id" {
providers = {
opslevel = opslevel.fake
}

assert {
condition = data.opslevel_filter.id_filter.filter.field == "id"
error_message = "filter field should be id"
}

assert {
condition = data.opslevel_filter.id_filter.filter.value == "Z2lkOi8vb3BzbGV2ZWwvVGllci8yMTAw"
error_message = "filter value for opslevel_filter.id_filter should be Z2lkOi8vb3BzbGV2ZWwvVGllci8yMTAw"
}

}
7 changes: 7 additions & 0 deletions tests/mock_datasource/filter.tfmock.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
mock_data "opslevel_filter" {
defaults = {
name = "mock-filter-name"
# filter is not set here because its fields are not computed
# id intentionally omitted - will be assigned a random string
}
}

0 comments on commit 43321bb

Please sign in to comment.