github.com/nathanielks/terraform@v0.6.1-0.20170509030759-13e1a62319dc/builtin/providers/aws/resource_aws_iam_role.go (about)

     1  package aws
     2  
     3  import (
     4  	"fmt"
     5  	"net/url"
     6  	"regexp"
     7  	"time"
     8  
     9  	"github.com/aws/aws-sdk-go/aws"
    10  	"github.com/aws/aws-sdk-go/aws/awserr"
    11  	"github.com/aws/aws-sdk-go/service/iam"
    12  
    13  	"github.com/hashicorp/terraform/helper/resource"
    14  	"github.com/hashicorp/terraform/helper/schema"
    15  )
    16  
    17  func resourceAwsIamRole() *schema.Resource {
    18  	return &schema.Resource{
    19  		Create: resourceAwsIamRoleCreate,
    20  		Read:   resourceAwsIamRoleRead,
    21  		Update: resourceAwsIamRoleUpdate,
    22  		Delete: resourceAwsIamRoleDelete,
    23  		Importer: &schema.ResourceImporter{
    24  			State: schema.ImportStatePassthrough,
    25  		},
    26  
    27  		Schema: map[string]*schema.Schema{
    28  			"arn": {
    29  				Type:     schema.TypeString,
    30  				Computed: true,
    31  			},
    32  
    33  			"unique_id": {
    34  				Type:     schema.TypeString,
    35  				Computed: true,
    36  			},
    37  
    38  			"name": {
    39  				Type:          schema.TypeString,
    40  				Optional:      true,
    41  				Computed:      true,
    42  				ForceNew:      true,
    43  				ConflictsWith: []string{"name_prefix"},
    44  				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
    45  					// https://github.com/boto/botocore/blob/2485f5c/botocore/data/iam/2010-05-08/service-2.json#L8329-L8334
    46  					value := v.(string)
    47  					if len(value) > 64 {
    48  						errors = append(errors, fmt.Errorf(
    49  							"%q cannot be longer than 64 characters", k))
    50  					}
    51  					if !regexp.MustCompile("^[\\w+=,.@-]*$").MatchString(value) {
    52  						errors = append(errors, fmt.Errorf(
    53  							"%q must match [\\w+=,.@-]", k))
    54  					}
    55  					return
    56  				},
    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) > 32 {
    67  						errors = append(errors, fmt.Errorf(
    68  							"%q cannot be longer than 32 characters, name is limited to 64", 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  
    78  			"path": {
    79  				Type:     schema.TypeString,
    80  				Optional: true,
    81  				Default:  "/",
    82  				ForceNew: true,
    83  			},
    84  
    85  			"description": {
    86  				Type:         schema.TypeString,
    87  				Optional:     true,
    88  				ValidateFunc: validateIamRoleDescription,
    89  			},
    90  
    91  			"assume_role_policy": {
    92  				Type:             schema.TypeString,
    93  				Required:         true,
    94  				DiffSuppressFunc: suppressEquivalentAwsPolicyDiffs,
    95  				ValidateFunc:     validateJsonString,
    96  			},
    97  
    98  			"create_date": {
    99  				Type:     schema.TypeString,
   100  				Computed: true,
   101  			},
   102  		},
   103  	}
   104  }
   105  
   106  func resourceAwsIamRoleCreate(d *schema.ResourceData, meta interface{}) error {
   107  	iamconn := meta.(*AWSClient).iamconn
   108  
   109  	var name string
   110  	if v, ok := d.GetOk("name"); ok {
   111  		name = v.(string)
   112  	} else if v, ok := d.GetOk("name_prefix"); ok {
   113  		name = resource.PrefixedUniqueId(v.(string))
   114  	} else {
   115  		name = resource.UniqueId()
   116  	}
   117  
   118  	request := &iam.CreateRoleInput{
   119  		Path:                     aws.String(d.Get("path").(string)),
   120  		RoleName:                 aws.String(name),
   121  		AssumeRolePolicyDocument: aws.String(d.Get("assume_role_policy").(string)),
   122  	}
   123  
   124  	if v, ok := d.GetOk("description"); ok {
   125  		request.Description = aws.String(v.(string))
   126  	}
   127  
   128  	var createResp *iam.CreateRoleOutput
   129  	err := resource.Retry(30*time.Second, func() *resource.RetryError {
   130  		var err error
   131  		createResp, err = iamconn.CreateRole(request)
   132  		// IAM users (referenced in Principal field of assume policy)
   133  		// can take ~30 seconds to propagate in AWS
   134  		if isAWSErr(err, "MalformedPolicyDocument", "Invalid principal in policy") {
   135  			return resource.RetryableError(err)
   136  		}
   137  		return resource.NonRetryableError(err)
   138  	})
   139  	if err != nil {
   140  		return fmt.Errorf("Error creating IAM Role %s: %s", name, err)
   141  	}
   142  	d.SetId(*createResp.Role.RoleName)
   143  	return resourceAwsIamRoleRead(d, meta)
   144  }
   145  
   146  func resourceAwsIamRoleRead(d *schema.ResourceData, meta interface{}) error {
   147  	iamconn := meta.(*AWSClient).iamconn
   148  
   149  	request := &iam.GetRoleInput{
   150  		RoleName: aws.String(d.Id()),
   151  	}
   152  
   153  	getResp, err := iamconn.GetRole(request)
   154  	if err != nil {
   155  		if iamerr, ok := err.(awserr.Error); ok && iamerr.Code() == "NoSuchEntity" { // XXX test me
   156  			d.SetId("")
   157  			return nil
   158  		}
   159  		return fmt.Errorf("Error reading IAM Role %s: %s", d.Id(), err)
   160  	}
   161  
   162  	role := getResp.Role
   163  
   164  	if err := d.Set("name", role.RoleName); err != nil {
   165  		return err
   166  	}
   167  	if err := d.Set("arn", role.Arn); err != nil {
   168  		return err
   169  	}
   170  	if err := d.Set("path", role.Path); err != nil {
   171  		return err
   172  	}
   173  	if err := d.Set("unique_id", role.RoleId); err != nil {
   174  		return err
   175  	}
   176  	if err := d.Set("create_date", role.CreateDate.Format(time.RFC3339)); err != nil {
   177  		return err
   178  	}
   179  
   180  	if role.Description != nil {
   181  		// the description isn't present in the response to CreateRole.
   182  		if err := d.Set("description", role.Description); err != nil {
   183  			return err
   184  		}
   185  	}
   186  
   187  	assumRolePolicy, err := url.QueryUnescape(*role.AssumeRolePolicyDocument)
   188  	if err != nil {
   189  		return err
   190  	}
   191  	if err := d.Set("assume_role_policy", assumRolePolicy); err != nil {
   192  		return err
   193  	}
   194  	return nil
   195  }
   196  
   197  func resourceAwsIamRoleUpdate(d *schema.ResourceData, meta interface{}) error {
   198  	iamconn := meta.(*AWSClient).iamconn
   199  
   200  	if d.HasChange("assume_role_policy") {
   201  		assumeRolePolicyInput := &iam.UpdateAssumeRolePolicyInput{
   202  			RoleName:       aws.String(d.Id()),
   203  			PolicyDocument: aws.String(d.Get("assume_role_policy").(string)),
   204  		}
   205  		_, err := iamconn.UpdateAssumeRolePolicy(assumeRolePolicyInput)
   206  		if err != nil {
   207  			if iamerr, ok := err.(awserr.Error); ok && iamerr.Code() == "NoSuchEntity" {
   208  				d.SetId("")
   209  				return nil
   210  			}
   211  			return fmt.Errorf("Error Updating IAM Role (%s) Assume Role Policy: %s", d.Id(), err)
   212  		}
   213  	}
   214  
   215  	if d.HasChange("description") {
   216  		roleDescriptionInput := &iam.UpdateRoleDescriptionInput{
   217  			RoleName:    aws.String(d.Id()),
   218  			Description: aws.String(d.Get("description").(string)),
   219  		}
   220  		_, err := iamconn.UpdateRoleDescription(roleDescriptionInput)
   221  		if err != nil {
   222  			if iamerr, ok := err.(awserr.Error); ok && iamerr.Code() == "NoSuchEntity" {
   223  				d.SetId("")
   224  				return nil
   225  			}
   226  			return fmt.Errorf("Error Updating IAM Role (%s) Assume Role Policy: %s", d.Id(), err)
   227  		}
   228  	}
   229  
   230  	return nil
   231  }
   232  
   233  func resourceAwsIamRoleDelete(d *schema.ResourceData, meta interface{}) error {
   234  	iamconn := meta.(*AWSClient).iamconn
   235  
   236  	// Roles cannot be destroyed when attached to an existing Instance Profile
   237  	resp, err := iamconn.ListInstanceProfilesForRole(&iam.ListInstanceProfilesForRoleInput{
   238  		RoleName: aws.String(d.Id()),
   239  	})
   240  	if err != nil {
   241  		return fmt.Errorf("Error listing Profiles for IAM Role (%s) when trying to delete: %s", d.Id(), err)
   242  	}
   243  
   244  	// Loop and remove this Role from any Profiles
   245  	if len(resp.InstanceProfiles) > 0 {
   246  		for _, i := range resp.InstanceProfiles {
   247  			_, err := iamconn.RemoveRoleFromInstanceProfile(&iam.RemoveRoleFromInstanceProfileInput{
   248  				InstanceProfileName: i.InstanceProfileName,
   249  				RoleName:            aws.String(d.Id()),
   250  			})
   251  			if err != nil {
   252  				return fmt.Errorf("Error deleting IAM Role %s: %s", d.Id(), err)
   253  			}
   254  		}
   255  	}
   256  
   257  	request := &iam.DeleteRoleInput{
   258  		RoleName: aws.String(d.Id()),
   259  	}
   260  
   261  	if _, err := iamconn.DeleteRole(request); err != nil {
   262  		return fmt.Errorf("Error deleting IAM Role %s: %s", d.Id(), err)
   263  	}
   264  	return nil
   265  }