github.com/nathanielks/terraform@v0.6.1-0.20170509030759-13e1a62319dc/builtin/providers/aws/resource_aws_ssm_document.go (about) 1 package aws 2 3 import ( 4 "fmt" 5 "log" 6 "strconv" 7 "strings" 8 "time" 9 10 "github.com/aws/aws-sdk-go/aws" 11 "github.com/aws/aws-sdk-go/aws/awserr" 12 "github.com/aws/aws-sdk-go/service/ssm" 13 "github.com/hashicorp/errwrap" 14 "github.com/hashicorp/terraform/helper/resource" 15 "github.com/hashicorp/terraform/helper/schema" 16 ) 17 18 const ( 19 MINIMUM_VERSIONED_SCHEMA = 2.0 20 ) 21 22 func resourceAwsSsmDocument() *schema.Resource { 23 return &schema.Resource{ 24 Create: resourceAwsSsmDocumentCreate, 25 Read: resourceAwsSsmDocumentRead, 26 Update: resourceAwsSsmDocumentUpdate, 27 Delete: resourceAwsSsmDocumentDelete, 28 29 Schema: map[string]*schema.Schema{ 30 "arn": { 31 Type: schema.TypeString, 32 Computed: true, 33 }, 34 "name": { 35 Type: schema.TypeString, 36 Required: true, 37 }, 38 "content": { 39 Type: schema.TypeString, 40 Required: true, 41 }, 42 "document_type": { 43 Type: schema.TypeString, 44 Required: true, 45 ValidateFunc: validateAwsSSMDocumentType, 46 }, 47 "schema_version": { 48 Type: schema.TypeString, 49 Computed: true, 50 }, 51 "created_date": { 52 Type: schema.TypeString, 53 Computed: true, 54 }, 55 "default_version": { 56 Type: schema.TypeString, 57 Computed: true, 58 }, 59 "description": { 60 Type: schema.TypeString, 61 Computed: true, 62 }, 63 "hash": { 64 Type: schema.TypeString, 65 Computed: true, 66 }, 67 "hash_type": { 68 Type: schema.TypeString, 69 Computed: true, 70 }, 71 "latest_version": { 72 Type: schema.TypeString, 73 Computed: true, 74 }, 75 "owner": { 76 Type: schema.TypeString, 77 Computed: true, 78 }, 79 "status": { 80 Type: schema.TypeString, 81 Computed: true, 82 }, 83 "platform_types": { 84 Type: schema.TypeList, 85 Computed: true, 86 Elem: &schema.Schema{Type: schema.TypeString}, 87 }, 88 "parameter": { 89 Type: schema.TypeList, 90 Computed: true, 91 Elem: &schema.Resource{ 92 Schema: map[string]*schema.Schema{ 93 "name": { 94 Type: schema.TypeString, 95 Optional: true, 96 }, 97 "default_value": { 98 Type: schema.TypeString, 99 Optional: true, 100 }, 101 "description": { 102 Type: schema.TypeString, 103 Optional: true, 104 }, 105 "type": { 106 Type: schema.TypeString, 107 Optional: true, 108 }, 109 }, 110 }, 111 }, 112 "permissions": { 113 Type: schema.TypeMap, 114 Optional: true, 115 Elem: &schema.Resource{ 116 Schema: map[string]*schema.Schema{ 117 "type": { 118 Type: schema.TypeString, 119 Required: true, 120 }, 121 "account_ids": { 122 Type: schema.TypeString, 123 Required: true, 124 }, 125 }, 126 }, 127 }, 128 }, 129 } 130 } 131 132 func resourceAwsSsmDocumentCreate(d *schema.ResourceData, meta interface{}) error { 133 ssmconn := meta.(*AWSClient).ssmconn 134 135 log.Printf("[INFO] Creating SSM Document: %s", d.Get("name").(string)) 136 137 docInput := &ssm.CreateDocumentInput{ 138 Name: aws.String(d.Get("name").(string)), 139 Content: aws.String(d.Get("content").(string)), 140 DocumentType: aws.String(d.Get("document_type").(string)), 141 } 142 143 log.Printf("[DEBUG] Waiting for SSM Document %q to be created", d.Get("name").(string)) 144 err := resource.Retry(5*time.Minute, func() *resource.RetryError { 145 resp, err := ssmconn.CreateDocument(docInput) 146 147 if err != nil { 148 return resource.NonRetryableError(err) 149 } 150 151 d.SetId(*resp.DocumentDescription.Name) 152 return nil 153 }) 154 155 if err != nil { 156 return errwrap.Wrapf("[ERROR] Error creating SSM document: {{err}}", err) 157 } 158 159 if v, ok := d.GetOk("permissions"); ok && v != nil { 160 if err := setDocumentPermissions(d, meta); err != nil { 161 return err 162 } 163 } else { 164 log.Printf("[DEBUG] Not setting permissions for %q", d.Id()) 165 } 166 167 return resourceAwsSsmDocumentRead(d, meta) 168 } 169 170 func resourceAwsSsmDocumentRead(d *schema.ResourceData, meta interface{}) error { 171 ssmconn := meta.(*AWSClient).ssmconn 172 173 log.Printf("[DEBUG] Reading SSM Document: %s", d.Id()) 174 175 docInput := &ssm.DescribeDocumentInput{ 176 Name: aws.String(d.Get("name").(string)), 177 } 178 179 resp, err := ssmconn.DescribeDocument(docInput) 180 if err != nil { 181 if ssmErr, ok := err.(awserr.Error); ok && ssmErr.Code() == "InvalidDocument" { 182 log.Printf("[WARN] SSM Document not found so removing from state") 183 d.SetId("") 184 return nil 185 } 186 return errwrap.Wrapf("[ERROR] Error describing SSM document: {{err}}", err) 187 } 188 189 doc := resp.Document 190 d.Set("created_date", doc.CreatedDate) 191 d.Set("default_version", doc.DefaultVersion) 192 d.Set("description", doc.Description) 193 d.Set("schema_version", doc.SchemaVersion) 194 195 if _, ok := d.GetOk("document_type"); ok { 196 d.Set("document_type", doc.DocumentType) 197 } 198 199 d.Set("document_version", doc.DocumentVersion) 200 d.Set("hash", doc.Hash) 201 d.Set("hash_type", doc.HashType) 202 d.Set("latest_version", doc.LatestVersion) 203 d.Set("name", doc.Name) 204 d.Set("owner", doc.Owner) 205 d.Set("platform_types", flattenStringList(doc.PlatformTypes)) 206 if err := d.Set("arn", flattenAwsSsmDocumentArn(meta, doc.Name)); err != nil { 207 return fmt.Errorf("[DEBUG] Error setting arn error: %#v", err) 208 } 209 210 d.Set("status", doc.Status) 211 212 gp, err := getDocumentPermissions(d, meta) 213 214 if err != nil { 215 return errwrap.Wrapf("[ERROR] Error reading SSM document permissions: {{err}}", err) 216 } 217 218 d.Set("permissions", gp) 219 220 params := make([]map[string]interface{}, 0) 221 for i := 0; i < len(doc.Parameters); i++ { 222 223 dp := doc.Parameters[i] 224 param := make(map[string]interface{}) 225 226 if dp.DefaultValue != nil { 227 param["default_value"] = *dp.DefaultValue 228 } 229 if dp.Description != nil { 230 param["description"] = *dp.Description 231 } 232 if dp.Name != nil { 233 param["name"] = *dp.Name 234 } 235 if dp.Type != nil { 236 param["type"] = *dp.Type 237 } 238 params = append(params, param) 239 } 240 241 if len(params) == 0 { 242 params = make([]map[string]interface{}, 1) 243 } 244 245 if err := d.Set("parameter", params); err != nil { 246 return err 247 } 248 249 return nil 250 } 251 252 func flattenAwsSsmDocumentArn(meta interface{}, docName *string) string { 253 region := meta.(*AWSClient).region 254 255 return fmt.Sprintf("arn:aws:ssm:%s::document/%s", region, *docName) 256 } 257 258 func resourceAwsSsmDocumentUpdate(d *schema.ResourceData, meta interface{}) error { 259 260 if _, ok := d.GetOk("permissions"); ok { 261 if err := setDocumentPermissions(d, meta); err != nil { 262 return err 263 } 264 } else { 265 log.Printf("[DEBUG] Not setting document permissions on %q", d.Id()) 266 } 267 268 if !d.HasChange("content") { 269 return nil 270 } 271 272 if schemaVersion, ok := d.GetOk("schemaVersion"); ok { 273 schemaNumber, _ := strconv.ParseFloat(schemaVersion.(string), 64) 274 275 if schemaNumber < MINIMUM_VERSIONED_SCHEMA { 276 log.Printf("[DEBUG] Skipping document update because document version is not 2.0 %q", d.Id()) 277 return nil 278 } 279 } 280 281 if err := updateAwsSSMDocument(d, meta); err != nil { 282 return err 283 } 284 285 return resourceAwsSsmDocumentRead(d, meta) 286 } 287 288 func resourceAwsSsmDocumentDelete(d *schema.ResourceData, meta interface{}) error { 289 ssmconn := meta.(*AWSClient).ssmconn 290 291 if err := deleteDocumentPermissions(d, meta); err != nil { 292 return err 293 } 294 295 log.Printf("[INFO] Deleting SSM Document: %s", d.Id()) 296 297 params := &ssm.DeleteDocumentInput{ 298 Name: aws.String(d.Get("name").(string)), 299 } 300 301 _, err := ssmconn.DeleteDocument(params) 302 if err != nil { 303 return err 304 } 305 306 log.Printf("[DEBUG] Waiting for SSM Document %q to be deleted", d.Get("name").(string)) 307 err = resource.Retry(10*time.Minute, func() *resource.RetryError { 308 _, err := ssmconn.DescribeDocument(&ssm.DescribeDocumentInput{ 309 Name: aws.String(d.Get("name").(string)), 310 }) 311 312 if err != nil { 313 awsErr, ok := err.(awserr.Error) 314 if !ok { 315 return resource.NonRetryableError(err) 316 } 317 318 if awsErr.Code() == "InvalidDocument" { 319 return nil 320 } 321 322 return resource.NonRetryableError(err) 323 } 324 325 return resource.RetryableError( 326 fmt.Errorf("%q: Timeout while waiting for the document to be deleted", d.Id())) 327 }) 328 if err != nil { 329 return err 330 } 331 332 d.SetId("") 333 334 return nil 335 } 336 337 func setDocumentPermissions(d *schema.ResourceData, meta interface{}) error { 338 ssmconn := meta.(*AWSClient).ssmconn 339 340 log.Printf("[INFO] Setting permissions for document: %s", d.Id()) 341 permission := d.Get("permissions").(map[string]interface{}) 342 343 ids := aws.StringSlice([]string{permission["account_ids"].(string)}) 344 345 if strings.Contains(permission["account_ids"].(string), ",") { 346 ids = aws.StringSlice(strings.Split(permission["account_ids"].(string), ",")) 347 } 348 349 permInput := &ssm.ModifyDocumentPermissionInput{ 350 Name: aws.String(d.Get("name").(string)), 351 PermissionType: aws.String(permission["type"].(string)), 352 AccountIdsToAdd: ids, 353 } 354 355 _, err := ssmconn.ModifyDocumentPermission(permInput) 356 357 if err != nil { 358 return errwrap.Wrapf("[ERROR] Error setting permissions for SSM document: {{err}}", err) 359 } 360 361 return nil 362 } 363 364 func getDocumentPermissions(d *schema.ResourceData, meta interface{}) (map[string]interface{}, error) { 365 ssmconn := meta.(*AWSClient).ssmconn 366 367 log.Printf("[INFO] Getting permissions for document: %s", d.Id()) 368 369 //How to get from nested scheme resource? 370 permissionType := "Share" 371 372 permInput := &ssm.DescribeDocumentPermissionInput{ 373 Name: aws.String(d.Get("name").(string)), 374 PermissionType: aws.String(permissionType), 375 } 376 377 resp, err := ssmconn.DescribeDocumentPermission(permInput) 378 379 if err != nil { 380 return nil, errwrap.Wrapf("[ERROR] Error setting permissions for SSM document: {{err}}", err) 381 } 382 383 var account_ids = make([]string, len(resp.AccountIds)) 384 for i := 0; i < len(resp.AccountIds); i++ { 385 account_ids[i] = *resp.AccountIds[i] 386 } 387 388 var ids = "" 389 if len(account_ids) == 1 { 390 ids = account_ids[0] 391 } else if len(account_ids) > 1 { 392 ids = strings.Join(account_ids, ",") 393 } else { 394 ids = "" 395 } 396 397 if ids == "" { 398 return nil, nil 399 } 400 401 perms := make(map[string]interface{}) 402 perms["type"] = permissionType 403 perms["account_ids"] = ids 404 405 return perms, nil 406 } 407 408 func deleteDocumentPermissions(d *schema.ResourceData, meta interface{}) error { 409 ssmconn := meta.(*AWSClient).ssmconn 410 411 log.Printf("[INFO] Removing permissions from document: %s", d.Id()) 412 413 permInput := &ssm.ModifyDocumentPermissionInput{ 414 Name: aws.String(d.Get("name").(string)), 415 PermissionType: aws.String("Share"), 416 AccountIdsToRemove: aws.StringSlice(strings.Split("all", ",")), 417 } 418 419 _, err := ssmconn.ModifyDocumentPermission(permInput) 420 421 if err != nil { 422 return errwrap.Wrapf("[ERROR] Error removing permissions for SSM document: {{err}}", err) 423 } 424 425 return nil 426 } 427 428 func updateAwsSSMDocument(d *schema.ResourceData, meta interface{}) error { 429 log.Printf("[INFO] Updating SSM Document: %s", d.Id()) 430 431 name := d.Get("name").(string) 432 433 updateDocInput := &ssm.UpdateDocumentInput{ 434 Name: aws.String(name), 435 Content: aws.String(d.Get("content").(string)), 436 DocumentVersion: aws.String(d.Get("default_version").(string)), 437 } 438 439 newDefaultVersion := d.Get("default_version").(string) 440 441 ssmconn := meta.(*AWSClient).ssmconn 442 updated, err := ssmconn.UpdateDocument(updateDocInput) 443 444 if isAWSErr(err, "DuplicateDocumentContent", "") { 445 log.Printf("[DEBUG] Content is a duplicate of the latest version so update is not necessary: %s", d.Id()) 446 log.Printf("[INFO] Updating the default version to the latest version %s: %s", newDefaultVersion, d.Id()) 447 448 newDefaultVersion = d.Get("latest_version").(string) 449 } else if err != nil { 450 return errwrap.Wrapf("Error updating SSM document: {{err}}", err) 451 } else { 452 log.Printf("[INFO] Updating the default version to the new version %s: %s", newDefaultVersion, d.Id()) 453 newDefaultVersion = *updated.DocumentDescription.DocumentVersion 454 } 455 456 updateDefaultInput := &ssm.UpdateDocumentDefaultVersionInput{ 457 Name: aws.String(name), 458 DocumentVersion: aws.String(newDefaultVersion), 459 } 460 461 _, err = ssmconn.UpdateDocumentDefaultVersion(updateDefaultInput) 462 463 if err != nil { 464 return errwrap.Wrapf("Error updating the default document version to that of the updated document: {{err}}", err) 465 } 466 return nil 467 } 468 469 func validateAwsSSMDocumentType(v interface{}, k string) (ws []string, errors []error) { 470 value := v.(string) 471 types := map[string]bool{ 472 "Command": true, 473 "Policy": true, 474 "Automation": true, 475 } 476 477 if !types[value] { 478 errors = append(errors, fmt.Errorf("Document type %s is invalid. Valid types are Command, Policy or Automation", value)) 479 } 480 return 481 }