diff --git a/go.mod b/go.mod index 7e650f7..f87e270 100644 --- a/go.mod +++ b/go.mod @@ -12,5 +12,5 @@ require ( github.com/hashicorp/terraform-plugin-sdk/v2 v2.3.0 github.com/nullstone-io/module v0.2.3 github.com/stretchr/testify v1.5.1 - gopkg.in/nullstone-io/go-api-client.v0 v0.0.0-20210419161255-60ff46ef629c + gopkg.in/nullstone-io/go-api-client.v0 v0.0.0-20210422145113-1def18026731 ) diff --git a/go.sum b/go.sum index 25eebd0..c8a6fb7 100644 --- a/go.sum +++ b/go.sum @@ -572,8 +572,8 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8X gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/nullstone-io/go-api-client.v0 v0.0.0-20210419161255-60ff46ef629c h1:X7CI5hCviuyx66WuO3Hcic4DH+o/fFVKKRuZdHBpneg= -gopkg.in/nullstone-io/go-api-client.v0 v0.0.0-20210419161255-60ff46ef629c/go.mod h1:6z3xyBE0tWngWi5equdUPGtEKClza3g5hpSkQKWLid8= +gopkg.in/nullstone-io/go-api-client.v0 v0.0.0-20210422145113-1def18026731 h1:Xvwk1m7Zqjl+pueoSGlh9ylN0fv1xCVMsMQvqRO5DdA= +gopkg.in/nullstone-io/go-api-client.v0 v0.0.0-20210422145113-1def18026731/go.mod h1:6z3xyBE0tWngWi5equdUPGtEKClza3g5hpSkQKWLid8= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/internal/provider/data_domain.go b/internal/provider/data_domain.go new file mode 100644 index 0000000..00154d9 --- /dev/null +++ b/internal/provider/data_domain.go @@ -0,0 +1,96 @@ +package provider + +import ( + "context" + "fmt" + "github.com/hashicorp/terraform-plugin-go/tfprotov5" + "github.com/hashicorp/terraform-plugin-go/tfprotov5/tftypes" + "gopkg.in/nullstone-io/go-api-client.v0" + "strconv" +) + +type dataDomain struct { + p *provider +} + +func newDataDomain(p *provider) (*dataDomain, error) { + if p == nil { + return nil, fmt.Errorf("a provider is required") + } + return &dataDomain{p: p}, nil +} + +func (*dataDomain) Schema(ctx context.Context) *tfprotov5.Schema { + return &tfprotov5.Schema{ + Version: 1, + Block: &tfprotov5.SchemaBlock{ + Description: "Data source to read a nullstone domain.", + DescriptionKind: tfprotov5.StringKindMarkdown, + Attributes: []*tfprotov5.SchemaAttribute{ + deprecatedIDAttribute(), + { + Name: "stack", + Type: tftypes.String, + Description: "The domain belongs to this stack", + Required: true, + DescriptionKind: tfprotov5.StringKindMarkdown, + }, + { + Name: "block", + Type: tftypes.String, + Description: "The domain belongs to this block (in the specified stack)", + Required: true, + DescriptionKind: tfprotov5.StringKindMarkdown, + }, + { + Name: "dns_name", + Type: tftypes.String, + Description: "The DNS name defined on the domain", + Computed: true, + DescriptionKind: tfprotov5.StringKindMarkdown, + }, + }, + }, + } +} + +func (d *dataDomain) Validate(ctx context.Context, config map[string]tftypes.Value) ([]*tfprotov5.Diagnostic, error) { + return nil, nil +} + +func (d *dataDomain) Read(ctx context.Context, config map[string]tftypes.Value) (map[string]tftypes.Value, []*tfprotov5.Diagnostic, error) { + nsConfig := d.p.NsConfig + nsClient := api.Client{Config: nsConfig} + + diags := make([]*tfprotov5.Diagnostic, 0) + + stack := extractStringFromConfig(config, "stack") + block := extractStringFromConfig(config, "block") + + var domainId int + var dnsName string + + domain, err := nsClient.Domains().Get(stack, block) + if err != nil { + diags = append(diags, &tfprotov5.Diagnostic{ + Severity: tfprotov5.DiagnosticSeverityError, + Summary: "Unable to find nullstone domain.", + Detail: err.Error(), + }) + } else if domain != nil { + domainId = domain.Id + dnsName = domain.DnsName + } else { + diags = append(diags, &tfprotov5.Diagnostic{ + Severity: tfprotov5.DiagnosticSeverityError, + Summary: fmt.Sprintf("The domain in the stack %q and block %q does not exist in nullstone.", stack, block), + }) + } + + return map[string]tftypes.Value{ + "id": tftypes.NewValue(tftypes.String, strconv.Itoa(domainId)), + "stack": tftypes.NewValue(tftypes.String, stack), + "block": tftypes.NewValue(tftypes.String, block), + "dns_name": tftypes.NewValue(tftypes.String, dnsName), + }, diags, nil +} diff --git a/internal/provider/data_domain_test.go b/internal/provider/data_domain_test.go new file mode 100644 index 0000000..34b2c40 --- /dev/null +++ b/internal/provider/data_domain_test.go @@ -0,0 +1,72 @@ +package provider + +import ( + "fmt" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "net/http" + "regexp" + "testing" +) + +func TestDataDomain(t *testing.T) { + t.Run("fails to find non-existent domain", func(t *testing.T) { + tfconfig := fmt.Sprintf(` +provider "ns" { + organization = "org0" +} +data "ns_domain" "domain" { + stack = "global" + block = "nullstone-io" +} +`) + + checks := resource.ComposeTestCheckFunc() + + getNsConfig, closeNsFn := mockNs(http.NotFoundHandler()) + defer closeNsFn() + getTfeConfig, _ := mockTfe(nil) + + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: protoV5ProviderFactories(getNsConfig, getTfeConfig), + Steps: []resource.TestStep{ + { + Config: tfconfig, + Check: checks, + ExpectError: regexp.MustCompile(`The domain in the stack "global" and block "nullstone-io" does not exist in nullstone.`), + }, + }, + }) + }) + + t.Run("sets up attributes properly", func(t *testing.T) { + tfconfig := fmt.Sprintf(` +provider "ns" { + organization = "org0" +} +data "ns_domain" "domain" { + stack = "global" + block = "nullstone-io" +} +`) + + checks := resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.ns_domain.domain", `stack`, "global"), + resource.TestCheckResourceAttr("data.ns_domain.domain", `block`, "nullstone-io"), + resource.TestCheckResourceAttr("data.ns_domain.domain", `dns_name`, "nullstone.io"), + ) + + getNsConfig, closeNsFn := mockNs(mockNsServerWithDomains()) + defer closeNsFn() + getTfeConfig, _ := mockTfe(nil) + + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: protoV5ProviderFactories(getNsConfig, getTfeConfig), + Steps: []resource.TestStep{ + { + Config: tfconfig, + Check: checks, + }, + }, + }) + }) +} diff --git a/internal/provider/data_subdomain.go b/internal/provider/data_subdomain.go new file mode 100644 index 0000000..fb29c7c --- /dev/null +++ b/internal/provider/data_subdomain.go @@ -0,0 +1,96 @@ +package provider + +import ( + "context" + "fmt" + "github.com/hashicorp/terraform-plugin-go/tfprotov5" + "github.com/hashicorp/terraform-plugin-go/tfprotov5/tftypes" + "gopkg.in/nullstone-io/go-api-client.v0" + "strconv" +) + +type dataSubdomain struct { + p *provider +} + +func newDataSubdomain(p *provider) (*dataSubdomain, error) { + if p == nil { + return nil, fmt.Errorf("a provider is required") + } + return &dataSubdomain{p: p}, nil +} + +func (*dataSubdomain) Schema(ctx context.Context) *tfprotov5.Schema { + return &tfprotov5.Schema{ + Version: 1, + Block: &tfprotov5.SchemaBlock{ + Description: "Data source to read a nullstone subdomain.", + DescriptionKind: tfprotov5.StringKindMarkdown, + Attributes: []*tfprotov5.SchemaAttribute{ + deprecatedIDAttribute(), + { + Name: "stack", + Type: tftypes.String, + Description: "The subdomain belongs to this stack", + Required: true, + DescriptionKind: tfprotov5.StringKindMarkdown, + }, + { + Name: "block", + Type: tftypes.String, + Description: "The subdomain belongs to this block (in the specified stack)", + Required: true, + DescriptionKind: tfprotov5.StringKindMarkdown, + }, + { + Name: "dns_name", + Type: tftypes.String, + Description: "The DNS name defined on the subdomain", + Computed: true, + DescriptionKind: tfprotov5.StringKindMarkdown, + }, + }, + }, + } +} + +func (d *dataSubdomain) Validate(ctx context.Context, config map[string]tftypes.Value) ([]*tfprotov5.Diagnostic, error) { + return nil, nil +} + +func (d *dataSubdomain) Read(ctx context.Context, config map[string]tftypes.Value) (map[string]tftypes.Value, []*tfprotov5.Diagnostic, error) { + nsConfig := d.p.NsConfig + nsClient := api.Client{Config: nsConfig} + + diags := make([]*tfprotov5.Diagnostic, 0) + + stack := extractStringFromConfig(config, "stack") + block := extractStringFromConfig(config, "block") + + var subdomainId int + var dnsName string + + subdomain, err := nsClient.Subdomains().Get(stack, block) + if err != nil { + diags = append(diags, &tfprotov5.Diagnostic{ + Severity: tfprotov5.DiagnosticSeverityError, + Summary: "Unable to find nullstone subdomain.", + Detail: err.Error(), + }) + } else if subdomain != nil { + subdomainId = subdomain.Id + dnsName = subdomain.DnsName + } else { + diags = append(diags, &tfprotov5.Diagnostic{ + Severity: tfprotov5.DiagnosticSeverityError, + Summary: fmt.Sprintf("The subdomain in the stack %q and block %q does not exist in nullstone.", stack, block), + }) + } + + return map[string]tftypes.Value{ + "id": tftypes.NewValue(tftypes.String, strconv.Itoa(subdomainId)), + "stack": tftypes.NewValue(tftypes.String, stack), + "block": tftypes.NewValue(tftypes.String, block), + "dns_name": tftypes.NewValue(tftypes.String, dnsName), + }, diags, nil +} diff --git a/internal/provider/data_subdomain_test.go b/internal/provider/data_subdomain_test.go new file mode 100644 index 0000000..bfd3129 --- /dev/null +++ b/internal/provider/data_subdomain_test.go @@ -0,0 +1,72 @@ +package provider + +import ( + "fmt" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "net/http" + "regexp" + "testing" +) + +func TestDataSubdomain(t *testing.T) { + t.Run("fails to find non-existent subdomain", func(t *testing.T) { + tfconfig := fmt.Sprintf(` +provider "ns" { + organization = "org0" +} +data "ns_subdomain" "subdomain" { + stack = "demo" + block = "api-subdomain" +} +`) + + checks := resource.ComposeTestCheckFunc() + + getNsConfig, closeNsFn := mockNs(http.NotFoundHandler()) + defer closeNsFn() + getTfeConfig, _ := mockTfe(nil) + + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: protoV5ProviderFactories(getNsConfig, getTfeConfig), + Steps: []resource.TestStep{ + { + Config: tfconfig, + Check: checks, + ExpectError: regexp.MustCompile(`The subdomain in the stack "demo" and block "api-subdomain" does not exist in nullstone.`), + }, + }, + }) + }) + + t.Run("sets up attributes properly", func(t *testing.T) { + tfconfig := fmt.Sprintf(` +provider "ns" { + organization = "org0" +} +data "ns_subdomain" "subdomain" { + stack = "demo" + block = "api-subdomain" +} +`) + + checks := resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.ns_subdomain.subdomain", `stack`, "demo"), + resource.TestCheckResourceAttr("data.ns_subdomain.subdomain", `block`, "api-subdomain"), + resource.TestCheckResourceAttr("data.ns_subdomain.subdomain", `dns_name`, "api"), + ) + + getNsConfig, closeNsFn := mockNs(mockNsServerWithSubdomains()) + defer closeNsFn() + getTfeConfig, _ := mockTfe(nil) + + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: protoV5ProviderFactories(getNsConfig, getTfeConfig), + Steps: []resource.TestStep{ + { + Config: tfconfig, + Check: checks, + }, + }, + }) + }) +} diff --git a/internal/provider/domain_test.go b/internal/provider/domain_test.go new file mode 100644 index 0000000..a412b3a --- /dev/null +++ b/internal/provider/domain_test.go @@ -0,0 +1,25 @@ +package provider + +import ( + "encoding/json" + "github.com/gorilla/mux" + "gopkg.in/nullstone-io/go-api-client.v0/types" + "net/http" +) + +func mockNsServerWithDomains() http.Handler { + router := mux.NewRouter() + router. + Methods(http.MethodGet). + Path("/orgs/{orgName}/stacks/{stackName}/domains/{domainName}"). + HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + domain := types.Domain{ + DnsName: "nullstone.io", + OrgName: "org0", + StackName: "global", + } + raw, _ := json.Marshal(domain) + w.Write(raw) + }) + return router +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 6371890..e115322 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -33,6 +33,10 @@ func New(version string, getNsConfig func() api.Config, getTfeConfig func() *tfe // data sources s.MustRegisterDataSource("ns_workspace", newDataWorkspace) s.MustRegisterDataSource("ns_connection", newDataConnection) + s.MustRegisterDataSource("ns_subdomain", newDataSubdomain) + s.MustRegisterDataSource("ns_domain", newDataDomain) + + // resources s.MustRegisterDataSource("ns_autogen_subdomain", newDataAutogenSubdomain) s.MustRegisterResource("ns_autogen_subdomain", newResourceAutogenSubdomain) s.MustRegisterResource("ns_autogen_subdomain_delegation", newResourceAutogenSubdomainDelegation) diff --git a/internal/provider/subdomain_test.go b/internal/provider/subdomain_test.go new file mode 100644 index 0000000..80618c8 --- /dev/null +++ b/internal/provider/subdomain_test.go @@ -0,0 +1,25 @@ +package provider + +import ( + "encoding/json" + "github.com/gorilla/mux" + "gopkg.in/nullstone-io/go-api-client.v0/types" + "net/http" +) + +func mockNsServerWithSubdomains() http.Handler { + router := mux.NewRouter() + router. + Methods(http.MethodGet). + Path("/orgs/{orgName}/stacks/{stackName}/subdomains/{subdomainName}"). + HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + subdomain := types.Subdomain{ + DnsName: "api", + OrgName: "org0", + StackName: "demo", + } + raw, _ := json.Marshal(subdomain) + w.Write(raw) + }) + return router +}