Hey all. I have an `InputList<string>` of Azure ro...
# dotnet
n
Hey all. I have an
InputList<string>
of Azure role ids I'm accessing in a custom component. Essentially I'm trying to loop over the roles and create a new
AzureNative.Authorization.RoleAssignment
for each ID. Initially I was successful, until I changed a name (trigger a teardown of the old resources). The error looks like so:
Copy code
Type                                             Name                                                               Status                  Info
     pulumi:pulumi:Stack                              api-infra-data-dev                                  **failed**              1 error
 +   └─ custom-component:authorization:RoleAssignment     mi-storage-account-api-roles                                       created                 
 +      ├─ random:index:RandomUuid                    dev-mi-storage-account-api-roles-role-assignment-id-eus2-0002  created (0.15s)         
 +      ├─ random:index:RandomUuid                    dev-mi-storage-account-api-roles-role-assignment-id-eus2-0001  created (0.31s)         
 +      ├─ random:index:RandomUuid                    dev-mi-storage-account-api-roles-role-assignment-id-eus2-0003  created (0.45s)         
 +      ├─ random:index:RandomUuid                    dev-mi-storage-account-api-roles-role-assignment-id-eus2-0004  created (0.61s)         
 +      ├─ azure-native:authorization:RoleAssignment  dev-mi-storage-account-api-roles-role-assignment-eus2-0002     **creating failed**     1 error
 +      ├─ azure-native:authorization:RoleAssignment  dev-mi-storage-account-api-roles-role-assignment-eus2-0001     **creating failed**     1 error
 +      ├─ azure-native:authorization:RoleAssignment  dev-mi-storage-account-api-roles-role-assignment-eus2-0003     **creating failed**     1 error
 +      └─ azure-native:authorization:RoleAssignment  dev-mi-storage-account-api-roles-role-assignment-eus2-0004     **creating failed**     1 error
More details:
Copy code
azure-native:authorization:RoleAssignment (dev-mi-storage-account-api-roles-role-assignment-eus2-0002):
    error: autorest/azure: Service returned an error. Status=<nil> Code="RoleAssignmentExists" Message="The role assignment already exists."
Based on the output of the command line it appears it did not attempt to remove the old resources first. I'm guessing the issue is because of using a foreach in an apply function. This is how it's written using
Apply
Copy code
args.RoleDefinitionId.Apply(roles =>
        {
            var instance = args.Instance;
            foreach (var roleId in roles)
            {
                AssignSingleRole(name, roleId, args, instance);
                instance++;
            }
        
            return "";
        });
Reading through issues/posts I've learned it's not the right practice. But I'm failing to understand what is the right practice for looping over
InputList<T>
. I'm running into a wall. Does anyone have an example or direction for how to properly loop over InputList? Thank you.
m
looking at your code
AssignSingleRole(name ...)
, what's the
name
parameter used for ? I don't really work on Azure but i suppose the RoleAssignment resource name shouldn't be duplicated
n
Name is defined when instantiating the custom component:
Copy code
public RoleAssignment(string name, RoleAssignmentArgs args, ComponentResourceOptions? options = null) 
        : base($"custom-component:authorization:{nameof(RoleAssignment)}", name, options)
In
AssignSingleRole(name ...)
the name is extending upon for a Random UUID, and Role Assignment. That said, the name shouldn't be duplicated, and from what I can tell it isn't.
The name looks something like this
Copy code
mi-storage-account-api-roles
And the component resource names look like this:
Copy code
dev-mi-storage-account-api-roles-role-assignment-id-eus2-0001 
dev-mi-storage-account-api-roles-role-assignment-eus2-0001
m
I believe the component resource name you're referring is the one highlighted in yellow. just do make sure the RedArrow pointed name is not duplicated too
n
Yes, that's right. Based on the log it's not duplicated. This is what the preview output looks like when I change the name.
Copy code
+   ├─ custom-component:authorization:RoleAssignment     mi-storage-account-api-rolesz                                       create     
 +   │  ├─ random:index:RandomUuid                    dev-mi-storage-account-api-rolesz-role-assignment-id-eus2-0002  create     
 +   │  ├─ random:index:RandomUuid                    dev-mi-storage-account-api-rolesz-role-assignment-id-eus2-0001  create     
 +   │  ├─ random:index:RandomUuid                    dev-mi-storage-account-api-rolesz-role-assignment-id-eus2-0003  create     
 +   │  ├─ random:index:RandomUuid                    dev-mi-storage-account-api-rolesz-role-assignment-id-eus2-0004  create     
 +   │  ├─ azure-native:authorization:RoleAssignment  dev-mi-storage-account-api-rolesz-role-assignment-eus2-0001     create     
 +   │  ├─ azure-native:authorization:RoleAssignment  dev-mi-storage-account-api-rolesz-role-assignment-eus2-0002     create     
 +   │  ├─ azure-native:authorization:RoleAssignment  dev-mi-storage-account-api-rolesz-role-assignment-eus2-0004     create     
 +   │  └─ azure-native:authorization:RoleAssignment  dev-mi-storage-account-api-rolesz-role-assignment-eus2-0003     create     
 -   └─ custom-component:authorization:RoleAssignment     mi-storage-account-api-roles                                        delete     
 -      ├─ azure-native:authorization:RoleAssignment  dev-mi-storage-account-api-roles-role-assignment-eus2-0001      delete     
 -      ├─ azure-native:authorization:RoleAssignment  dev-mi-storage-account-api-roles-role-assignment-eus2-0004      delete     
 -      ├─ azure-native:authorization:RoleAssignment  dev-mi-storage-account-api-roles-role-assignment-eus2-0003      delete     
 -      ├─ azure-native:authorization:RoleAssignment  dev-mi-storage-account-api-roles-role-assignment-eus2-0002      delete     
 -      ├─ random:index:RandomUuid                    dev-mi-storage-account-api-roles-role-assignment-id-eus2-0002   delete     
 -      ├─ random:index:RandomUuid                    dev-mi-storage-account-api-roles-role-assignment-id-eus2-0003   delete     
 -      ├─ random:index:RandomUuid                    dev-mi-storage-account-api-roles-role-assignment-id-eus2-0001   delete     
 -      └─ random:index:RandomUuid                    dev-mi-storage-account-api-roles-role-assignment-id-eus2-0004   delete     

Resources:
    + 9 to create
    - 9 to delete
    18 changes. 2 unchanged
The change is from
mi-storage-account-api-roles
to
mi-storage-account-api-rolesz
-- as seen in the output, all the resources that have the substring ``mi-storage-account-api-roles` are marked for deletion.
m
I'm afraid the output will not show the actual resource name, those are just part of pulumi resource urn. you can have a look on the "details" when u run
pulumi up
n
Here's what the full custom component looks like:
Copy code
public class RoleAssignment : ComponentResource
{
    public RoleAssignment(string name, RoleAssignmentArgs args, ComponentResourceOptions? options = null) 
        : base($"custom-component:authorization:{nameof(RoleAssignment)}", name, options)
    {
        // Issues looping over InputList -- disabled until feedback is provided.
        // args.RoleDefinitionId.Apply(roles =>
        // {
        //     var instance = args.Instance;
        //     foreach (var roleId in roles)
        //     {
        //         AssignSingleRole(name, roleId, args, instance);
        //         instance++;
        //     }
        //
        //     return "";
        // });

        var instance = args.Instance;
        foreach (var roleId in args.RoleDefinitionId)
        {
            AssignSingleRole(name, roleId, args, instance);
            instance++;
        }
        
        // var roleId = args.RoleDefinitionId.Apply(x => x.Single());
        // AssignSingleRole(name, roleId, args);
    }

    private void AssignSingleRole(string name, Input<string> id, RoleAssignmentArgs args, int instance)
    {
        var roleDefinition = GetRoleDefinition.Invoke(new ()
        {
            RoleDefinitionId = id,
            Scope = $"/subscriptions/{args.SubscriptionId}"
        });

        var newRoleId = new Random.RandomUuid(
            args.NameBuilder.GenerateNameWithLocation($"{name}-role-assignment-id", "", instance),
            Random.RandomUuidArgs.Empty,
            new CustomResourceOptions
            {
                Parent = this,
                DeleteBeforeReplace = true,
            });
    
        var roleAssignment = new AzureNative.Authorization.RoleAssignment(
            args.NameBuilder.GenerateNameWithLocation($"{name}-role-assignment", "", instance),
            new ()
            {
                Scope = args.Scope,
                RoleDefinitionId = roleDefinition.Apply(x => x.Id),
                PrincipalId = args.PrincipalId,
                PrincipalType = args.PrincipalType,
                RoleAssignmentName = newRoleId.Id
            },
            new CustomResourceOptions
            {
                Parent = this,
                DependsOn = { newRoleId },
                DeleteBeforeReplace = true,
            });
    }
}

public class RoleAssignmentArgs
{
    public int Instance { get; set; }
    public ResourceNameBuilder NameBuilder { get; set; } = null!;
    public string SubscriptionId { get; set; } = null!;
    public Input<string> Scope { get; set; } = null!;
    public List<string> RoleDefinitionId { get; set; } = null!;
    public Input<string> PrincipalId { get; set; } = null!;
    public PrincipalType PrincipalType { get; set; }
}
And here's how to use it.
Copy code
var roleAssignmentsApiStorageAccount = new CustomAuthorization.RoleAssignment(
        "mi-storage-account-api-roles",
        new RoleAssignmentArgs
        {
            Instance = 1,
            NameBuilder = context.NameBuilder,
            SubscriptionId = context.SubscriptionId,
            Scope = storageAccountApi.Apply(x => x.Id),
            RoleDefinitionId = [
                AzureBuiltInRoleDefinitions.StorageAccountContributor,
                AzureBuiltInRoleDefinitions.StorageBlobDataContributor,
                AzureBuiltInRoleDefinitions.StorageQueueDataContributor,
                AzureBuiltInRoleDefinitions.StorageTableDataContributor,
            ],
            PrincipalId = context.ManagedIdentity.Apply(x => x.PrincipalId),
            PrincipalType = AzureNative.Authorization.PrincipalType.ServicePrincipal,
        });
Note: I change
public List<string> RoleDefinitionId
to a regular list to troubleshoot.
@modern-boots-64313 just confirmed using Pulumi cloud the names in the
pulumi up
output given match.
Think I found something. When the name is changed, it doesn't appear it's deleting the access control assignments for the resource before it re-creates the role assignments. The error message produced.
Copy code
error: autorest/azure: Service returned an error. Status=<nil> Code="RoleAssignmentExists" Message="The role assignment already exists."
I've run
pulumi up
multiple times and it fails to remove the previous role assignment. Here's what the preview output looks like:
Copy code
Type                                             Name                                                               Plan       
     pulumi:pulumi:Stack                              api-infra-data-dev                                             
     ├─ custom-component:authorization:RoleAssignment     testing-name-collision                                                        
 +   │  ├─ azure-native:authorization:RoleAssignment  dev-testing-name-collision-role-assignment-eus2-0001           create     
 +   │  ├─ azure-native:authorization:RoleAssignment  dev-testing-name-collision-role-assignment-eus2-0003           create     
 +   │  ├─ azure-native:authorization:RoleAssignment  dev-testing-name-collision-role-assignment-eus2-0002           create     
 +   │  └─ azure-native:authorization:RoleAssignment  dev-testing-name-collision-role-assignment-eus2-0004           create     
 -   └─ custom-component:authorization:RoleAssignment     mi-storage-account-api-roles                                       delete     
 -      ├─ azure-native:authorization:RoleAssignment  dev-mi-storage-account-api-roles-role-assignment-eus2-0003     delete     
 -      ├─ azure-native:authorization:RoleAssignment  dev-mi-storage-account-api-roles-role-assignment-eus2-0004     delete     
 -      ├─ azure-native:authorization:RoleAssignment  dev-mi-storage-account-api-roles-role-assignment-eus2-0002     delete     
 -      ├─ azure-native:authorization:RoleAssignment  dev-mi-storage-account-api-roles-role-assignment-eus2-0001     delete     
 -      ├─ random:index:RandomUuid                    dev-mi-storage-account-api-roles-role-assignment-id-eus2-0002  delete     
 -      ├─ random:index:RandomUuid                    dev-mi-storage-account-api-roles-role-assignment-id-eus2-0001  delete     
 -      ├─ random:index:RandomUuid                    dev-mi-storage-account-api-roles-role-assignment-id-eus2-0004  delete     
 -      └─ random:index:RandomUuid                    dev-mi-storage-account-api-roles-role-assignment-id-eus2-0003  delete     

Resources:
    + 4 to create
    - 9 to delete
Shouldn't
CustomResourceOptions.DeleteBeforeReplace
handle removing the resource before attempting to re-create?
m
changing the logical name means Pulumi will treat it as a new resource, hence it didn't consider as a "Replace". more info can have a look at this issue, it looks similar to me on your scenario: https://github.com/pulumi/pulumi/issues/11259
n
Yes, I believe that does explain it. It's interesting... Commenting out the code removes the resource, but changing the name doesn't. One would think changing the name is the same as removing the code? It's interesting how that works. So it appears it wasn't a
InputList<string>
issue after all. Based on this code used for iterating over an input list, does this seem correct to you?
Copy code
args.RoleDefinitionId.Apply(roles =>
        {
            var instance = args.Instance;
            foreach (var roleId in roles)
            {
                AssignSingleRole(name, roleId, args, instance);
                instance++;
            }
        
            return "";
        });
I've read it's not the best way... But I cannot find an alternative. So just want to make sure. Also, thanks for the help @modern-boots-64313
m
no prob. the code looks good to me
n
Thank you again @modern-boots-64313