From 7f0b77b89558fb804fae7442a745815851e4d786 Mon Sep 17 00:00:00 2001 From: apollorion Date: Thu, 31 Oct 2024 12:10:55 -0400 Subject: [PATCH] feat: add new tool versions datasource --- docs/data-sources/tool_versions.md | 59 ++++++++++ .../spacelift_tool_versions/data-source.tf | 31 ++++++ spacelift/data_tool_versions.go | 102 ++++++++++++++++++ spacelift/data_tool_versions_test.go | 84 +++++++++++++++ spacelift/internal/testhelpers/helpers.go | 18 ++++ spacelift/provider.go | 1 + 6 files changed, 295 insertions(+) create mode 100644 docs/data-sources/tool_versions.md create mode 100644 examples/data-sources/spacelift_tool_versions/data-source.tf create mode 100644 spacelift/data_tool_versions.go create mode 100644 spacelift/data_tool_versions_test.go diff --git a/docs/data-sources/tool_versions.md b/docs/data-sources/tool_versions.md new file mode 100644 index 00000000..5c5b818e --- /dev/null +++ b/docs/data-sources/tool_versions.md @@ -0,0 +1,59 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "spacelift_tool_versions Data Source - terraform-provider-spacelift" +subcategory: "" +description: |- + Lists supported versions for a given tool. +--- + +# spacelift_tool_versions (Data Source) + +Lists supported versions for a given tool. + +## Example Usage + +```terraform +data "spacelift_tool_versions" "terraform" { + tool = "TERRAFORM_FOSS" +} + +output "terraform" { + value = data.spacelift_tool_versions.terraform.versions +} + +data "spacelift_tool_versions" "open_tofu" { + tool = "OPEN_TOFU" +} + +output "open_tofu" { + value = data.spacelift_tool_versions.open_tofu.versions +} + +data "spacelift_tool_versions" "kubectl" { + tool = "KUBECTL" +} + +output "kubectl" { + value = data.spacelift_tool_versions.kubectl.versions +} + +data "spacelift_tool_versions" "terragrunt" { + tool = "TERRAGRUNT" +} + +output "terragrunt" { + value = data.spacelift_tool_versions.terragrunt.versions +} +``` + + +## Schema + +### Required + +- `tool` (String) The tool to get a list of supported versions for. This can be one of `KUBECTL`, `OPEN_TOFU`, `TERRAFORM_FOSS`, or `TERRAGRUNT`. + +### Read-Only + +- `id` (String) The ID of this resource. +- `versions` (List of String) Supported versions of the given tool. diff --git a/examples/data-sources/spacelift_tool_versions/data-source.tf b/examples/data-sources/spacelift_tool_versions/data-source.tf new file mode 100644 index 00000000..39b4e230 --- /dev/null +++ b/examples/data-sources/spacelift_tool_versions/data-source.tf @@ -0,0 +1,31 @@ +data "spacelift_tool_versions" "terraform" { + tool = "TERRAFORM_FOSS" +} + +output "terraform" { + value = data.spacelift_tool_versions.terraform.versions +} + +data "spacelift_tool_versions" "open_tofu" { + tool = "OPEN_TOFU" +} + +output "open_tofu" { + value = data.spacelift_tool_versions.open_tofu.versions +} + +data "spacelift_tool_versions" "kubectl" { + tool = "KUBECTL" +} + +output "kubectl" { + value = data.spacelift_tool_versions.kubectl.versions +} + +data "spacelift_tool_versions" "terragrunt" { + tool = "TERRAGRUNT" +} + +output "terragrunt" { + value = data.spacelift_tool_versions.terragrunt.versions +} \ No newline at end of file diff --git a/spacelift/data_tool_versions.go b/spacelift/data_tool_versions.go new file mode 100644 index 00000000..635b3bd0 --- /dev/null +++ b/spacelift/data_tool_versions.go @@ -0,0 +1,102 @@ +package spacelift + +import ( + "context" + "slices" + + "github.com/hashicorp/go-cty/cty" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/spacelift-io/terraform-provider-spacelift/spacelift/internal" +) + +func dataToolVersions() *schema.Resource { + return &schema.Resource{ + Description: "Lists supported versions for a given tool.", + ReadContext: dataToolVersionsRead, + Schema: map[string]*schema.Schema{ + "tool": { + Type: schema.TypeString, + Description: "The tool to get a list of supported versions for. This can be one of `KUBECTL`, `OPEN_TOFU`, `TERRAFORM_FOSS`, or `TERRAGRUNT`.", + ValidateDiagFunc: dataToolVersionsValidateInput, + Required: true, + }, + "versions": { + Type: schema.TypeList, + Description: "Supported versions of the given tool.", + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Computed: true, + }, + }, + } +} + +func dataToolVersionsValidateInput(i interface{}, p cty.Path) diag.Diagnostics { + tool := i.(string) + + validTools := []string{ + "KUBECTL", + "OPEN_TOFU", + "TERRAFORM_FOSS", + "TERRAGRUNT", + } + + if !slices.Contains(validTools, tool) { + return diag.Errorf("tool must be one of %v", validTools) + } + + return nil +} + +func dataToolVersionsRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + tool := d.Get("tool").(string) + switch tool { + case "KUBECTL": + var query struct { + KubectlVersions []string `graphql:"kubectlVersions"` + } + if err := meta.(*internal.Client).Query(ctx, "ReadKubectlVersions", &query, nil); err != nil { + d.SetId("") + return diag.Errorf("could not query for tool: %v", err) + } + d.SetId("kubectl-versions") + d.Set("versions", query.KubectlVersions) + case "OPEN_TOFU": + var query struct { + OpenTofuVersions []string `graphql:"openTofuVersions"` + } + if err := meta.(*internal.Client).Query(ctx, "ReadOpenTofuVersions", &query, nil); err != nil { + d.SetId("") + return diag.Errorf("could not query for tool: %v", err) + } + d.SetId("open-tofu-versions") + d.Set("versions", query.OpenTofuVersions) + case "TERRAFORM_FOSS": + var query struct { + TerraformVersions []string `graphql:"terraformVersions"` + } + if err := meta.(*internal.Client).Query(ctx, "ReadTerraformVersions", &query, nil); err != nil { + d.SetId("") + return diag.Errorf("could not query for tool: %v", err) + } + d.SetId("terraform-foss-versions") + d.Set("versions", query.TerraformVersions) + case "TERRAGRUNT": + var query struct { + TerragruntVersions []string `graphql:"terragruntVersions"` + } + if err := meta.(*internal.Client).Query(ctx, "ReadTerragruntVersions", &query, nil); err != nil { + d.SetId("") + return diag.Errorf("could not query for tool: %v", err) + } + d.SetId("terragrunt-versions") + d.Set("versions", query.TerragruntVersions) + default: + return diag.Errorf("unsupported tool: %s", tool) + } + + return nil +} diff --git a/spacelift/data_tool_versions_test.go b/spacelift/data_tool_versions_test.go new file mode 100644 index 00000000..bf969537 --- /dev/null +++ b/spacelift/data_tool_versions_test.go @@ -0,0 +1,84 @@ +package spacelift + +import ( + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + + . "github.com/spacelift-io/terraform-provider-spacelift/spacelift/internal/testhelpers" +) + +func TestToolVersionsData(t *testing.T) { + testSteps(t, []resource.TestStep{ + { + Config: ` + data "spacelift_tool_versions" "kubectl" { + tool = "KUBECTL" + } + `, + Check: Resource( + "data.spacelift_tool_versions.kubectl", + Attribute("id", Contains("spacelift-versions")), + Attribute("tool", Equals("KUBECTL")), + SetLengthGreaterThanZero("versions"), + ), + }, + { + Config: ` + data "spacelift_tool_versions" "open_tofu" { + tool = "OPEN_TOFU" + } + `, + Check: Resource( + "data.spacelift_tool_versions.open_tofu", + Attribute("id", Contains("spacelift-versions")), + Attribute("tool", Equals("OPEN_TOFU")), + SetLengthGreaterThanZero("versions"), + ), + }, + { + Config: ` + data "spacelift_tool_versions" "terraform_foss" { + tool = "TERRAFORM_FOSS" + } + `, + Check: Resource( + "data.spacelift_tool_versions.terraform_foss", + Attribute("id", Contains("spacelift-versions")), + Attribute("tool", Equals("TERRAFORM_FOSS")), + SetLengthGreaterThanZero("versions"), + ), + }, + { + Config: ` + data "spacelift_tool_versions" "terragrunt" { + tool = "TERRAGRUNT" + } + `, + Check: Resource( + "data.spacelift_tool_versions.terragrunt", + Attribute("id", Contains("spacelift-versions")), + Attribute("tool", Equals("TERRAGRUNT")), + SetLengthGreaterThanZero("versions"), + ), + }, + }) + + t.Run("only allows specific tools", func(t *testing.T) { + re, err := regexp.Compile(`tool must be one of \[.*]`) + if err != nil { + t.Fatalf("could not compile regexp: %v", err) + } + testSteps(t, []resource.TestStep{ + { + Config: ` + data "spacelift_tool_versions" "test" { + tool = "this-tool-should-error" + } + `, + ExpectError: re, + }, + }) + }) +} diff --git a/spacelift/internal/testhelpers/helpers.go b/spacelift/internal/testhelpers/helpers.go index 6fc30148..b2ae20d8 100644 --- a/spacelift/internal/testhelpers/helpers.go +++ b/spacelift/internal/testhelpers/helpers.go @@ -301,6 +301,24 @@ func SetContains(name string, values ...string) AttributeCheck { } } +// SetLengthGreaterThanZero ensures the given attribute is a set and has a length greater than zero +func SetLengthGreaterThanZero(name string) AttributeCheck { + return func(attributes map[string]string) error { + + _, ok := attributes[fmt.Sprintf("%s.#", name)] + if !ok { + return errors.Errorf("%q does not appear to be a set", name) + } + + _, ok = attributes[fmt.Sprintf("%s.0", name)] + if !ok { + return errors.Errorf("%q has no length", name) + } + + return nil + } +} + // SetDoesNotContain checks the set does not contain any of the specified values func SetDoesNotContain(name string, values ...string) AttributeCheck { return func(attributes map[string]string) error { diff --git a/spacelift/provider.go b/spacelift/provider.go index 98094334..7b26d750 100644 --- a/spacelift/provider.go +++ b/spacelift/provider.go @@ -90,6 +90,7 @@ func Provider(commit, version string) plugin.ProviderFunc { "spacelift_scheduled_delete_stack": dataScheduledDeleteStack(), "spacelift_stack": dataStack(), "spacelift_stacks": dataStacks(), + "spacelift_tool_versions": dataToolVersions(), "spacelift_webhook": dataWebhook(), "spacelift_named_webhook": dataNamedWebhook(), "spacelift_stack_aws_role": dataStackAWSRole(), // deprecated