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 }