Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add instance_port and permissions to the Application resource, allow updating applications. #15

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
5 changes: 4 additions & 1 deletion Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 23 additions & 9 deletions spinnaker/api/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ func GetApplication(client *gate.GatewayClient, applicationName string, dest int
if resp.StatusCode == http.StatusNotFound {
return fmt.Errorf("Application '%s' not found\n", applicationName)
} else if resp.StatusCode != http.StatusOK {
return fmt.Errorf("Encountered an error getting application, status code: %d\n", resp.StatusCode)
return fmt.Errorf("Encountered an error getting application: %s status code: %d\n", err, resp.StatusCode)
}
}

Expand All @@ -31,16 +31,10 @@ func GetApplication(client *gate.GatewayClient, applicationName string, dest int
return nil
}

func CreateApplication(client *gate.GatewayClient, applicationName, email string) error {

app := map[string]interface{}{
"instancePort": 80,
"name": applicationName,
"email": email,
}
func CreateApplication(client *gate.GatewayClient, applicationName string, application interface{}) error {

createAppTask := map[string]interface{}{
"job": []interface{}{map[string]interface{}{"type": "createApplication", "application": app}},
"job": []interface{}{map[string]interface{}{"type": "createApplication", "application": application}},
"application": applicationName,
"description": fmt.Sprintf("Create Application: %s", applicationName),
}
Expand Down Expand Up @@ -74,6 +68,26 @@ func CreateApplication(client *gate.GatewayClient, applicationName, email string
return fmt.Errorf("Encountered an error saving application, task output was: %v\n", task)
}

// HACK:
// When creating an application with group permissions terraform fails to
// read the application due to a HTTP 403. It looks like the permissions
// are set without refreshing the cache.
//
// It looks like the responses of the gate API are cached.
// Try accessing the application until the cache timeout is reached
// and the API allows access to the application.
// The default redis cache timeout is 30 seconds.
attempts = 0
for attempts < 10 {
_, resp, _ := client.ApplicationControllerApi.GetApplicationUsingGET(client.Context, applicationName, map[string]interface{}{})
if resp != nil && resp.StatusCode != 403 {
break
}

attempts += 1
time.Sleep(time.Duration(10) * time.Second)
}

return nil
}

Expand Down
109 changes: 98 additions & 11 deletions spinnaker/resource_application.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

"github.com/armory-io/terraform-provider-spinnaker/spinnaker/api"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/helper/validation"
)

func resourceApplication() *schema.Resource {
Expand All @@ -13,11 +14,27 @@ func resourceApplication() *schema.Resource {
"application": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"email": {
Type: schema.TypeString,
Required: true,
},
"instance_port": {
Type: schema.TypeInt,
Required: false,
Optional: true,
Default: 80,
ValidateFunc: validation.IntBetween(1, 65535),
},
"permissions": {
Type: schema.TypeMap,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{"read", "write", "read_write"}, false),
},
},
},
Create: resourceApplicationCreate,
Read: resourceApplicationRead,
Expand All @@ -27,20 +44,32 @@ func resourceApplication() *schema.Resource {
}
}

// application represents the Gate API schema
//
// HINT: to extend this schema have a look at the output
// of the spin (https://github.com/spinnaker/spin)
// application get command.
type application struct {
Name string `json:"name"`
Email string `json:"email"`
InstancePort int `json:"instancePort"`
Permissions map[string][]string `json:"permissions,omitempty"`
}

// applicationRead represents the Gate API schema of an application
// get request. The relevenat part of the schema is identical with
// the application struct, it's just wrapped in an attributes field.
type applicationRead struct {
Name string `json:"name"`
Attributes struct {
Email string `json:"email"`
} `json:"attributes"`
Name string `json:"name"`
Attributes *application `json:"attributes"`
}

func resourceApplicationCreate(data *schema.ResourceData, meta interface{}) error {
clientConfig := meta.(gateConfig)
client := clientConfig.client
application := data.Get("application").(string)
email := data.Get("email").(string)

if err := api.CreateApplication(client, application, email); err != nil {
app := applicationFromResource(data)
if err := api.CreateApplication(client, app.Name, app); err != nil {
return err
}

Expand All @@ -50,17 +79,27 @@ func resourceApplicationCreate(data *schema.ResourceData, meta interface{}) erro
func resourceApplicationRead(data *schema.ResourceData, meta interface{}) error {
clientConfig := meta.(gateConfig)
client := clientConfig.client

applicationName := data.Get("application").(string)
var app applicationRead
if err := api.GetApplication(client, applicationName, &app); err != nil {
app := &applicationRead{}
if err := api.GetApplication(client, applicationName, app); err != nil {
return err
}

return readApplication(data, app)
}

func resourceApplicationUpdate(data *schema.ResourceData, meta interface{}) error {
return nil
// the application update in spinnaker is an simple upsert
clientConfig := meta.(gateConfig)
client := clientConfig.client

app := applicationFromResource(data)
if err := api.CreateApplication(client, app.Name, app); err != nil {
return err
}

return resourceApplicationRead(data, meta)
}

func resourceApplicationDelete(data *schema.ResourceData, meta interface{}) error {
Expand Down Expand Up @@ -92,7 +131,55 @@ func resourceApplicationExists(data *schema.ResourceData, meta interface{}) (boo
return true, nil
}

func readApplication(data *schema.ResourceData, application applicationRead) error {
func applicationFromResource(data *schema.ResourceData) *application {
app := &application{
Name: data.Get("application").(string),
Email: data.Get("email").(string),
InstancePort: data.Get("instance_port").(int),
Permissions: make(map[string][]string),
}

// convert {"team_name": "read_write"} to {"READ": ["team_name"], "WRITE": ["team_name"]}
// for the spinnaker API
readPerms := []string{}
writePerms := []string{}
for team, permI := range data.Get("permissions").(map[string]interface{}) {
perm := permI.(string)
if strings.HasPrefix(perm, "read") {
readPerms = append(readPerms, team)
}
if strings.HasSuffix(perm, "write") {
writePerms = append(writePerms, team)
}

}
app.Permissions["READ"] = readPerms
app.Permissions["WRITE"] = writePerms

return app
}

func readApplication(data *schema.ResourceData, application *applicationRead) error {
data.SetId(application.Name)
data.Set("name", application.Name)
data.Set("email", application.Attributes.Email)
data.Set("instance_port", application.Attributes.InstancePort)

// convert {"READ": ["team_name"], "WRITE": ["team_name"]} to {"team_name": "read_write"}
// for the spinnaker API
perms := make(map[string]string)
for _, team := range application.Attributes.Permissions["READ"] {
perms[team] = "read"
}
for _, team := range application.Attributes.Permissions["WRITE"] {
perm, ok := perms[team]
if ok {
// perms contains "read", append undescore to create "read_write"
perm += "_"
}
perm += "write"
perms[team] = perm
}
data.Set("permissions", perms)
return nil
}