2016-12-05 16:29:41 +01:00
|
|
|
package rancher
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/hashicorp/terraform/helper/resource"
|
|
|
|
"github.com/hashicorp/terraform/helper/schema"
|
|
|
|
"github.com/hashicorp/terraform/helper/validation"
|
|
|
|
rancherClient "github.com/rancher/go-rancher/client"
|
|
|
|
)
|
|
|
|
|
|
|
|
func resourceRancherStack() *schema.Resource {
|
|
|
|
return &schema.Resource{
|
|
|
|
Create: resourceRancherStackCreate,
|
|
|
|
Read: resourceRancherStackRead,
|
|
|
|
Update: resourceRancherStackUpdate,
|
|
|
|
Delete: resourceRancherStackDelete,
|
|
|
|
Importer: &schema.ResourceImporter{
|
|
|
|
State: schema.ImportStatePassthrough,
|
|
|
|
},
|
|
|
|
|
|
|
|
Schema: map[string]*schema.Schema{
|
|
|
|
"id": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
"name": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
"description": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
"environment_id": {
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
"docker_compose": {
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
"rancher_compose": {
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
"environment": {
|
|
|
|
Type: schema.TypeMap,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
"catalog_id": {
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
"scope": {
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Default: "user",
|
|
|
|
Optional: true,
|
|
|
|
ValidateFunc: validation.StringInSlice([]string{"user", "system"}, true),
|
|
|
|
},
|
|
|
|
"start_on_create": {
|
|
|
|
Type: schema.TypeBool,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
"finish_upgrade": {
|
|
|
|
Type: schema.TypeBool,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
"rendered_docker_compose": {
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
"rendered_rancher_compose": {
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func resourceRancherStackCreate(d *schema.ResourceData, meta interface{}) error {
|
|
|
|
log.Printf("[INFO] Creating Stack: %s", d.Id())
|
|
|
|
client, err := meta.(*Config).EnvironmentClient(d.Get("environment_id").(string))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
data, err := makeStackData(d, meta)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var newStack rancherClient.Environment
|
|
|
|
if err := client.Create("environment", data, &newStack); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
stateConf := &resource.StateChangeConf{
|
|
|
|
Pending: []string{"activating", "active", "removed", "removing"},
|
|
|
|
Target: []string{"active"},
|
|
|
|
Refresh: StackStateRefreshFunc(client, newStack.Id),
|
|
|
|
Timeout: 10 * time.Minute,
|
|
|
|
Delay: 1 * time.Second,
|
|
|
|
MinTimeout: 3 * time.Second,
|
|
|
|
}
|
|
|
|
_, waitErr := stateConf.WaitForState()
|
|
|
|
if waitErr != nil {
|
|
|
|
return fmt.Errorf(
|
|
|
|
"Error waiting for stack (%s) to be created: %s", newStack.Id, waitErr)
|
|
|
|
}
|
|
|
|
|
|
|
|
d.SetId(newStack.Id)
|
|
|
|
log.Printf("[INFO] Stack ID: %s", d.Id())
|
|
|
|
|
|
|
|
return resourceRancherStackRead(d, meta)
|
|
|
|
}
|
|
|
|
|
|
|
|
func resourceRancherStackRead(d *schema.ResourceData, meta interface{}) error {
|
|
|
|
log.Printf("[INFO] Refreshing Stack: %s", d.Id())
|
2017-01-30 18:08:21 +01:00
|
|
|
client, err := meta.(*Config).EnvironmentClient(d.Get("environment_id").(string))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2016-12-05 16:29:41 +01:00
|
|
|
|
|
|
|
stack, err := client.Environment.ById(d.Id())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-01-31 14:04:59 +01:00
|
|
|
if stack == nil {
|
|
|
|
log.Printf("[INFO] Stack %s not found", d.Id())
|
|
|
|
d.SetId("")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-02-01 20:49:17 +01:00
|
|
|
if removed(stack.State) {
|
2017-01-31 10:58:16 +01:00
|
|
|
log.Printf("[INFO] Stack %s was removed on %v", d.Id(), stack.Removed)
|
|
|
|
d.SetId("")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-12-05 16:29:41 +01:00
|
|
|
config, err := client.Environment.ActionExportconfig(stack, &rancherClient.ComposeConfigInput{})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Printf("[INFO] Stack Name: %s", stack.Name)
|
|
|
|
|
|
|
|
d.Set("description", stack.Description)
|
|
|
|
d.Set("name", stack.Name)
|
2017-01-31 16:57:35 +01:00
|
|
|
dockerCompose := strings.Replace(config.DockerComposeConfig, "\r", "", -1)
|
|
|
|
rancherCompose := strings.Replace(config.RancherComposeConfig, "\r", "", -1)
|
2017-02-01 13:33:23 +01:00
|
|
|
|
|
|
|
catalogId := d.Get("catalog_id")
|
|
|
|
if catalogId == "" {
|
|
|
|
d.Set("docker_compose", dockerCompose)
|
|
|
|
d.Set("rancher_compose", rancherCompose)
|
|
|
|
}
|
2017-01-31 16:57:35 +01:00
|
|
|
d.Set("rendered_docker_compose", dockerCompose)
|
|
|
|
d.Set("rendered_rancher_compose", rancherCompose)
|
2016-12-05 16:29:41 +01:00
|
|
|
d.Set("environment_id", stack.AccountId)
|
|
|
|
d.Set("environment", stack.Environment)
|
|
|
|
|
|
|
|
if stack.ExternalId == "" {
|
|
|
|
d.Set("scope", "user")
|
|
|
|
d.Set("catalog_id", "")
|
|
|
|
} else {
|
|
|
|
trimmedID := strings.TrimPrefix(stack.ExternalId, "system-")
|
|
|
|
if trimmedID == stack.ExternalId {
|
|
|
|
d.Set("scope", "user")
|
|
|
|
} else {
|
|
|
|
d.Set("scope", "system")
|
|
|
|
}
|
|
|
|
d.Set("catalog_id", strings.TrimPrefix(trimmedID, "catalog://"))
|
|
|
|
}
|
|
|
|
|
|
|
|
d.Set("start_on_create", stack.StartOnCreate)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func resourceRancherStackUpdate(d *schema.ResourceData, meta interface{}) error {
|
|
|
|
log.Printf("[INFO] Updating Stack: %s", d.Id())
|
|
|
|
client, err := meta.(*Config).EnvironmentClient(d.Get("environment_id").(string))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
d.Partial(true)
|
|
|
|
|
|
|
|
data, err := makeStackData(d, meta)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
stack, err := client.Environment.ById(d.Id())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var newStack rancherClient.Environment
|
|
|
|
if err := client.Update("environment", &stack.Resource, data, &newStack); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
stateConf := &resource.StateChangeConf{
|
|
|
|
Pending: []string{"active", "active-updating"},
|
|
|
|
Target: []string{"active"},
|
|
|
|
Refresh: StackStateRefreshFunc(client, newStack.Id),
|
|
|
|
Timeout: 10 * time.Minute,
|
|
|
|
Delay: 1 * time.Second,
|
|
|
|
MinTimeout: 3 * time.Second,
|
|
|
|
}
|
|
|
|
s, waitErr := stateConf.WaitForState()
|
|
|
|
stack = s.(*rancherClient.Environment)
|
|
|
|
if waitErr != nil {
|
|
|
|
return fmt.Errorf(
|
|
|
|
"Error waiting for stack (%s) to be updated: %s", stack.Id, waitErr)
|
|
|
|
}
|
|
|
|
|
|
|
|
d.SetPartial("name")
|
|
|
|
d.SetPartial("description")
|
|
|
|
d.SetPartial("scope")
|
|
|
|
|
|
|
|
if d.HasChange("docker_compose") ||
|
|
|
|
d.HasChange("rancher_compose") ||
|
|
|
|
d.HasChange("environment") ||
|
|
|
|
d.HasChange("catalog_id") {
|
|
|
|
|
|
|
|
envMap := make(map[string]interface{})
|
|
|
|
for key, value := range *data["environment"].(*map[string]string) {
|
|
|
|
envValue := value
|
|
|
|
envMap[key] = &envValue
|
|
|
|
}
|
|
|
|
stack, err = client.Environment.ActionUpgrade(stack, &rancherClient.EnvironmentUpgrade{
|
|
|
|
DockerCompose: *data["dockerCompose"].(*string),
|
|
|
|
RancherCompose: *data["rancherCompose"].(*string),
|
|
|
|
Environment: envMap,
|
|
|
|
ExternalId: *data["externalId"].(*string),
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
stateConf := &resource.StateChangeConf{
|
|
|
|
Pending: []string{"active", "upgrading", "upgraded"},
|
|
|
|
Target: []string{"upgraded"},
|
|
|
|
Refresh: StackStateRefreshFunc(client, stack.Id),
|
|
|
|
Timeout: 10 * time.Minute,
|
|
|
|
Delay: 1 * time.Second,
|
|
|
|
MinTimeout: 3 * time.Second,
|
|
|
|
}
|
|
|
|
s, waitErr := stateConf.WaitForState()
|
|
|
|
if waitErr != nil {
|
|
|
|
return fmt.Errorf(
|
|
|
|
"Error waiting for stack (%s) to be upgraded: %s", stack.Id, waitErr)
|
|
|
|
}
|
|
|
|
stack = s.(*rancherClient.Environment)
|
|
|
|
|
|
|
|
if d.Get("finish_upgrade").(bool) {
|
|
|
|
stack, err = client.Environment.ActionFinishupgrade(stack)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
stateConf = &resource.StateChangeConf{
|
2017-01-04 10:16:04 +01:00
|
|
|
Pending: []string{"active", "upgraded", "finishing-upgrade"},
|
2016-12-05 16:29:41 +01:00
|
|
|
Target: []string{"active"},
|
|
|
|
Refresh: StackStateRefreshFunc(client, stack.Id),
|
|
|
|
Timeout: 10 * time.Minute,
|
|
|
|
Delay: 1 * time.Second,
|
|
|
|
MinTimeout: 3 * time.Second,
|
|
|
|
}
|
|
|
|
_, waitErr = stateConf.WaitForState()
|
|
|
|
if waitErr != nil {
|
|
|
|
return fmt.Errorf(
|
|
|
|
"Error waiting for stack (%s) to be upgraded: %s", stack.Id, waitErr)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
d.SetPartial("rendered_docker_compose")
|
|
|
|
d.SetPartial("rendered_rancher_compose")
|
|
|
|
d.SetPartial("docker_compose")
|
|
|
|
d.SetPartial("rancher_compose")
|
|
|
|
d.SetPartial("environment")
|
|
|
|
d.SetPartial("catalog_id")
|
|
|
|
}
|
|
|
|
|
|
|
|
d.Partial(false)
|
|
|
|
|
|
|
|
return resourceRancherStackRead(d, meta)
|
|
|
|
}
|
|
|
|
|
|
|
|
func resourceRancherStackDelete(d *schema.ResourceData, meta interface{}) error {
|
|
|
|
log.Printf("[INFO] Deleting Stack: %s", d.Id())
|
|
|
|
id := d.Id()
|
|
|
|
client, err := meta.(*Config).EnvironmentClient(d.Get("environment_id").(string))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
stack, err := client.Environment.ById(id)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := client.Environment.Delete(stack); err != nil {
|
|
|
|
return fmt.Errorf("Error deleting Stack: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Printf("[DEBUG] Waiting for stack (%s) to be removed", id)
|
|
|
|
|
|
|
|
stateConf := &resource.StateChangeConf{
|
|
|
|
Pending: []string{"active", "removed", "removing"},
|
|
|
|
Target: []string{"removed"},
|
|
|
|
Refresh: StackStateRefreshFunc(client, id),
|
|
|
|
Timeout: 10 * time.Minute,
|
|
|
|
Delay: 1 * time.Second,
|
|
|
|
MinTimeout: 3 * time.Second,
|
|
|
|
}
|
|
|
|
|
|
|
|
_, waitErr := stateConf.WaitForState()
|
|
|
|
if waitErr != nil {
|
|
|
|
return fmt.Errorf(
|
|
|
|
"Error waiting for stack (%s) to be removed: %s", id, waitErr)
|
|
|
|
}
|
|
|
|
|
|
|
|
d.SetId("")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// StackStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
|
|
|
|
// a Rancher Stack.
|
|
|
|
func StackStateRefreshFunc(client *rancherClient.RancherClient, stackID string) resource.StateRefreshFunc {
|
|
|
|
return func() (interface{}, string, error) {
|
|
|
|
stack, err := client.Environment.ById(stackID)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return stack, stack.State, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func environmentFromMap(m map[string]interface{}) map[string]string {
|
|
|
|
result := make(map[string]string)
|
|
|
|
for k, v := range m {
|
|
|
|
result[k] = v.(string)
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
func makeStackData(d *schema.ResourceData, meta interface{}) (data map[string]interface{}, err error) {
|
|
|
|
name := d.Get("name").(string)
|
|
|
|
description := d.Get("description").(string)
|
|
|
|
|
|
|
|
var externalID string
|
|
|
|
var dockerCompose string
|
|
|
|
var rancherCompose string
|
|
|
|
var environment map[string]string
|
|
|
|
if c, ok := d.GetOk("catalog_id"); ok {
|
|
|
|
if scope, ok := d.GetOk("scope"); ok && scope.(string) == "system" {
|
|
|
|
externalID = "system-"
|
|
|
|
}
|
|
|
|
catalogID := c.(string)
|
|
|
|
externalID += "catalog://" + catalogID
|
|
|
|
|
|
|
|
catalogClient, err := meta.(*Config).CatalogClient()
|
|
|
|
if err != nil {
|
|
|
|
return data, err
|
|
|
|
}
|
|
|
|
template, err := catalogClient.Template.ById(catalogID)
|
|
|
|
if err != nil {
|
|
|
|
return data, fmt.Errorf("Failed to get catalog template: %s", err)
|
|
|
|
}
|
|
|
|
|
2017-01-31 14:04:24 +01:00
|
|
|
if template == nil {
|
|
|
|
return data, fmt.Errorf("Unknown catalog template %s", catalogID)
|
|
|
|
}
|
|
|
|
|
2016-12-05 16:29:41 +01:00
|
|
|
dockerCompose = template.Files["docker-compose.yml"].(string)
|
|
|
|
rancherCompose = template.Files["rancher-compose.yml"].(string)
|
|
|
|
}
|
|
|
|
|
|
|
|
if c, ok := d.GetOk("docker_compose"); ok {
|
|
|
|
dockerCompose = c.(string)
|
|
|
|
}
|
|
|
|
if c, ok := d.GetOk("rancher_compose"); ok {
|
|
|
|
rancherCompose = c.(string)
|
|
|
|
}
|
|
|
|
environment = environmentFromMap(d.Get("environment").(map[string]interface{}))
|
|
|
|
|
|
|
|
startOnCreate := d.Get("start_on_create")
|
|
|
|
|
|
|
|
data = map[string]interface{}{
|
|
|
|
"name": &name,
|
|
|
|
"description": &description,
|
|
|
|
"dockerCompose": &dockerCompose,
|
|
|
|
"rancherCompose": &rancherCompose,
|
|
|
|
"environment": &environment,
|
|
|
|
"externalId": &externalID,
|
|
|
|
"startOnCreate": &startOnCreate,
|
|
|
|
}
|
|
|
|
|
|
|
|
return data, nil
|
|
|
|
}
|