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 }