skip to Main Content

I have a function that takes an input of an AWS OpenIdConnectProvider Pulumi Resource and creates a IAM Role with an AssumeRolePolicy attached that contains info from that OIDC provider.

The Problem: I am trying to write a test for this function and mock out a OIDC provider to feed in as input for the function call. I’m having trouble understanding how to properly mock this so that the test output shows what I expect, currently it appears that the mocked data isn’t coming up like I’d expect.

It seems like I’m not using the mock correctly, but I went off the example here

Further Docs here

package myPkg

import (
   "github.com/pulumi/pulumi-aws/sdk/v5/go/aws/iam"
   "github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func CreateMyCustomRole(ctx *pulumi.Context, name string, oidcProvider *iam.OpenIdConnectProvider, opts ...pulumi.ResourceOption) (*iam.Role, error) {
    role := &iam.Role{}

    componentURN := fmt.Sprintf("%s-custom-role", name)

    err := ctx.RegisterComponentResource("pkg:aws:MyCustomRole", componentURN, role, opts...)
    if err != nil {
        return nil, err
    }

    url := oidc.Url.ApplyT(func(s string) string {
        return fmt.Sprint(strings.ReplaceAll(s, "https://", ""), ":sub")
    }).(pulumi.StringOutput)

    assumeRolePolicy := pulumi.Sprintf(`{
            "Version": "2012-10-17",
            "Statement": [{
                "Effect": "Allow",
                "Principal": { "Federated": "%s" },
                "Action": "sts:AssumeRoleWithWebIdentity",
                "Condition": {
                    "StringEquals": {
                        "%s": [
                            "system:serviceaccount:kube-system:*",
                            "system:serviceaccount:kube-system:cluster-autoscaler"
                        ]
                    }
                }
            }]
        }`, oidcProvider.Arn, url)

    roleURN := fmt.Sprintf("%s-custom-role", name)

    role, err = iam.NewRole(ctx, roleURN, &iam.RoleArgs{
            Name:             pulumi.String(roleURN),
            Description:      pulumi.String("Create Custom Role"),
            AssumeRolePolicy: assumeRolePolicy,
            Tags:             pulumi.ToStringMap(map[string]string{"project": "test"}),
        })
    if err != nil {
        return nil, err
    }

    return role, nil
}

package myPkg

import (
    "sync"
    "testing"

    "github.com/pulumi/pulumi-aws/sdk/v5/go/aws/iam"
    "github.com/pulumi/pulumi/sdk/v3/go/common/resource"
    "github.com/pulumi/pulumi/sdk/v3/go/pulumi"
    "github.com/stretchr/testify/assert"
)

type mocks int

func (mocks) NewResource(args pulumi.MockResourceArgs) (string, resource.PropertyMap, error) {
    return args.Name + "_id", args.Inputs, nil
}

func (mocks) Call(args pulumi.MockCallArgs) (resource.PropertyMap, error) {
    outputs := map[string]interface{}{}
    if args.Token == "aws:iam/getOpenidConnectProvider:getOpenidConnectProvider" {
        outputs["arn"] = "arn:aws:iam::123:oidc-provider/oidc.eks.us-west-2.amazonaws.com/id/abc"
        outputs["id"] = "abc"
        outputs["url"] = "https://someurl"
    }
    return resource.NewPropertyMapFromMap(outputs), nil
}

func TestCreateMyCustomRole(t *testing.T) {
    err := pulumi.RunErr(func(ctx *pulumi.Context) error {

        // Gets the mocked OIDC provider to use as input for the CreateDefaultAutoscalerRole
        oidc, err := iam.GetOpenIdConnectProvider(ctx, "get-test-oidc-provider", pulumi.ID("abc"), &iam.OpenIdConnectProviderState{})
        assert.NoError(t, err)

        infra, err := CreateMyCustomRole(ctx, "role1", oidc})
        assert.NoError(t, err)

        var wg sync.WaitGroup
        wg.Add(1)

        // check 1: Assume Role Policy is formatted correctly
        pulumi.All(infra.URN(), infra.AssumeRolePolicy).ApplyT(func(all []interface{}) error {
            urn := all[0].(pulumi.URN)
            assumeRolePolicy := all[1].(string)

            assert.Equal(t, `{
            "Version": "2012-10-17",
            "Statement": [{
                "Effect": "Allow",
                "Principal": { "Federated": "arn:aws:iam::123:oidc-provider/oidc.eks.us-west-2.amazonaws.com/id/abc" },
                "Action": "sts:AssumeRoleWithWebIdentity",
                "Condition": {
                    "StringEquals": {
                        "someurl:sub": [
                            "system:serviceaccount:kube-system:*",
                            "system:serviceaccount:kube-system:cluster-autoscaler"
                        ]
                    }
                }
            }]
        }`, assumeRolePolicy)

            wg.Done()
            return nil
        })

        wg.Wait()
        return nil
    }, pulumi.WithMocks("project", "stack", mocks(0)))
    assert.NoError(t, err)
}

Output
                        Diff:
                        --- Expected
                        +++ Actual
                        @@ -4,3 +4,3 @@
                                        "Effect": "Allow",
                        -               "Principal": { "Federated": "arn:aws:iam::123:oidc-provider/oidc.eks.us-west-2.amazonaws.com/id/abc" },
                        +               "Principal": { "Federated": "" },
                                        "Action": "sts:AssumeRoleWithWebIdentity",
                        @@ -8,3 +8,3 @@
                                            "StringEquals": {
                        -                       "someurl:sub": [
                        +                       ":sub": [
                                                    "system:serviceaccount:kube-system:*",
        Test:           TestCreateMyCustomRole

2

Answers


  1. Chosen as BEST ANSWER

    Turns out I was using the NewResource wrong.

    When GetOpenIdConnectProvider is called in the test function it goes to read a resource and create a new resource output which triggers the call to mocks.NewResource

    The fix was to instead define an if statement for the resource type returned by the GetOpenIdConnectProvider openIdConnectProviderin the NewResource function call with the mocked outputs.

    func (mocks) NewResource(args pulumi.MockResourceArgs) (string, resource.PropertyMap, error) {
        pulumi.Printf(args.TypeToken)
        outputs := args.Inputs.Mappable()
        if args.TypeToken == "aws:iam/openIdConnectProvider:OpenIdConnectProvider" {
            outputs["arn"] = "arn:aws:iam::123:oidc-provider/oidc.eks.us-west-2.amazonaws.com/id/abc"
            outputs["id"] = "abc"
            outputs["url"] = "https://someurl"
        }
        return args.Name + "_id", resource.NewPropertyMapFromMap(outputs), nil
    }
    
    func (mocks) Call(args pulumi.MockCallArgs) (resource.PropertyMap, error) {
        outputs := map[string]interface{}{}
        return resource.NewPropertyMapFromMap(outputs), nil
    }
    

    Is this below code snippet I changed the assert so that it fails to show what the diff now looks like with the changes made to NewResource above

    Diff:
                                --- Expected
                                +++ Actual
                                @@ -8,3 +8,3 @@
                                                    "StringEquals": {
                                -                       "b:sub": [
                                +                       "someurl:sub": [
                                                            "system:serviceaccount:kube-system:*",
    

  2. If you want to mock Pulumi resources in Go unit tests, you can use the "mock" package provided by the Pulumi SDK. Here are the general steps you can follow:

    Create a new Go file for your test and import the necessary packages, including the "testing" and "github.com/pulumi/pulumi/sdk/v3/go/pulumi/mock" packages.

    Define a struct that implements the Pulumi Resource interface. This struct will act as a mock for the actual resource. You can set the fields of the struct to the values you want to use in your test.

    type mockResource struct {
        pulumi.ResourceState
        Field1 string
        Field2 int
    }
    

    Create a new instance of the mockResource struct and set its fields to the values you want to use in your test.

    mock := &mockResource{
        Field1: "test",
        Field2: 123,
    }
    

    Use the mock.NewResourceMock function to create a new mock resource provider. This provider will return the mockResource instance when called.

    provider := mock.NewResourceMock(t, "test", "aws:ec2/instance:Instance", mock)
    

    Create a new instance of the resource you want to test and pass in the mock provider.

    instance := &ec2.Instance{}
    err := instance.Create(ctx, "test", ec2.InstanceArgs{
        // set args here
    }, pulumi.Provider(provider))
    

    Write your test code to validate that the resource behaves as expected.

    
    // assert that the resource was created successfully
    assert.Nil(t, err)
    
    // assert that the resource has the expected values
    assert.Equal(t, mock.Field1, instance.Field1)
    assert.Equal(t, mock.Field2, instance.Field2)
    

    That’s it! By following these steps, you can easily mock Pulumi resources in Go unit tests using the Pulumi SDK.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search