Skip to content

Commit 823ab13

Browse files
authored
Add support for instance-permission-set in azure (#632)
* Add support for instance-permission-set in azure. The instance-permission-set is parsed as a comma-separated list of ARM resource ids in the form '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ManagedIdentity/userAssignedIdentities/{identityName}'. * Update docs. * Support permission sets in azure (task). * Minor updates to aws permission set resource. Fix typo in error message. Move regexp compilation to package level (quicker panic). Handle error case first. * Add permission set parsing step to azure task creation. Also proposing a refactoring of task command step definition - renumbering the progress indication manually seems a bit tedious. * Revert "Add permission set parsing step to azure task creation." This reverts commit aaa06e9. * Add permission set read step. * Specify resource identity type. * Improved ARMID matching regular expression. * Propagate command line permission set value to task.
1 parent bfca950 commit 823ab13

File tree

8 files changed

+127
-25
lines changed

8 files changed

+127
-25
lines changed

cmd/create/create.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@ func (o *Options) Run(cmd *cobra.Command, args []string, cloud *common.Cloud) er
9393
Ports: &[]uint16{22},
9494
},
9595
},
96-
Parallelism: uint16(1),
96+
Parallelism: uint16(1),
97+
PermissionSet: o.PermissionSet,
9798
}
9899

99100
cfg.Spot = common.Spot(common.SpotDisabled)

docs/resources/task.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -274,8 +274,8 @@ A service account email and a [list of scopes](https://cloud.google.com/sdk/gclo
274274
`permission_set = "sa-name@project_id.iam.gserviceaccount.com,scopes=storage-rw"`
275275

276276
#### Microsoft Azure
277-
278-
[Not yet implemented](https://github.com/iterative/terraform-provider-iterative/issues/559)
277+
A comma-separated list of [user-assigned identity](https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview) ARM resource ids, e.g.:
278+
`permission_set = "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ManagedIdentity/userAssignedIdentities/{identityName}"`
279279

280280
#### Kubernetes
281281

iterative/azure/provider.go

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import (
1616
"github.com/Azure/go-autorest/autorest/to"
1717

1818
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
19+
20+
azresources "terraform-provider-iterative/task/az/resources"
1921
)
2022

2123
//ResourceMachineCreate creates AWS instance
@@ -34,6 +36,11 @@ func ResourceMachineCreate(ctx context.Context, d *schema.ResourceData, m interf
3436
spot := d.Get("spot").(bool)
3537
spotPrice := d.Get("spot_price").(float64)
3638

39+
userAssignedIdentities, err := getUserAssignedIdentityMap(d.Get("instance_permission_set").(string))
40+
if err != nil {
41+
return err
42+
}
43+
3744
metadata := map[string]*string{}
3845
for key, value := range d.Get("metadata").(map[string]interface{}) {
3946
stringValue := value.(string)
@@ -199,7 +206,11 @@ func ResourceMachineCreate(ctx context.Context, d *schema.ResourceData, m interf
199206

200207
vmClient, _ := getVMClient(subscriptionID)
201208
vmSettings := compute.VirtualMachine{
202-
Tags: metadata,
209+
Tags: metadata,
210+
Identity: &compute.VirtualMachineIdentity{
211+
UserAssignedIdentities: userAssignedIdentities,
212+
Type: compute.ResourceIdentityTypeSystemAssignedUserAssigned,
213+
},
203214
Location: to.StringPtr(region),
204215
VirtualMachineProperties: &compute.VirtualMachineProperties{
205216
HardwareProfile: &compute.HardwareProfile{
@@ -360,6 +371,30 @@ func getVMClient(subscriptionID string) (compute.VirtualMachinesClient, error) {
360371
return client, err
361372
}
362373

374+
// getUserAssignedIdentityMap generates a map of user-assigned identity values from a comma-separated
375+
// list of ARM resource ids.
376+
func getUserAssignedIdentityMap(identitiesRaw string) (map[string]*compute.VirtualMachineIdentityUserAssignedIdentitiesValue, error) {
377+
if len(strings.TrimSpace(identitiesRaw)) == 0 {
378+
return nil, nil
379+
}
380+
identities := strings.Split(identitiesRaw, ",")
381+
identityMap := map[string]*compute.VirtualMachineIdentityUserAssignedIdentitiesValue{}
382+
for _, identity := range identities {
383+
identity = strings.TrimSpace(identity)
384+
if len(identity) == 0 {
385+
continue
386+
}
387+
if err := azresources.ValidateARMID(identity); err != nil {
388+
return nil, err
389+
}
390+
identityMap[identity] = &compute.VirtualMachineIdentityUserAssignedIdentitiesValue{}
391+
}
392+
if len(identityMap) == 0 {
393+
return nil, nil
394+
}
395+
return identityMap, nil
396+
}
397+
363398
//GetRegion maps region to real cloud regions
364399
func GetRegion(region string) string {
365400
instanceRegions := make(map[string]string)

iterative/resource_machine.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ func resourceMachineCreate(ctx context.Context, d *schema.ResourceData, m interf
181181

182182
cloud := d.Get("cloud").(string)
183183

184-
if len(d.Get("instance_permission_set").(string)) > 0 && (cloud == "azure" || cloud == "kubernetes") {
184+
if len(d.Get("instance_permission_set").(string)) > 0 && (cloud == "kubernetes") {
185185
diags = append(diags, diag.Diagnostic{
186186
Severity: diag.Error,
187187
Summary: fmt.Sprintf("instance_permission_set is not yet supported in " + cloud),

task/aws/resources/data_source_permission_set.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import (
1111
"terraform-provider-iterative/task/aws/client"
1212
)
1313

14+
var validateARN = regexp.MustCompile(`arn:aws:iam::[\d]*:instance-profile/[\S]*`)
15+
1416
func NewPermissionSet(client *client.Client, identifier string) *PermissionSet {
1517
ps := new(PermissionSet)
1618
ps.Client = client
@@ -31,12 +33,11 @@ func (ps *PermissionSet) Read(ctx context.Context) error {
3133
ps.Resource = nil
3234
return nil
3335
}
34-
re := regexp.MustCompile(`arn:aws:iam::[\d]*:instance-profile/[\S]*`)
35-
if re.MatchString(arn) {
36-
ps.Resource = &types.LaunchTemplateIamInstanceProfileSpecificationRequest{
37-
Arn: aws.String(arn),
38-
}
39-
return nil
36+
if !validateARN.MatchString(arn) {
37+
return fmt.Errorf("invalid IAM Instance Profile: %s", arn)
38+
}
39+
ps.Resource = &types.LaunchTemplateIamInstanceProfileSpecificationRequest{
40+
Arn: aws.String(arn),
4041
}
41-
return fmt.Errorf("invlaid IAM Instance Profile: %s", arn)
42+
return nil
4243
}

task/az/resources/data_source_permission_set.go

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,25 @@ package resources
33
import (
44
"context"
55
"fmt"
6+
"regexp"
7+
"strings"
68

79
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2020-06-30/compute"
810

911
"terraform-provider-iterative/task/az/client"
1012
)
1113

14+
// validateARMID is a regular expression for validating user-assigned identity ids.
15+
var validateARMID = regexp.MustCompile(`^/subscriptions/(\w{8}-\w{4}-\w{4}-\w{4}-\w{12})/resourceGroups/(.*)/providers/Microsoft.ManagedIdentity/userAssignedIdentities/(.*)`)
16+
17+
// ValidateARMID validates the user-assigned identity value.
18+
func ValidateARMID(id string) error {
19+
if !validateARMID.MatchString(id) {
20+
return fmt.Errorf("invalid user-assigned identity id: %q", id)
21+
}
22+
return nil
23+
}
24+
1225
func NewPermissionSet(client *client.Client, identifer string) *PermissionSet {
1326
ps := new(PermissionSet)
1427
ps.Client = client
@@ -23,9 +36,26 @@ type PermissionSet struct {
2336
}
2437

2538
func (ps *PermissionSet) Read(ctx context.Context) error {
26-
if ps.Identifier != "" {
27-
return fmt.Errorf("not yet implemented")
39+
identities := strings.Split(ps.Identifier, ",")
40+
identityMap := map[string]*compute.VirtualMachineScaleSetIdentityUserAssignedIdentitiesValue{}
41+
for _, identity := range identities {
42+
identity = strings.TrimSpace(identity)
43+
if identity == "" {
44+
continue
45+
}
46+
if err := ValidateARMID(identity); err != nil {
47+
return err
48+
}
49+
50+
identityMap[identity] = &compute.VirtualMachineScaleSetIdentityUserAssignedIdentitiesValue{}
51+
}
52+
if len(identityMap) == 0 {
53+
ps.Resource = nil
54+
return nil
55+
}
56+
ps.Resource = &compute.VirtualMachineScaleSetIdentity{
57+
UserAssignedIdentities: identityMap,
58+
Type: compute.ResourceIdentityTypeSystemAssignedUserAssigned,
2859
}
29-
ps.Resource = nil
3060
return nil
3161
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package resources_test
2+
3+
import (
4+
"terraform-provider-iterative/task/az/resources"
5+
"testing"
6+
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
// TestARMIDValidation tests the validation of user-assigned identity values.
11+
func TestARMIDValidation(t *testing.T) {
12+
tests := []struct {
13+
Identity string
14+
ExpectError string
15+
}{{
16+
Identity: "/subscriptions/cse78759-ef49-49f7-b371-f6841fa82182/resourceGroups/resource-group/providers/Microsoft.ManagedIdentity/userAssignedIdentities/managed-identity",
17+
ExpectError: "",
18+
}, {
19+
Identity: "/subscriptions/no-valid",
20+
ExpectError: `invalid user-assigned identity id: "/subscriptions/no-valid"`,
21+
}}
22+
23+
for _, test := range tests {
24+
err := resources.ValidateARMID(test.Identity)
25+
if test.ExpectError == "" {
26+
require.NoError(t, err)
27+
} else {
28+
require.EqualError(t, err, test.ExpectError)
29+
}
30+
}
31+
}

task/az/task.go

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -110,45 +110,49 @@ type Task struct {
110110

111111
func (t *Task) Create(ctx context.Context) error {
112112
logrus.Info("Creating resources...")
113-
logrus.Info("[1/10] Creating ResourceGroup...")
113+
logrus.Info("[1/11] Creating PermissionSet...")
114+
if err := t.DataSources.PermissionSet.Read(ctx); err != nil {
115+
return err
116+
}
117+
logrus.Info("[2/11] Creating ResourceGroup...")
114118
if err := t.Resources.ResourceGroup.Create(ctx); err != nil {
115119
return err
116120
}
117-
logrus.Info("[2/10] Creating StorageAccount...")
121+
logrus.Info("[3/11] Creating StorageAccount...")
118122
if err := t.Resources.StorageAccount.Create(ctx); err != nil {
119123
return err
120124
}
121-
logrus.Info("[3/10] Creating BlobContainer...")
125+
logrus.Info("[4/11] Creating BlobContainer...")
122126
if err := t.Resources.BlobContainer.Create(ctx); err != nil {
123127
return err
124128
}
125-
logrus.Info("[4/10] Creating Credentials...")
129+
logrus.Info("[5/11] Creating Credentials...")
126130
if err := t.DataSources.Credentials.Read(ctx); err != nil {
127131
return err
128132
}
129-
logrus.Info("[5/10] Creating VirtualNetwork...")
133+
logrus.Info("[6/11] Creating VirtualNetwork...")
130134
if err := t.Resources.VirtualNetwork.Create(ctx); err != nil {
131135
return err
132136
}
133-
logrus.Info("[6/10] Creating SecurityGroup...")
137+
logrus.Info("[7/11] Creating SecurityGroup...")
134138
if err := t.Resources.SecurityGroup.Create(ctx); err != nil {
135139
return err
136140
}
137-
logrus.Info("[7/10] Creating Subnet...")
141+
logrus.Info("[8/11] Creating Subnet...")
138142
if err := t.Resources.Subnet.Create(ctx); err != nil {
139143
return err
140144
}
141-
logrus.Info("[8/10] Creating VirtualMachineScaleSet...")
145+
logrus.Info("[9/11] Creating VirtualMachineScaleSet...")
142146
if err := t.Resources.VirtualMachineScaleSet.Create(ctx); err != nil {
143147
return err
144148
}
145-
logrus.Info("[9/10] Uploading Directory...")
149+
logrus.Info("[10/11] Uploading Directory...")
146150
if t.Attributes.Environment.Directory != "" {
147151
if err := t.Push(ctx, t.Attributes.Environment.Directory); err != nil {
148152
return err
149153
}
150154
}
151-
logrus.Info("[10/10] Starting task...")
155+
logrus.Info("[11/11] Starting task...")
152156
if err := t.Start(ctx); err != nil {
153157
return err
154158
}

0 commit comments

Comments
 (0)