-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
419 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
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" | ||
"gopkg.in/nullstone-io/go-api-client.v0/types" | ||
"strconv" | ||
) | ||
|
||
type dataAppEnv struct { | ||
p *provider | ||
} | ||
|
||
func newDataAppEnv(p *provider) (*dataAppEnv, error) { | ||
if p == nil { | ||
return nil, fmt.Errorf("a provider is required") | ||
} | ||
return &dataAppEnv{p: p}, nil | ||
} | ||
|
||
func (*dataAppEnv) Schema(ctx context.Context) *tfprotov5.Schema { | ||
return &tfprotov5.Schema{ | ||
Version: 1, | ||
Block: &tfprotov5.SchemaBlock{ | ||
Description: "Data source to configure module based on current nullstone workspace.", | ||
DescriptionKind: tfprotov5.StringKindMarkdown, | ||
Attributes: []*tfprotov5.SchemaAttribute{ | ||
deprecatedIDAttribute(), | ||
{ | ||
Name: "app", | ||
Type: tftypes.String, | ||
Required: true, | ||
Description: "The name of the application in nullstone.", | ||
DescriptionKind: tfprotov5.StringKindMarkdown, | ||
}, | ||
{ | ||
Name: "stack", | ||
Type: tftypes.String, | ||
Required: true, | ||
Description: "The owning stack for the application in nullstone.", | ||
DescriptionKind: tfprotov5.StringKindMarkdown, | ||
}, | ||
{ | ||
Name: "env", | ||
Type: tftypes.String, | ||
Required: true, | ||
Description: "The name of the environment in nullstone.", | ||
DescriptionKind: tfprotov5.StringKindMarkdown, | ||
}, | ||
{ | ||
Name: "version", | ||
Type: tftypes.String, | ||
Computed: true, | ||
Description: "The version configured for the application in the specific environment.", | ||
DescriptionKind: tfprotov5.StringKindMarkdown, | ||
}, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func (d *dataAppEnv) Validate(ctx context.Context, config map[string]tftypes.Value) ([]*tfprotov5.Diagnostic, error) { | ||
return nil, nil | ||
} | ||
|
||
func (d *dataAppEnv) Read(ctx context.Context, config map[string]tftypes.Value) (map[string]tftypes.Value, []*tfprotov5.Diagnostic, error) { | ||
appName := extractStringFromConfig(config, "app") | ||
stackName := extractStringFromConfig(config, "stack") | ||
envName := extractStringFromConfig(config, "env") | ||
diags := make([]*tfprotov5.Diagnostic, 0) | ||
|
||
var appEnvId int | ||
var appEnvVersion string | ||
|
||
nsClient := api.Client{Config: d.p.NsConfig} | ||
app, err := d.findApp(appName, stackName) | ||
if err != nil { | ||
diags = append(diags, &tfprotov5.Diagnostic{ | ||
Severity: tfprotov5.DiagnosticSeverityError, | ||
Summary: fmt.Sprintf("Unable to list applications."), | ||
Detail: err.Error(), | ||
}) | ||
} else if app == nil { | ||
diags = append(diags, &tfprotov5.Diagnostic{ | ||
Severity: tfprotov5.DiagnosticSeverityError, | ||
Summary: fmt.Sprintf("The application environment (app=%s, stack=%s, env=%s) is missing.", appName, stackName, envName), | ||
}) | ||
} else { | ||
appEnv, err := nsClient.AppEnvs().Get(app.Id, envName) | ||
if err != nil { | ||
diags = append(diags, &tfprotov5.Diagnostic{ | ||
Severity: tfprotov5.DiagnosticSeverityError, | ||
Summary: fmt.Sprintf("Unable to retrieve the application environment (app=%s, stack=%s, env=%s).", appName, stackName, envName), | ||
Detail: err.Error(), | ||
}) | ||
} else if appEnv == nil { | ||
diags = append(diags, &tfprotov5.Diagnostic{ | ||
Severity: tfprotov5.DiagnosticSeverityError, | ||
Summary: fmt.Sprintf("Unable to find the application environment (app=%s, stack=%s, env=%s).", appName, stackName, envName), | ||
Detail: err.Error(), | ||
}) | ||
} else { | ||
appEnvId = appEnv.Id | ||
appEnvVersion = appEnv.Version | ||
} | ||
} | ||
|
||
return map[string]tftypes.Value{ | ||
"id": tftypes.NewValue(tftypes.String, strconv.Itoa(appEnvId)), | ||
"app": tftypes.NewValue(tftypes.String, appName), | ||
"stack": tftypes.NewValue(tftypes.String, stackName), | ||
"env": tftypes.NewValue(tftypes.String, envName), | ||
"version": tftypes.NewValue(tftypes.String, appEnvVersion), | ||
}, diags, nil | ||
} | ||
|
||
func (d *dataAppEnv) findApp(appName, stackName string) (*types.Application, error) { | ||
nsClient := api.Client{Config: d.p.NsConfig} | ||
apps, err := nsClient.Apps().List() | ||
if err != nil { | ||
return nil, fmt.Errorf("Unable to list applications.") | ||
} | ||
for _, app := range apps { | ||
if app.Name == appName && app.StackName == stackName { | ||
return &app, nil | ||
} | ||
} | ||
return nil, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,243 @@ | ||
package provider | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"github.com/gorilla/mux" | ||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" | ||
"gopkg.in/nullstone-io/go-api-client.v0/types" | ||
"net/http" | ||
"strconv" | ||
"testing" | ||
) | ||
|
||
func TestDataAppEnv(t *testing.T) { | ||
core := &types.Stack{ | ||
IdModel: types.IdModel{ | ||
Id: 2, | ||
}, | ||
Name: "core", | ||
OrgName: "org0", | ||
} | ||
app1 := &types.Application{ | ||
IdModel: types.IdModel{ | ||
Id: 1, | ||
}, | ||
Name: "app1", | ||
OrgName: "org0", | ||
StackName: core.Name, | ||
} | ||
dev := &types.Environment{ | ||
IdModel: types.IdModel{ | ||
Id: 1, | ||
}, | ||
Name: "dev", | ||
OrgName: "org0", | ||
StackName: core.Name, | ||
} | ||
prod := &types.Environment{ | ||
IdModel: types.IdModel{ | ||
Id: 2, | ||
}, | ||
Name: "prod", | ||
OrgName: "org0", | ||
StackName: core.Name, | ||
} | ||
|
||
appEnvs := []*types.AppEnv{ | ||
{ | ||
IdModel: types.IdModel{ | ||
Id: 5, | ||
}, | ||
AppId: app1.Id, | ||
EnvId: dev.Id, | ||
Version: "1.0.0", | ||
App: app1, | ||
Env: prod, | ||
}, | ||
} | ||
apps := []*types.Application{app1} | ||
envs := []*types.Environment{dev,prod} | ||
|
||
t.Run("sets up attributes properly with new AppEnv", func(t *testing.T) { | ||
tfconfig := fmt.Sprintf(` | ||
provider "ns" { | ||
organization = "org0" | ||
} | ||
data "ns_workspace" "this" { | ||
stack = "core" | ||
block = "app1" | ||
env = "dev" | ||
} | ||
data "ns_app_env" "this" { | ||
app = data.ns_workspace.this.block | ||
stack = data.ns_workspace.this.stack | ||
env = data.ns_workspace.this.env | ||
} | ||
`) | ||
checks := resource.ComposeTestCheckFunc( | ||
resource.TestCheckResourceAttr("data.ns_app_env.this", `id`, "10"), | ||
resource.TestCheckResourceAttr("data.ns_app_env.this", `app`, "app1"), | ||
resource.TestCheckResourceAttr("data.ns_app_env.this", `stack`, "core"), | ||
resource.TestCheckResourceAttr("data.ns_app_env.this", `env`, "dev"), | ||
resource.TestCheckResourceAttr("data.ns_app_env.this", `version`, ""), | ||
) | ||
|
||
getNsConfig, closeNsFn := mockNs(mockNsHandlerAppEnvs(&appEnvs, apps, envs)) | ||
defer closeNsFn() | ||
getTfeConfig, closeTfeFn := mockTfe(nil) | ||
defer closeTfeFn() | ||
|
||
resource.UnitTest(t, resource.TestCase{ | ||
ProtoV5ProviderFactories: protoV5ProviderFactories(getNsConfig, getTfeConfig), | ||
Steps: []resource.TestStep{ | ||
{ | ||
Config: tfconfig, | ||
Check: checks, | ||
}, | ||
}, | ||
}) | ||
}) | ||
|
||
t.Run("sets up attributes properly with existing AppEnv", func(t *testing.T) { | ||
tfconfig := fmt.Sprintf(` | ||
provider "ns" { | ||
organization = "org0" | ||
} | ||
data "ns_workspace" "this" { | ||
stack = "core" | ||
block = "app1" | ||
env = "prod" | ||
} | ||
data "ns_app_env" "this" { | ||
app = data.ns_workspace.this.block | ||
stack = data.ns_workspace.this.stack | ||
env = data.ns_workspace.this.env | ||
} | ||
`) | ||
checks := resource.ComposeTestCheckFunc( | ||
resource.TestCheckResourceAttr("data.ns_app_env.this", `id`, "5"), | ||
resource.TestCheckResourceAttr("data.ns_app_env.this", `app`, "app1"), | ||
resource.TestCheckResourceAttr("data.ns_app_env.this", `stack`, "core"), | ||
resource.TestCheckResourceAttr("data.ns_app_env.this", `env`, "prod"), | ||
resource.TestCheckResourceAttr("data.ns_app_env.this", `version`, "1.0.0"), | ||
) | ||
|
||
getNsConfig, closeNsFn := mockNs(mockNsHandlerAppEnvs(&appEnvs, apps, envs)) | ||
defer closeNsFn() | ||
getTfeConfig, closeTfeFn := mockTfe(nil) | ||
defer closeTfeFn() | ||
|
||
resource.UnitTest(t, resource.TestCase{ | ||
ProtoV5ProviderFactories: protoV5ProviderFactories(getNsConfig, getTfeConfig), | ||
Steps: []resource.TestStep{ | ||
{ | ||
Config: tfconfig, | ||
Check: checks, | ||
}, | ||
}, | ||
}) | ||
}) | ||
} | ||
|
||
func mockNsHandlerAppEnvs(appEnvs *[]*types.AppEnv, apps []*types.Application, envs []*types.Environment) http.Handler { | ||
findApp := func(orgName, appIdStr string) *types.Application { | ||
for _, app := range apps { | ||
if app.OrgName == orgName && strconv.Itoa(app.Id) == appIdStr { | ||
return app | ||
} | ||
} | ||
return nil | ||
} | ||
findEnv := func(orgName, envName string) *types.Environment { | ||
for _, env := range envs { | ||
if env.OrgName == orgName && env.Name == envName { | ||
return env | ||
} | ||
} | ||
return nil | ||
} | ||
getAppEnv := func(orgName, appIdStr, envName string) *types.AppEnv { | ||
for _, existing := range *appEnvs { | ||
if existing.App.OrgName == orgName && strconv.Itoa(existing.App.Id) == appIdStr && | ||
existing.Env.OrgName == orgName && existing.Env.Name == envName { | ||
return existing | ||
} | ||
} | ||
return nil | ||
} | ||
addAppEnv := func(orgName, appIdStr, envName string) *types.AppEnv { | ||
app := findApp(orgName, appIdStr) | ||
env := findEnv(orgName, envName) | ||
if app == nil || env == nil { | ||
return nil | ||
} | ||
|
||
appEnv := &types.AppEnv{ | ||
IdModel: types.IdModel{ | ||
Id: 10, | ||
}, | ||
AppId: app.Id, | ||
EnvId: env.Id, | ||
Version: "", | ||
App: app, | ||
Env: env, | ||
} | ||
*appEnvs = append(*appEnvs, appEnv) | ||
return appEnv | ||
} | ||
findOrCreateEnv := func(r *http.Request) *types.AppEnv { | ||
vars := mux.Vars(r) | ||
orgName, appId, envName := vars["orgName"], vars["appId"], vars["envName"] | ||
|
||
appEnv := getAppEnv(orgName, appId, envName) | ||
if appEnv == nil { | ||
appEnv = addAppEnv(orgName, appId, envName) | ||
} | ||
return appEnv | ||
} | ||
|
||
router := mux.NewRouter() | ||
router. | ||
Methods(http.MethodGet). | ||
Path("/orgs/{orgName}/apps"). | ||
HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
raw, _ := json.Marshal(apps) | ||
w.Write(raw) | ||
}) | ||
router. | ||
Methods(http.MethodGet). | ||
Path("/orgs/{orgName}/apps/{appId}/envs/{envName}"). | ||
HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
if appEnv := findOrCreateEnv(r); appEnv == nil { | ||
http.NotFound(w, r) | ||
} else { | ||
raw, _ := json.Marshal(appEnv) | ||
w.Write(raw) | ||
} | ||
}) | ||
router. | ||
Methods(http.MethodPut). | ||
Path("/orgs/{orgName}/apps/{appId}/envs/{envName}"). | ||
HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
payload := struct{ | ||
Version string `json:"version"` | ||
}{} | ||
decoder := json.NewDecoder(r.Body) | ||
if appEnv := findOrCreateEnv(r); appEnv == nil { | ||
http.NotFound(w, r) | ||
} else if err := decoder.Decode(&payload); err != nil { | ||
w.WriteHeader(http.StatusInternalServerError) | ||
w.Write([]byte(err.Error())) | ||
} else { | ||
appEnv.Version = payload.Version | ||
raw, _ := json.Marshal(appEnv) | ||
w.Write(raw) | ||
} | ||
}) | ||
return router | ||
} |
Oops, something went wrong.