github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/builtin/providers/aws/resource_aws_iam_policy.go (about)

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