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  }