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  }