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 }