github.com/recobe182/terraform@v0.8.5-0.20170117231232-49ab22a935b7/builtin/providers/rancher/resource_rancher_stack.go (about)

     1  package rancher
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"strings"
     7  	"time"
     8  
     9  	"github.com/hashicorp/terraform/helper/resource"
    10  	"github.com/hashicorp/terraform/helper/schema"
    11  	"github.com/hashicorp/terraform/helper/validation"
    12  	rancherClient "github.com/rancher/go-rancher/client"
    13  )
    14  
    15  func resourceRancherStack() *schema.Resource {
    16  	return &schema.Resource{
    17  		Create: resourceRancherStackCreate,
    18  		Read:   resourceRancherStackRead,
    19  		Update: resourceRancherStackUpdate,
    20  		Delete: resourceRancherStackDelete,
    21  		Importer: &schema.ResourceImporter{
    22  			State: schema.ImportStatePassthrough,
    23  		},
    24  
    25  		Schema: map[string]*schema.Schema{
    26  			"id": &schema.Schema{
    27  				Type:     schema.TypeString,
    28  				Computed: true,
    29  			},
    30  			"name": &schema.Schema{
    31  				Type:     schema.TypeString,
    32  				Required: true,
    33  			},
    34  			"description": &schema.Schema{
    35  				Type:     schema.TypeString,
    36  				Optional: true,
    37  			},
    38  			"environment_id": {
    39  				Type:     schema.TypeString,
    40  				Required: true,
    41  				ForceNew: true,
    42  			},
    43  			"docker_compose": {
    44  				Type:     schema.TypeString,
    45  				Optional: true,
    46  			},
    47  			"rancher_compose": {
    48  				Type:     schema.TypeString,
    49  				Optional: true,
    50  			},
    51  			"environment": {
    52  				Type:     schema.TypeMap,
    53  				Optional: true,
    54  			},
    55  			"catalog_id": {
    56  				Type:     schema.TypeString,
    57  				Optional: true,
    58  			},
    59  			"scope": {
    60  				Type:         schema.TypeString,
    61  				Default:      "user",
    62  				Optional:     true,
    63  				ValidateFunc: validation.StringInSlice([]string{"user", "system"}, true),
    64  			},
    65  			"start_on_create": {
    66  				Type:     schema.TypeBool,
    67  				Optional: true,
    68  				Computed: true,
    69  			},
    70  			"finish_upgrade": {
    71  				Type:     schema.TypeBool,
    72  				Optional: true,
    73  			},
    74  			"rendered_docker_compose": {
    75  				Type:     schema.TypeString,
    76  				Computed: true,
    77  			},
    78  			"rendered_rancher_compose": {
    79  				Type:     schema.TypeString,
    80  				Computed: true,
    81  			},
    82  		},
    83  	}
    84  }
    85  
    86  func resourceRancherStackCreate(d *schema.ResourceData, meta interface{}) error {
    87  	log.Printf("[INFO] Creating Stack: %s", d.Id())
    88  	client, err := meta.(*Config).EnvironmentClient(d.Get("environment_id").(string))
    89  	if err != nil {
    90  		return err
    91  	}
    92  
    93  	data, err := makeStackData(d, meta)
    94  	if err != nil {
    95  		return err
    96  	}
    97  
    98  	var newStack rancherClient.Environment
    99  	if err := client.Create("environment", data, &newStack); err != nil {
   100  		return err
   101  	}
   102  
   103  	stateConf := &resource.StateChangeConf{
   104  		Pending:    []string{"activating", "active", "removed", "removing"},
   105  		Target:     []string{"active"},
   106  		Refresh:    StackStateRefreshFunc(client, newStack.Id),
   107  		Timeout:    10 * time.Minute,
   108  		Delay:      1 * time.Second,
   109  		MinTimeout: 3 * time.Second,
   110  	}
   111  	_, waitErr := stateConf.WaitForState()
   112  	if waitErr != nil {
   113  		return fmt.Errorf(
   114  			"Error waiting for stack (%s) to be created: %s", newStack.Id, waitErr)
   115  	}
   116  
   117  	d.SetId(newStack.Id)
   118  	log.Printf("[INFO] Stack ID: %s", d.Id())
   119  
   120  	return resourceRancherStackRead(d, meta)
   121  }
   122  
   123  func resourceRancherStackRead(d *schema.ResourceData, meta interface{}) error {
   124  	log.Printf("[INFO] Refreshing Stack: %s", d.Id())
   125  	client := meta.(*Config)
   126  
   127  	stack, err := client.Environment.ById(d.Id())
   128  	if err != nil {
   129  		return err
   130  	}
   131  
   132  	config, err := client.Environment.ActionExportconfig(stack, &rancherClient.ComposeConfigInput{})
   133  	if err != nil {
   134  		return err
   135  	}
   136  
   137  	log.Printf("[INFO] Stack Name: %s", stack.Name)
   138  
   139  	d.Set("description", stack.Description)
   140  	d.Set("name", stack.Name)
   141  	d.Set("rendered_docker_compose", strings.Replace(config.DockerComposeConfig, "\r", "", -1))
   142  	d.Set("rendered_rancher_compose", strings.Replace(config.RancherComposeConfig, "\r", "", -1))
   143  	d.Set("environment_id", stack.AccountId)
   144  	d.Set("environment", stack.Environment)
   145  
   146  	if stack.ExternalId == "" {
   147  		d.Set("scope", "user")
   148  		d.Set("catalog_id", "")
   149  	} else {
   150  		trimmedID := strings.TrimPrefix(stack.ExternalId, "system-")
   151  		if trimmedID == stack.ExternalId {
   152  			d.Set("scope", "user")
   153  		} else {
   154  			d.Set("scope", "system")
   155  		}
   156  		d.Set("catalog_id", strings.TrimPrefix(trimmedID, "catalog://"))
   157  	}
   158  
   159  	d.Set("start_on_create", stack.StartOnCreate)
   160  
   161  	return nil
   162  }
   163  
   164  func resourceRancherStackUpdate(d *schema.ResourceData, meta interface{}) error {
   165  	log.Printf("[INFO] Updating Stack: %s", d.Id())
   166  	client, err := meta.(*Config).EnvironmentClient(d.Get("environment_id").(string))
   167  	if err != nil {
   168  		return err
   169  	}
   170  	d.Partial(true)
   171  
   172  	data, err := makeStackData(d, meta)
   173  	if err != nil {
   174  		return err
   175  	}
   176  
   177  	stack, err := client.Environment.ById(d.Id())
   178  	if err != nil {
   179  		return err
   180  	}
   181  
   182  	var newStack rancherClient.Environment
   183  	if err := client.Update("environment", &stack.Resource, data, &newStack); err != nil {
   184  		return err
   185  	}
   186  
   187  	stateConf := &resource.StateChangeConf{
   188  		Pending:    []string{"active", "active-updating"},
   189  		Target:     []string{"active"},
   190  		Refresh:    StackStateRefreshFunc(client, newStack.Id),
   191  		Timeout:    10 * time.Minute,
   192  		Delay:      1 * time.Second,
   193  		MinTimeout: 3 * time.Second,
   194  	}
   195  	s, waitErr := stateConf.WaitForState()
   196  	stack = s.(*rancherClient.Environment)
   197  	if waitErr != nil {
   198  		return fmt.Errorf(
   199  			"Error waiting for stack (%s) to be updated: %s", stack.Id, waitErr)
   200  	}
   201  
   202  	d.SetPartial("name")
   203  	d.SetPartial("description")
   204  	d.SetPartial("scope")
   205  
   206  	if d.HasChange("docker_compose") ||
   207  		d.HasChange("rancher_compose") ||
   208  		d.HasChange("environment") ||
   209  		d.HasChange("catalog_id") {
   210  
   211  		envMap := make(map[string]interface{})
   212  		for key, value := range *data["environment"].(*map[string]string) {
   213  			envValue := value
   214  			envMap[key] = &envValue
   215  		}
   216  		stack, err = client.Environment.ActionUpgrade(stack, &rancherClient.EnvironmentUpgrade{
   217  			DockerCompose:  *data["dockerCompose"].(*string),
   218  			RancherCompose: *data["rancherCompose"].(*string),
   219  			Environment:    envMap,
   220  			ExternalId:     *data["externalId"].(*string),
   221  		})
   222  		if err != nil {
   223  			return err
   224  		}
   225  
   226  		stateConf := &resource.StateChangeConf{
   227  			Pending:    []string{"active", "upgrading", "upgraded"},
   228  			Target:     []string{"upgraded"},
   229  			Refresh:    StackStateRefreshFunc(client, stack.Id),
   230  			Timeout:    10 * time.Minute,
   231  			Delay:      1 * time.Second,
   232  			MinTimeout: 3 * time.Second,
   233  		}
   234  		s, waitErr := stateConf.WaitForState()
   235  		if waitErr != nil {
   236  			return fmt.Errorf(
   237  				"Error waiting for stack (%s) to be upgraded: %s", stack.Id, waitErr)
   238  		}
   239  		stack = s.(*rancherClient.Environment)
   240  
   241  		if d.Get("finish_upgrade").(bool) {
   242  			stack, err = client.Environment.ActionFinishupgrade(stack)
   243  			if err != nil {
   244  				return err
   245  			}
   246  
   247  			stateConf = &resource.StateChangeConf{
   248  				Pending:    []string{"active", "upgraded", "finishing-upgrade"},
   249  				Target:     []string{"active"},
   250  				Refresh:    StackStateRefreshFunc(client, stack.Id),
   251  				Timeout:    10 * time.Minute,
   252  				Delay:      1 * time.Second,
   253  				MinTimeout: 3 * time.Second,
   254  			}
   255  			_, waitErr = stateConf.WaitForState()
   256  			if waitErr != nil {
   257  				return fmt.Errorf(
   258  					"Error waiting for stack (%s) to be upgraded: %s", stack.Id, waitErr)
   259  			}
   260  		}
   261  
   262  		d.SetPartial("rendered_docker_compose")
   263  		d.SetPartial("rendered_rancher_compose")
   264  		d.SetPartial("docker_compose")
   265  		d.SetPartial("rancher_compose")
   266  		d.SetPartial("environment")
   267  		d.SetPartial("catalog_id")
   268  	}
   269  
   270  	d.Partial(false)
   271  
   272  	return resourceRancherStackRead(d, meta)
   273  }
   274  
   275  func resourceRancherStackDelete(d *schema.ResourceData, meta interface{}) error {
   276  	log.Printf("[INFO] Deleting Stack: %s", d.Id())
   277  	id := d.Id()
   278  	client, err := meta.(*Config).EnvironmentClient(d.Get("environment_id").(string))
   279  	if err != nil {
   280  		return err
   281  	}
   282  
   283  	stack, err := client.Environment.ById(id)
   284  	if err != nil {
   285  		return err
   286  	}
   287  
   288  	if err := client.Environment.Delete(stack); err != nil {
   289  		return fmt.Errorf("Error deleting Stack: %s", err)
   290  	}
   291  
   292  	log.Printf("[DEBUG] Waiting for stack (%s) to be removed", id)
   293  
   294  	stateConf := &resource.StateChangeConf{
   295  		Pending:    []string{"active", "removed", "removing"},
   296  		Target:     []string{"removed"},
   297  		Refresh:    StackStateRefreshFunc(client, id),
   298  		Timeout:    10 * time.Minute,
   299  		Delay:      1 * time.Second,
   300  		MinTimeout: 3 * time.Second,
   301  	}
   302  
   303  	_, waitErr := stateConf.WaitForState()
   304  	if waitErr != nil {
   305  		return fmt.Errorf(
   306  			"Error waiting for stack (%s) to be removed: %s", id, waitErr)
   307  	}
   308  
   309  	d.SetId("")
   310  	return nil
   311  }
   312  
   313  // StackStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
   314  // a Rancher Stack.
   315  func StackStateRefreshFunc(client *rancherClient.RancherClient, stackID string) resource.StateRefreshFunc {
   316  	return func() (interface{}, string, error) {
   317  		stack, err := client.Environment.ById(stackID)
   318  
   319  		if err != nil {
   320  			return nil, "", err
   321  		}
   322  
   323  		return stack, stack.State, nil
   324  	}
   325  }
   326  
   327  func environmentFromMap(m map[string]interface{}) map[string]string {
   328  	result := make(map[string]string)
   329  	for k, v := range m {
   330  		result[k] = v.(string)
   331  	}
   332  	return result
   333  }
   334  
   335  func makeStackData(d *schema.ResourceData, meta interface{}) (data map[string]interface{}, err error) {
   336  	name := d.Get("name").(string)
   337  	description := d.Get("description").(string)
   338  
   339  	var externalID string
   340  	var dockerCompose string
   341  	var rancherCompose string
   342  	var environment map[string]string
   343  	if c, ok := d.GetOk("catalog_id"); ok {
   344  		if scope, ok := d.GetOk("scope"); ok && scope.(string) == "system" {
   345  			externalID = "system-"
   346  		}
   347  		catalogID := c.(string)
   348  		externalID += "catalog://" + catalogID
   349  
   350  		catalogClient, err := meta.(*Config).CatalogClient()
   351  		if err != nil {
   352  			return data, err
   353  		}
   354  		template, err := catalogClient.Template.ById(catalogID)
   355  		if err != nil {
   356  			return data, fmt.Errorf("Failed to get catalog template: %s", err)
   357  		}
   358  
   359  		dockerCompose = template.Files["docker-compose.yml"].(string)
   360  		rancherCompose = template.Files["rancher-compose.yml"].(string)
   361  	}
   362  
   363  	if c, ok := d.GetOk("docker_compose"); ok {
   364  		dockerCompose = c.(string)
   365  	}
   366  	if c, ok := d.GetOk("rancher_compose"); ok {
   367  		rancherCompose = c.(string)
   368  	}
   369  	environment = environmentFromMap(d.Get("environment").(map[string]interface{}))
   370  
   371  	startOnCreate := d.Get("start_on_create")
   372  
   373  	data = map[string]interface{}{
   374  		"name":           &name,
   375  		"description":    &description,
   376  		"dockerCompose":  &dockerCompose,
   377  		"rancherCompose": &rancherCompose,
   378  		"environment":    &environment,
   379  		"externalId":     &externalID,
   380  		"startOnCreate":  &startOnCreate,
   381  	}
   382  
   383  	return data, nil
   384  }