Skip to content

Commit

Permalink
221 Autogen subdomain (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
BSick7 authored Mar 8, 2021
1 parent 889cdec commit 6de356e
Show file tree
Hide file tree
Showing 15 changed files with 855 additions and 63 deletions.
114 changes: 114 additions & 0 deletions internal/provider/autogen_subdomain_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package provider

import (
"encoding/json"
"fmt"
"github.com/gorilla/mux"
"github.com/nullstone-io/terraform-provider-ns/ns"
"net/http"
)

func mockNsServerWithAutogenSubdomains(subdomains map[string]map[string]*ns.AutogenSubdomain, delegations map[string]map[string]*ns.AutogenSubdomainDelegation) http.Handler {
findSubdomain := func(orgName, subdomainName string) *ns.AutogenSubdomain {
orgSubdomains, ok := subdomains[orgName]
if !ok {
return nil
}
subdomain, ok := orgSubdomains[subdomainName]
if !ok {
return nil
}
return subdomain
}
findDelegation := func(orgName, subdomainName string) *ns.AutogenSubdomainDelegation {
orgDelegations, ok := delegations[orgName]
if !ok {
return nil
}
delegation, ok := orgDelegations[subdomainName]
if !ok {
return nil
}
return delegation
}

router := mux.NewRouter()
router.
Methods(http.MethodGet).
Path("/orgs/{orgName}/autogen_subdomains/{subdomainName}").
HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
orgName, subdomainName := vars["orgName"], vars["subdomainName"]
subdomain := findSubdomain(orgName, subdomainName)
if subdomain != nil {
raw, _ := json.Marshal(subdomain)
w.Write(raw)
} else {
http.NotFound(w, r)
}
})
router.
Methods(http.MethodGet).
Path("/orgs/{orgName}/autogen_subdomains/{subdomainName}/delegation").
HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
orgName, subdomainName := vars["orgName"], vars["subdomainName"]
delegation := findDelegation(orgName, subdomainName)
if delegation != nil {
raw, _ := json.Marshal(delegation)
w.Write(raw)
return
} else {
http.NotFound(w, r)
}
})
router.
Methods(http.MethodPut).
Path("/orgs/{orgName}/autogen_subdomains/{subdomainName}/delegation").
Headers("Content-Type", "application/json").
HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
orgName, subdomainName := vars["orgName"], vars["subdomainName"]
if subdomain := findSubdomain(orgName, subdomainName); subdomain == nil {
http.NotFound(w, r)
return
}
if _, ok := delegations[orgName]; !ok {
delegations[orgName] = map[string]*ns.AutogenSubdomainDelegation{}
}

if r.Body == nil {
http.Error(w, "invalid body", http.StatusInternalServerError)
return
}
defer r.Body.Close()
decoder := json.NewDecoder(r.Body)
var delegation ns.AutogenSubdomainDelegation
if err := decoder.Decode(&delegation); err != nil {
http.Error(w, fmt.Sprintf("invalid body: %s", err), http.StatusInternalServerError)
return
}

delegations[orgName][subdomainName] = &delegation
raw, _ := json.Marshal(delegation)
w.Write(raw)
})
router.
Methods(http.MethodDelete).
Path("/orgs/{orgName}/autogen_subdomains/{subdomainName}/delegation").
HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
orgName, subdomainName := vars["orgName"], vars["subdomainName"]
if subdomain := findSubdomain(orgName, subdomainName); subdomain == nil {
http.NotFound(w, r)
return
}
if _, ok := delegations[orgName]; !ok {
delegations[orgName] = map[string]*ns.AutogenSubdomainDelegation{}
}

delegations[orgName][subdomainName] = &ns.AutogenSubdomainDelegation{Nameservers: []string{}}
w.WriteHeader(http.StatusNoContent)
})
return router
}
21 changes: 21 additions & 0 deletions internal/provider/config_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,24 @@ func boolFromConfig(config map[string]tftypes.Value, key string) bool {
config[key].As(&val)
return val
}

func stringSliceFromConfig(config map[string]tftypes.Value, key string) ([]string, error) {
if config[key].IsNull() {
return make([]string, 0), nil
}

tfslice := make([]tftypes.Value, 0)
if err := config[key].As(&tfslice); err != nil {
return nil, err
}

slice := make([]string, 0)
for _, tfitem := range tfslice {
var item string
if err := tfitem.As(&item); err != nil {
return nil, err
}
slice = append(slice, item)
}
return slice, nil
}
92 changes: 92 additions & 0 deletions internal/provider/data_autogen_subdomain.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package provider

import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
"github.com/hashicorp/terraform-plugin-go/tfprotov5/tftypes"
"github.com/nullstone-io/terraform-provider-ns/internal/server"
"strings"
)

type dataAutogenSubdomain struct {
p *provider
}

func newDataAutogenSubdomain(p *provider) (*dataAutogenSubdomain, error) {
if p == nil {
return nil, fmt.Errorf("a provider is required")
}
return &dataAutogenSubdomain{p: p}, nil
}

var (
_ server.DataSource = (*dataAutogenSubdomain)(nil)
)

func (*dataAutogenSubdomain) Schema(ctx context.Context) *tfprotov5.Schema {
return &tfprotov5.Schema{
Version: 1,
Block: &tfprotov5.SchemaBlock{
Description: "Data source to configure a connection to another nullstone workspace.",
DescriptionKind: tfprotov5.StringKindMarkdown,
Attributes: []*tfprotov5.SchemaAttribute{
deprecatedIDAttribute(),
{
Name: "name",
Type: tftypes.String,
Required: true,
Description: "The name of the autogenerated subdomain.",
DescriptionKind: tfprotov5.StringKindMarkdown,
},
{
Name: "domain_name",
Type: tftypes.String,
Computed: true,
Description: "The domain name that nullstone manages for this autogenerated subdomain. It is usually `nullstone.app`.",
DescriptionKind: tfprotov5.StringKindMarkdown,
},
{
Name: "fqdn",
Type: tftypes.String,
Computed: true,
Description: "The fully-qualified domain name (FQDN) that nullstone manages for this autogenerated subdomain. It is composed as `{name}.{domain_name}.`.",
DescriptionKind: tfprotov5.StringKindMarkdown,
},
},
},
}
}

func (d *dataAutogenSubdomain) Validate(ctx context.Context, config map[string]tftypes.Value) ([]*tfprotov5.Diagnostic, error) {
return nil, nil
}

func (d *dataAutogenSubdomain) Read(ctx context.Context, config map[string]tftypes.Value) (map[string]tftypes.Value, []*tfprotov5.Diagnostic, error) {
name := stringFromConfig(config, "name")

state := map[string]tftypes.Value{
"id": tftypes.NewValue(tftypes.String, name),
"name": tftypes.NewValue(tftypes.String, name),
}
diags := make([]*tfprotov5.Diagnostic, 0)

subdomain, err := d.p.NsClient.GetAutogenSubdomain(name)
if err != nil {
diags = append(diags, &tfprotov5.Diagnostic{
Severity: tfprotov5.DiagnosticSeverityError,
Summary: "error retrieving autogen subdomain",
Detail: err.Error(),
})
} else if subdomain == nil {
diags = append(diags, &tfprotov5.Diagnostic{
Severity: tfprotov5.DiagnosticSeverityError,
Summary: fmt.Sprintf("The autogen_subdomain %q is missing.", name),
})
} else {
state["domain_name"] = tftypes.NewValue(tftypes.String, subdomain.DomainName)
state["fqdn"] = tftypes.NewValue(tftypes.String, fmt.Sprintf("%s.%s.", subdomain.Name, strings.TrimSuffix(subdomain.DomainName, ".")))
}

return state, diags, nil
}
79 changes: 79 additions & 0 deletions internal/provider/data_autogen_subdomain_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package provider

import (
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/nullstone-io/terraform-provider-ns/ns"
"regexp"
"testing"
)

func TestDataAutogenSubdomain(t *testing.T) {
subdomains := map[string]map[string]*ns.AutogenSubdomain{
"org0": {
"api": {
Id: 1,
Name: "api",
DomainName: "nullstone.app",
},
},
}
delegations := map[string]map[string]*ns.AutogenSubdomainDelegation{}

t.Run("fails to find non-existent autogen_subdomain", func(t *testing.T) {
tfconfig := fmt.Sprintf(`
provider "ns" {
organization = "org0"
}
data "ns_autogen_subdomain" "subdomain" {
name = "docs"
}
`)

getNsConfig, closeNsFn := mockNs(mockNsServerWithAutogenSubdomains(subdomains, delegations))
defer closeNsFn()
getTfeConfig, _ := mockTfe(nil)

checks := resource.ComposeTestCheckFunc()
resource.UnitTest(t, resource.TestCase{
ProtoV5ProviderFactories: protoV5ProviderFactories(getNsConfig, getTfeConfig),
Steps: []resource.TestStep{
{
Config: tfconfig,
Check: checks,
ExpectError: regexp.MustCompile(`The autogen_subdomain "docs" is missing.`),
},
},
})
})

t.Run("sets up attributes properly", func(t *testing.T) {
tfconfig := fmt.Sprintf(`
provider "ns" {
organization = "org0"
}
data "ns_autogen_subdomain" "subdomain" {
name = "api"
}
`)
checks := resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("data.ns_autogen_subdomain.subdomain", `name`, "api"),
resource.TestCheckResourceAttr("data.ns_autogen_subdomain.subdomain", `domain_name`, "nullstone.app"),
resource.TestCheckResourceAttr("data.ns_autogen_subdomain.subdomain", `fqdn`, "api.nullstone.app."),
)

getNsConfig, closeNsFn := mockNs(mockNsServerWithAutogenSubdomains(subdomains, delegations))
defer closeNsFn()
getTfeConfig, _ := mockTfe(nil)

resource.UnitTest(t, resource.TestCase{
ProtoV5ProviderFactories: protoV5ProviderFactories(getNsConfig, getTfeConfig),
Steps: []resource.TestStep{
{
Config: tfconfig,
Check: checks,
},
},
})
})
}
37 changes: 10 additions & 27 deletions internal/provider/data_connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,33 +81,6 @@ Typically, this is set to data.ns_connection.other.name`,
}

func (d *dataConnection) Validate(ctx context.Context, config map[string]tftypes.Value) ([]*tfprotov5.Diagnostic, error) {
diags := make([]*tfprotov5.Diagnostic, 0)

var name string
if err := config["name"].As(&name); err != nil {
diags = append(diags, &tfprotov5.Diagnostic{
Severity: tfprotov5.DiagnosticSeverityError,
Summary: "ns_connection.name must be a string",
})
} else if !validConnectionName.Match([]byte(name)) {
diags = append(diags, &tfprotov5.Diagnostic{
Severity: tfprotov5.DiagnosticSeverityError,
Summary: "ns_connection.name can only contain the characters 'a'-'z', '0'-'9', '-', '_'",
})
}

var optional bool
if err := config["optional"].As(&optional); err != nil {
diags = append(diags, &tfprotov5.Diagnostic{
Severity: tfprotov5.DiagnosticSeverityError,
Summary: err.Error(),
})
}

if len(diags) > 0 {
return diags, nil
}

return nil, nil
}

Expand All @@ -119,6 +92,16 @@ func (d *dataConnection) Read(ctx context.Context, config map[string]tftypes.Val
workspaceId := ""

diags := make([]*tfprotov5.Diagnostic, 0)
if !validConnectionName.Match([]byte(name)) {
diags = append(diags, &tfprotov5.Diagnostic{
Severity: tfprotov5.DiagnosticSeverityError,
Summary: fmt.Sprintf("name (%s) can only contain the characters 'a'-'z', '0'-'9', '-', '_'", name),
})
}
if len(diags) > 0 {
return nil, diags, nil
}

outputsValue := tftypes.NewValue(tftypes.Map{AttributeType: tftypes.String}, map[string]tftypes.Value{})

workspace, err := d.getConnectionWorkspace(name, type_, via)
Expand Down
Loading

0 comments on commit 6de356e

Please sign in to comment.