diff --git a/pkg/testutils/BUILD.bazel b/pkg/testutils/BUILD.bazel index c639e91e1c70b..71ae0307ca37c 100644 --- a/pkg/testutils/BUILD.bazel +++ b/pkg/testutils/BUILD.bazel @@ -37,6 +37,7 @@ go_library( "//vendor/github.com/aws/aws-sdk-go/aws:go_default_library", "//vendor/github.com/aws/aws-sdk-go/service/ec2:go_default_library", "//vendor/github.com/aws/aws-sdk-go/service/elbv2:go_default_library", + "//vendor/github.com/aws/aws-sdk-go/service/iam:go_default_library", "//vendor/github.com/aws/aws-sdk-go/service/route53:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/zones:go_default_library", diff --git a/pkg/testutils/integrationtestharness.go b/pkg/testutils/integrationtestharness.go index 07825d67b99a4..9362bb260a941 100644 --- a/pkg/testutils/integrationtestharness.go +++ b/pkg/testutils/integrationtestharness.go @@ -26,6 +26,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/elbv2" + "github.com/aws/aws-sdk-go/service/iam" "github.com/aws/aws-sdk-go/service/route53" "github.com/gophercloud/gophercloud/openstack/compute/v2/flavors" "github.com/gophercloud/gophercloud/openstack/dns/v2/zones" @@ -256,6 +257,17 @@ func (h *IntegrationTestHarness) SetupMockAWS() *awsup.MockAWSCloud { Name: aws.String("my-external-tg-3"), }) + mockIAM.CreateRole(&iam.CreateRoleInput{ + RoleName: aws.String("kops-custom-node-role"), + }) + mockIAM.CreateInstanceProfile(&iam.CreateInstanceProfileInput{ + InstanceProfileName: aws.String("kops-custom-node-role"), + }) + mockIAM.AddRoleToInstanceProfile(&iam.AddRoleToInstanceProfileInput{ + InstanceProfileName: aws.String("kops-custom-node-role"), + RoleName: aws.String("kops-custom-node-role"), + }) + return cloud } diff --git a/upup/pkg/fi/cloudup/apply_cluster.go b/upup/pkg/fi/cloudup/apply_cluster.go index b86a29b402517..0a71a5f95f5db 100644 --- a/upup/pkg/fi/cloudup/apply_cluster.go +++ b/upup/pkg/fi/cloudup/apply_cluster.go @@ -472,6 +472,7 @@ func (c *ApplyClusterCmd) Run(ctx context.Context) error { tf := &TemplateFunctions{ KopsModelContext: *modelContext, + cloud: cloud, } { diff --git a/upup/pkg/fi/cloudup/awsup/aws_cloud.go b/upup/pkg/fi/cloudup/awsup/aws_cloud.go index cd9821a030de3..28b6c7207f7fa 100644 --- a/upup/pkg/fi/cloudup/awsup/aws_cloud.go +++ b/upup/pkg/fi/cloudup/awsup/aws_cloud.go @@ -1751,3 +1751,18 @@ func (c *awsCloudImplementation) AccountInfo() (string, string, error) { } return arn.AccountID, arn.Partition, nil } + +// GetRolesInInstanceProfile return role names which are associated with the instance profile specified by profileName. +func GetRolesInInstanceProfile(c AWSCloud, profileName string) ([]string, error) { + output, err := c.IAM().GetInstanceProfile(&iam.GetInstanceProfileInput{ + InstanceProfileName: aws.String(profileName), + }) + if err != nil { + return nil, err + } + var roleNames []string + for _, role := range output.InstanceProfile.Roles { + roleNames = append(roleNames, *role.RoleName) + } + return roleNames, nil +} diff --git a/upup/pkg/fi/cloudup/bootstrapchannelbuilder_test.go b/upup/pkg/fi/cloudup/bootstrapchannelbuilder_test.go index 8ddb93b08e428..455d83db83092 100644 --- a/upup/pkg/fi/cloudup/bootstrapchannelbuilder_test.go +++ b/upup/pkg/fi/cloudup/bootstrapchannelbuilder_test.go @@ -139,6 +139,7 @@ func runChannelBuilderTest(t *testing.T, key string, addonManifests []string) { tf := &TemplateFunctions{ KopsModelContext: kopsModel, + cloud: cloud, } tf.AddTo(templates.TemplateFunctions, secretStore) diff --git a/upup/pkg/fi/cloudup/template_functions.go b/upup/pkg/fi/cloudup/template_functions.go index 978f93aeaa280..cd2e4b47c2679 100644 --- a/upup/pkg/fi/cloudup/template_functions.go +++ b/upup/pkg/fi/cloudup/template_functions.go @@ -59,6 +59,8 @@ import ( // TemplateFunctions provides a collection of methods used throughout the templates type TemplateFunctions struct { model.KopsModelContext + + cloud fi.Cloud } // AddTo defines the available functions we can use in our YAML models. @@ -444,9 +446,24 @@ func (tf *TemplateFunctions) KopsControllerConfig() (string, error) { if ig.Spec.Role == kops.InstanceGroupRoleNode { profile, err := tf.LinkToIAMInstanceProfile(ig) if err != nil { - return "", fmt.Errorf("getting role for ig %s: %v", ig.Name, err) + return "", fmt.Errorf("getting profile for ig %s: %v", ig.Name, err) + } + // The IAM Instance Profile has not been created at this point if it is not specified. + // Because the IAM Instance Profile and the IAM Role are created in IAMModelBuilder tasks. + // Therefore, the IAM Role associated with IAM Instance Profile is acquired only when it is not specified. + if ig.Spec.IAM != nil && ig.Spec.IAM.Profile != nil { + c := tf.cloud.(awsup.AWSCloud) + roles, err := awsup.GetRolesInInstanceProfile(c, *profile.Name) + if err != nil { + return "", fmt.Errorf("getting role from profile %s: %v", *profile.Name, err) + } + nodesRoles.Insert(roles...) + } else { + // When the IAM Instance Profile is not specified, IAM Instance Profile is created by kOps. + // In this case, the IAM Instance Profile name and IAM Role name are same. + // So there is no problem even if IAM Instance Profile name is inserted as role name in nodesRoles. + nodesRoles.Insert(*profile.Name) } - nodesRoles.Insert(*profile.Name) } } config.Server.Provider.AWS = &awsup.AWSVerifierOptions{