github.com/bpineau/terraform@v0.8.0-rc1.0.20161126184705-a8886012d185/builtin/providers/aws/resource_aws_iam_policy.go (about)

     1  package aws
     2  
     3  import (
     4  	"fmt"
     5  	"regexp"
     6  
     7  	"github.com/aws/aws-sdk-go/aws"
     8  	"github.com/aws/aws-sdk-go/aws/awserr"
     9  	"github.com/aws/aws-sdk-go/service/iam"
    10  
    11  	"github.com/hashicorp/terraform/helper/resource"
    12  	"github.com/hashicorp/terraform/helper/schema"
    13  )
    14  
    15  func resourceAwsIamPolicy() *schema.Resource {
    16  	return &schema.Resource{
    17  		Create: resourceAwsIamPolicyCreate,
    18  		Read:   resourceAwsIamPolicyRead,
    19  		Update: resourceAwsIamPolicyUpdate,
    20  		Delete: resourceAwsIamPolicyDelete,
    21  
    22  		Schema: map[string]*schema.Schema{
    23  			"description": &schema.Schema{
    24  				Type:     schema.TypeString,
    25  				ForceNew: true,
    26  				Optional: true,
    27  			},
    28  			"path": &schema.Schema{
    29  				Type:     schema.TypeString,
    30  				Optional: true,
    31  				Default:  "/",
    32  				ForceNew: true,
    33  			},
    34  			"policy": &schema.Schema{
    35  				Type:         schema.TypeString,
    36  				Required:     true,
    37  				ValidateFunc: validateJsonString,
    38  			},
    39  			"name": &schema.Schema{
    40  				Type:          schema.TypeString,
    41  				Optional:      true,
    42  				Computed:      true,
    43  				ForceNew:      true,
    44  				ConflictsWith: []string{"name_prefix"},
    45  				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
    46  					// https://github.com/boto/botocore/blob/2485f5c/botocore/data/iam/2010-05-08/service-2.json#L8329-L8334
    47  					value := v.(string)
    48  					if len(value) > 128 {
    49  						errors = append(errors, fmt.Errorf(
    50  							"%q cannot be longer than 128 characters", k))
    51  					}
    52  					if !regexp.MustCompile("^[\\w+=,.@-]*$").MatchString(value) {
    53  						errors = append(errors, fmt.Errorf(
    54  							"%q must match [\\w+=,.@-]", k))
    55  					}
    56  					return
    57  				},
    58  			},
    59  			"name_prefix": {
    60  				Type:     schema.TypeString,
    61  				Optional: true,
    62  				ForceNew: true,
    63  				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
    64  					// https://github.com/boto/botocore/blob/2485f5c/botocore/data/iam/2010-05-08/service-2.json#L8329-L8334
    65  					value := v.(string)
    66  					if len(value) > 96 {
    67  						errors = append(errors, fmt.Errorf(
    68  							"%q cannot be longer than 96 characters, name is limited to 128", k))
    69  					}
    70  					if !regexp.MustCompile("^[\\w+=,.@-]*$").MatchString(value) {
    71  						errors = append(errors, fmt.Errorf(
    72  							"%q must match [\\w+=,.@-]", k))
    73  					}
    74  					return
    75  				},
    76  			},
    77  			"arn": &schema.Schema{
    78  				Type:     schema.TypeString,
    79  				Computed: true,
    80  			},
    81  		},
    82  	}
    83  }
    84  
    85  func resourceAwsIamPolicyCreate(d *schema.ResourceData, meta interface{}) error {
    86  	iamconn := meta.(*AWSClient).iamconn
    87  
    88  	var name string
    89  	if v, ok := d.GetOk("name"); ok {
    90  		name = v.(string)
    91  	} else if v, ok := d.GetOk("name_prefix"); ok {
    92  		name = resource.PrefixedUniqueId(v.(string))
    93  	} else {
    94  		name = resource.UniqueId()
    95  	}
    96  
    97  	request := &iam.CreatePolicyInput{
    98  		Description:    aws.String(d.Get("description").(string)),
    99  		Path:           aws.String(d.Get("path").(string)),
   100  		PolicyDocument: aws.String(d.Get("policy").(string)),
   101  		PolicyName:     aws.String(name),
   102  	}
   103  
   104  	response, err := iamconn.CreatePolicy(request)
   105  	if err != nil {
   106  		return fmt.Errorf("Error creating IAM policy %s: %s", name, err)
   107  	}
   108  
   109  	return readIamPolicy(d, response.Policy)
   110  }
   111  
   112  func resourceAwsIamPolicyRead(d *schema.ResourceData, meta interface{}) error {
   113  	iamconn := meta.(*AWSClient).iamconn
   114  
   115  	request := &iam.GetPolicyInput{
   116  		PolicyArn: aws.String(d.Id()),
   117  	}
   118  
   119  	response, err := iamconn.GetPolicy(request)
   120  	if err != nil {
   121  		if iamerr, ok := err.(awserr.Error); ok && iamerr.Code() == "NoSuchEntity" {
   122  			d.SetId("")
   123  			return nil
   124  		}
   125  		return fmt.Errorf("Error reading IAM policy %s: %s", d.Id(), err)
   126  	}
   127  
   128  	return readIamPolicy(d, response.Policy)
   129  }
   130  
   131  func resourceAwsIamPolicyUpdate(d *schema.ResourceData, meta interface{}) error {
   132  	iamconn := meta.(*AWSClient).iamconn
   133  
   134  	if err := iamPolicyPruneVersions(d.Id(), iamconn); err != nil {
   135  		return err
   136  	}
   137  
   138  	if !d.HasChange("policy") {
   139  		return nil
   140  	}
   141  	request := &iam.CreatePolicyVersionInput{
   142  		PolicyArn:      aws.String(d.Id()),
   143  		PolicyDocument: aws.String(d.Get("policy").(string)),
   144  		SetAsDefault:   aws.Bool(true),
   145  	}
   146  
   147  	if _, err := iamconn.CreatePolicyVersion(request); err != nil {
   148  		return fmt.Errorf("Error updating IAM policy %s: %s", d.Id(), err)
   149  	}
   150  	return nil
   151  }
   152  
   153  func resourceAwsIamPolicyDelete(d *schema.ResourceData, meta interface{}) error {
   154  	iamconn := meta.(*AWSClient).iamconn
   155  
   156  	if err := iamPolicyDeleteNondefaultVersions(d.Id(), iamconn); err != nil {
   157  		return err
   158  	}
   159  
   160  	request := &iam.DeletePolicyInput{
   161  		PolicyArn: aws.String(d.Id()),
   162  	}
   163  
   164  	_, err := iamconn.DeletePolicy(request)
   165  	if err != nil {
   166  		if iamerr, ok := err.(awserr.Error); ok && iamerr.Code() == "NoSuchEntity" {
   167  			return nil
   168  		}
   169  		return fmt.Errorf("Error reading IAM policy %s: %#v", d.Id(), err)
   170  	}
   171  	return nil
   172  }
   173  
   174  // iamPolicyPruneVersions deletes the oldest versions.
   175  //
   176  // Old versions are deleted until there are 4 or less remaining, which means at
   177  // least one more can be created before hitting the maximum of 5.
   178  //
   179  // The default version is never deleted.
   180  
   181  func iamPolicyPruneVersions(arn string, iamconn *iam.IAM) error {
   182  	versions, err := iamPolicyListVersions(arn, iamconn)
   183  	if err != nil {
   184  		return err
   185  	}
   186  	if len(versions) < 5 {
   187  		return nil
   188  	}
   189  
   190  	var oldestVersion *iam.PolicyVersion
   191  
   192  	for _, version := range versions {
   193  		if *version.IsDefaultVersion {
   194  			continue
   195  		}
   196  		if oldestVersion == nil ||
   197  			version.CreateDate.Before(*oldestVersion.CreateDate) {
   198  			oldestVersion = version
   199  		}
   200  	}
   201  
   202  	if err := iamPolicyDeleteVersion(arn, *oldestVersion.VersionId, iamconn); err != nil {
   203  		return err
   204  	}
   205  	return nil
   206  }
   207  
   208  func iamPolicyDeleteNondefaultVersions(arn string, iamconn *iam.IAM) error {
   209  	versions, err := iamPolicyListVersions(arn, iamconn)
   210  	if err != nil {
   211  		return err
   212  	}
   213  
   214  	for _, version := range versions {
   215  		if *version.IsDefaultVersion {
   216  			continue
   217  		}
   218  		if err := iamPolicyDeleteVersion(arn, *version.VersionId, iamconn); err != nil {
   219  			return err
   220  		}
   221  	}
   222  
   223  	return nil
   224  }
   225  
   226  func iamPolicyDeleteVersion(arn, versionID string, iamconn *iam.IAM) error {
   227  	request := &iam.DeletePolicyVersionInput{
   228  		PolicyArn: aws.String(arn),
   229  		VersionId: aws.String(versionID),
   230  	}
   231  
   232  	_, err := iamconn.DeletePolicyVersion(request)
   233  	if err != nil {
   234  		return fmt.Errorf("Error deleting version %s from IAM policy %s: %s", versionID, arn, err)
   235  	}
   236  	return nil
   237  }
   238  
   239  func iamPolicyListVersions(arn string, iamconn *iam.IAM) ([]*iam.PolicyVersion, error) {
   240  	request := &iam.ListPolicyVersionsInput{
   241  		PolicyArn: aws.String(arn),
   242  	}
   243  
   244  	response, err := iamconn.ListPolicyVersions(request)
   245  	if err != nil {
   246  		return nil, fmt.Errorf("Error listing versions for IAM policy %s: %s", arn, err)
   247  	}
   248  	return response.Versions, nil
   249  }
   250  
   251  func readIamPolicy(d *schema.ResourceData, policy *iam.Policy) error {
   252  	d.SetId(*policy.Arn)
   253  	if policy.Description != nil {
   254  		// the description isn't present in the response to CreatePolicy.
   255  		if err := d.Set("description", *policy.Description); err != nil {
   256  			return err
   257  		}
   258  	}
   259  	if err := d.Set("path", *policy.Path); err != nil {
   260  		return err
   261  	}
   262  	if err := d.Set("name", *policy.PolicyName); err != nil {
   263  		return err
   264  	}
   265  	if err := d.Set("arn", *policy.Arn); err != nil {
   266  		return err
   267  	}
   268  	// TODO: set policy
   269  
   270  	return nil
   271  }