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