github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/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: resourceRancherStackImport,
    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, err := meta.(*Config).EnvironmentClient(d.Get("environment_id").(string))
   126  	if err != nil {
   127  		return err
   128  	}
   129  
   130  	stack, err := client.Environment.ById(d.Id())
   131  	if err != nil {
   132  		return err
   133  	}
   134  
   135  	if stack == nil {
   136  		log.Printf("[INFO] Stack %s not found", d.Id())
   137  		d.SetId("")
   138  		return nil
   139  	}
   140  
   141  	if removed(stack.State) {
   142  		log.Printf("[INFO] Stack %s was removed on %v", d.Id(), stack.Removed)
   143  		d.SetId("")
   144  		return nil
   145  	}
   146  
   147  	config, err := client.Environment.ActionExportconfig(stack, &rancherClient.ComposeConfigInput{})
   148  	if err != nil {
   149  		return err
   150  	}
   151  
   152  	log.Printf("[INFO] Stack Name: %s", stack.Name)
   153  
   154  	d.Set("description", stack.Description)
   155  	d.Set("name", stack.Name)
   156  	dockerCompose := strings.Replace(config.DockerComposeConfig, "\r", "", -1)
   157  	rancherCompose := strings.Replace(config.RancherComposeConfig, "\r", "", -1)
   158  
   159  	catalogID := d.Get("catalog_id")
   160  	if catalogID == "" {
   161  		d.Set("docker_compose", dockerCompose)
   162  		d.Set("rancher_compose", rancherCompose)
   163  	} else {
   164  		d.Set("docker_compose", "")
   165  		d.Set("rancher_compose", "")
   166  	}
   167  	d.Set("rendered_docker_compose", dockerCompose)
   168  	d.Set("rendered_rancher_compose", rancherCompose)
   169  	d.Set("environment_id", stack.AccountId)
   170  	d.Set("environment", stack.Environment)
   171  
   172  	if stack.ExternalId == "" {
   173  		d.Set("scope", "user")
   174  		d.Set("catalog_id", "")
   175  	} else {
   176  		trimmedID := strings.TrimPrefix(stack.ExternalId, "system-")
   177  		if trimmedID == stack.ExternalId {
   178  			d.Set("scope", "user")
   179  		} else {
   180  			d.Set("scope", "system")
   181  		}
   182  		d.Set("catalog_id", strings.TrimPrefix(trimmedID, "catalog://"))
   183  	}
   184  
   185  	d.Set("start_on_create", stack.StartOnCreate)
   186  
   187  	return nil
   188  }
   189  
   190  func resourceRancherStackUpdate(d *schema.ResourceData, meta interface{}) error {
   191  	log.Printf("[INFO] Updating Stack: %s", d.Id())
   192  	client, err := meta.(*Config).EnvironmentClient(d.Get("environment_id").(string))
   193  	if err != nil {
   194  		return err
   195  	}
   196  	d.Partial(true)
   197  
   198  	data, err := makeStackData(d, meta)
   199  	if err != nil {
   200  		return err
   201  	}
   202  
   203  	stack, err := client.Environment.ById(d.Id())
   204  	if err != nil {
   205  		return err
   206  	}
   207  
   208  	var newStack rancherClient.Environment
   209  	if err = client.Update("environment", &stack.Resource, data, &newStack); err != nil {
   210  		return err
   211  	}
   212  
   213  	stateConf := &resource.StateChangeConf{
   214  		Pending:    []string{"active", "active-updating"},
   215  		Target:     []string{"active"},
   216  		Refresh:    StackStateRefreshFunc(client, newStack.Id),
   217  		Timeout:    10 * time.Minute,
   218  		Delay:      1 * time.Second,
   219  		MinTimeout: 3 * time.Second,
   220  	}
   221  	s, waitErr := stateConf.WaitForState()
   222  	stack = s.(*rancherClient.Environment)
   223  	if waitErr != nil {
   224  		return fmt.Errorf(
   225  			"Error waiting for stack (%s) to be updated: %s", stack.Id, waitErr)
   226  	}
   227  
   228  	d.SetPartial("name")
   229  	d.SetPartial("description")
   230  	d.SetPartial("scope")
   231  
   232  	if d.HasChange("docker_compose") ||
   233  		d.HasChange("rancher_compose") ||
   234  		d.HasChange("environment") ||
   235  		d.HasChange("catalog_id") {
   236  
   237  		envMap := make(map[string]interface{})
   238  		for key, value := range *data["environment"].(*map[string]string) {
   239  			envValue := value
   240  			envMap[key] = &envValue
   241  		}
   242  		stack, err = client.Environment.ActionUpgrade(stack, &rancherClient.EnvironmentUpgrade{
   243  			DockerCompose:  *data["dockerCompose"].(*string),
   244  			RancherCompose: *data["rancherCompose"].(*string),
   245  			Environment:    envMap,
   246  			ExternalId:     *data["externalId"].(*string),
   247  		})
   248  		if err != nil {
   249  			return err
   250  		}
   251  
   252  		stateConf := &resource.StateChangeConf{
   253  			Pending:    []string{"active", "upgrading", "upgraded"},
   254  			Target:     []string{"upgraded"},
   255  			Refresh:    StackStateRefreshFunc(client, stack.Id),
   256  			Timeout:    10 * time.Minute,
   257  			Delay:      1 * time.Second,
   258  			MinTimeout: 3 * time.Second,
   259  		}
   260  		s, waitErr := stateConf.WaitForState()
   261  		if waitErr != nil {
   262  			return fmt.Errorf(
   263  				"Error waiting for stack (%s) to be upgraded: %s", stack.Id, waitErr)
   264  		}
   265  		stack = s.(*rancherClient.Environment)
   266  
   267  		if d.Get("finish_upgrade").(bool) {
   268  			stack, err = client.Environment.ActionFinishupgrade(stack)
   269  			if err != nil {
   270  				return err
   271  			}
   272  
   273  			stateConf = &resource.StateChangeConf{
   274  				Pending:    []string{"active", "upgraded", "finishing-upgrade"},
   275  				Target:     []string{"active"},
   276  				Refresh:    StackStateRefreshFunc(client, stack.Id),
   277  				Timeout:    10 * time.Minute,
   278  				Delay:      1 * time.Second,
   279  				MinTimeout: 3 * time.Second,
   280  			}
   281  			_, waitErr = stateConf.WaitForState()
   282  			if waitErr != nil {
   283  				return fmt.Errorf(
   284  					"Error waiting for stack (%s) to be upgraded: %s", stack.Id, waitErr)
   285  			}
   286  		}
   287  
   288  		d.SetPartial("rendered_docker_compose")
   289  		d.SetPartial("rendered_rancher_compose")
   290  		d.SetPartial("docker_compose")
   291  		d.SetPartial("rancher_compose")
   292  		d.SetPartial("environment")
   293  		d.SetPartial("catalog_id")
   294  	}
   295  
   296  	d.Partial(false)
   297  
   298  	return resourceRancherStackRead(d, meta)
   299  }
   300  
   301  func resourceRancherStackDelete(d *schema.ResourceData, meta interface{}) error {
   302  	log.Printf("[INFO] Deleting Stack: %s", d.Id())
   303  	id := d.Id()
   304  	client, err := meta.(*Config).EnvironmentClient(d.Get("environment_id").(string))
   305  	if err != nil {
   306  		return err
   307  	}
   308  
   309  	stack, err := client.Environment.ById(id)
   310  	if err != nil {
   311  		return err
   312  	}
   313  
   314  	if err := client.Environment.Delete(stack); err != nil {
   315  		return fmt.Errorf("Error deleting Stack: %s", err)
   316  	}
   317  
   318  	log.Printf("[DEBUG] Waiting for stack (%s) to be removed", id)
   319  
   320  	stateConf := &resource.StateChangeConf{
   321  		Pending:    []string{"active", "removed", "removing"},
   322  		Target:     []string{"removed"},
   323  		Refresh:    StackStateRefreshFunc(client, id),
   324  		Timeout:    10 * time.Minute,
   325  		Delay:      1 * time.Second,
   326  		MinTimeout: 3 * time.Second,
   327  	}
   328  
   329  	_, waitErr := stateConf.WaitForState()
   330  	if waitErr != nil {
   331  		return fmt.Errorf(
   332  			"Error waiting for stack (%s) to be removed: %s", id, waitErr)
   333  	}
   334  
   335  	d.SetId("")
   336  	return nil
   337  }
   338  
   339  func resourceRancherStackImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
   340  	envID, resourceID := splitID(d.Id())
   341  	d.SetId(resourceID)
   342  	if envID != "" {
   343  		d.Set("environment_id", envID)
   344  	} else {
   345  		client, err := meta.(*Config).GlobalClient()
   346  		if err != nil {
   347  			return []*schema.ResourceData{}, err
   348  		}
   349  		stack, err := client.Environment.ById(d.Id())
   350  		if err != nil {
   351  			return []*schema.ResourceData{}, err
   352  		}
   353  		d.Set("environment_id", stack.AccountId)
   354  	}
   355  	return []*schema.ResourceData{d}, nil
   356  }
   357  
   358  // StackStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
   359  // a Rancher Stack.
   360  func StackStateRefreshFunc(client *rancherClient.RancherClient, stackID string) resource.StateRefreshFunc {
   361  	return func() (interface{}, string, error) {
   362  		stack, err := client.Environment.ById(stackID)
   363  
   364  		if err != nil {
   365  			return nil, "", err
   366  		}
   367  
   368  		return stack, stack.State, nil
   369  	}
   370  }
   371  
   372  func environmentFromMap(m map[string]interface{}) map[string]string {
   373  	result := make(map[string]string)
   374  	for k, v := range m {
   375  		result[k] = v.(string)
   376  	}
   377  	return result
   378  }
   379  
   380  func makeStackData(d *schema.ResourceData, meta interface{}) (data map[string]interface{}, err error) {
   381  	name := d.Get("name").(string)
   382  	description := d.Get("description").(string)
   383  
   384  	var externalID string
   385  	var dockerCompose string
   386  	var rancherCompose string
   387  	var environment map[string]string
   388  	if c, ok := d.GetOk("catalog_id"); ok {
   389  		if scope, ok := d.GetOk("scope"); ok && scope.(string) == "system" {
   390  			externalID = "system-"
   391  		}
   392  		catalogID := c.(string)
   393  		externalID += "catalog://" + catalogID
   394  
   395  		catalogClient, err := meta.(*Config).CatalogClient()
   396  		if err != nil {
   397  			return data, err
   398  		}
   399  		template, err := catalogClient.Template.ById(catalogID)
   400  		if err != nil {
   401  			return data, fmt.Errorf("Failed to get catalog template: %s", err)
   402  		}
   403  
   404  		if template == nil {
   405  			return data, fmt.Errorf("Unknown catalog template %s", catalogID)
   406  		}
   407  
   408  		dockerCompose = template.Files["docker-compose.yml"].(string)
   409  		rancherCompose = template.Files["rancher-compose.yml"].(string)
   410  	}
   411  
   412  	if c, ok := d.GetOk("docker_compose"); ok {
   413  		dockerCompose = c.(string)
   414  	}
   415  	if c, ok := d.GetOk("rancher_compose"); ok {
   416  		rancherCompose = c.(string)
   417  	}
   418  	environment = environmentFromMap(d.Get("environment").(map[string]interface{}))
   419  
   420  	startOnCreate := d.Get("start_on_create")
   421  
   422  	data = map[string]interface{}{
   423  		"name":           &name,
   424  		"description":    &description,
   425  		"dockerCompose":  &dockerCompose,
   426  		"rancherCompose": &rancherCompose,
   427  		"environment":    &environment,
   428  		"externalId":     &externalID,
   429  		"startOnCreate":  &startOnCreate,
   430  	}
   431  
   432  	return data, nil
   433  }