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