diff --git a/PROJECT b/PROJECT index 44c9df3c2c..263d5467a3 100644 --- a/PROJECT +++ b/PROJECT @@ -2,59 +2,60 @@ version: "2" domain: cluster.x-k8s.io repo: sigs.k8s.io/cluster-api-provider-aws resources: -# v1beta1 types - group: infrastructure - version: v1beta1 kind: AWSMachine -- group: infrastructure version: v1beta1 - kind: AWSCluster - group: infrastructure + kind: AWSCluster version: v1beta1 - kind: AWSMachineTemplate - group: infrastructure + kind: AWSMachineTemplate version: v1beta1 - kind: AWSClusterStaticIdentity - group: infrastructure + kind: AWSClusterStaticIdentity version: v1beta1 - kind: AWSClusterRoleIdentity - group: infrastructure + kind: AWSClusterRoleIdentity version: v1beta1 - kind: AWSClusterControllerIdentity - group: infrastructure + kind: AWSClusterControllerIdentity version: v1beta1 - kind: AWSClusterTemplate - group: infrastructure + kind: AWSClusterTemplate version: v1beta1 - kind: AWSManagedControlPlanes - group: infrastructure + kind: AWSManagedControlPlanes version: v1beta1 +- group: infrastructure kind: AWSManagedCluster -# v1beta2 types + version: v1beta1 - group: infrastructure - version: v1beta2 kind: AWSMachine -- group: infrastructure version: v1beta2 - kind: AWSCluster - group: infrastructure + kind: AWSCluster version: v1beta2 - kind: AWSMachineTemplate - group: infrastructure + kind: AWSMachineTemplate version: v1beta2 - kind: AWSClusterStaticIdentity - group: infrastructure + kind: AWSClusterStaticIdentity version: v1beta2 - kind: AWSClusterRoleIdentity - group: infrastructure + kind: AWSClusterRoleIdentity version: v1beta2 - kind: AWSClusterControllerIdentity - group: infrastructure + kind: AWSClusterControllerIdentity version: v1beta2 - kind: AWSClusterTemplate - group: infrastructure + kind: AWSClusterTemplate version: v1beta2 - kind: AWSManagedControlPlanes - group: infrastructure + kind: AWSManagedControlPlanes version: v1beta2 +- group: infrastructure kind: AWSManagedCluster + version: v1beta2 +- group: infrastructure + kind: ROSARoleConfig + version: v1beta2 diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_rosaroleconfigs.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_rosaroleconfigs.yaml new file mode 100644 index 0000000000..c823eec45c --- /dev/null +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_rosaroleconfigs.yaml @@ -0,0 +1,683 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.3 + name: rosaroleconfigs.infrastructure.cluster.x-k8s.io +spec: + group: infrastructure.cluster.x-k8s.io + names: + categories: + - cluster-api + kind: ROSARoleConfig + listKind: ROSARoleConfigList + plural: rosaroleconfigs + shortNames: + - rosarole + singular: rosaroleconfig + scope: Namespaced + versions: + - name: v1beta2 + schema: + openAPIV3Schema: + description: ROSARoleConfig is the Schema for the rosaroleconfigs API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: ROSARoleConfigSpec defines the desired state of ROSARoleConfig + properties: + accountRoleConfig: + description: AccountRoleConfig defines account-wide IAM roles before + creating your ROSA cluster. + properties: + path: + description: The arn path for the account/operator roles as well + as their policies. + type: string + permissionsBoundaryARN: + description: The ARN of the policy that is used to set the permissions + boundary for the account roles. + type: string + prefix: + description: User-defined prefix for all generated AWS resources + maxLength: 4 + type: string + sharedVPCConfig: + description: SharedVPCConfig is used to set up shared VPC. + properties: + routeRoleARN: + description: ' Role ARN associated with the private hosted + zone used for Hosted Control Plane cluster shared VPC, this + role contains policies to be used with Route 53' + type: string + vpcEndpointRoleArn: + description: ' Role ARN associated with the shared VPC used + for Hosted Control Plane clusters, this role contains policies + to be used with the VPC endpoint' + type: string + type: object + version: + description: ' Version of OpenShift that will be used to setup + policy tag, for example "4.11"' + type: string + required: + - prefix + - version + type: object + identityRef: + description: AWSIdentityReference specifies a identity. + properties: + kind: + description: Kind of the identity. + enum: + - AWSClusterControllerIdentity + - AWSClusterRoleIdentity + - AWSClusterStaticIdentity + type: string + name: + description: Name of the identity. + minLength: 1 + type: string + required: + - kind + - name + type: object + oidcConfig: + description: |- + OIDCConfig creates OIDC config in a S3 bucket for the client AWS account and populates it to be compliant with OIDC protocol. + It also creates a Secret in Secrets Manager containing the private key. + properties: + externalAuthProviders: + description: |- + ExternalAuthProviders are external OIDC identity providers that can issue tokens for this cluster. + Can only be set if "enableExternalAuthProviders" is set to "True". + + At most one provider can be configured. + items: + description: ExternalAuthProvider is an external OIDC identity + provider that can issue tokens for this cluster + properties: + claimMappings: + description: |- + ClaimMappings describes rules on how to transform information from an + ID token into a cluster identity + properties: + groups: + description: |- + Groups is a name of the claim that should be used to construct + groups for the cluster identity. + The referenced claim must use array of strings values. + properties: + claim: + description: Claim is a JWT token claim to be used + in the mapping + type: string + prefix: + description: |- + Prefix is a string to prefix the value from the token in the result of the + claim mapping. + + By default, no prefixing occurs. + + Example: if `prefix` is set to "myoidc:"" and the `claim` in JWT contains + an array of strings "a", "b" and "c", the mapping will result in an + array of string "myoidc:a", "myoidc:b" and "myoidc:c". + type: string + required: + - claim + type: object + username: + description: |- + Username is a name of the claim that should be used to construct + usernames for the cluster identity. + + Default value: "sub" + properties: + claim: + description: Claim is a JWT token claim to be used + in the mapping + type: string + prefix: + description: Prefix is prepended to claim to prevent + clashes with existing names. + minLength: 1 + type: string + prefixPolicy: + description: |- + PrefixPolicy specifies how a prefix should apply. + + By default, claims other than `email` will be prefixed with the issuer URL to + prevent naming clashes with other plugins. + + Set to "NoPrefix" to disable prefixing. + + Example: + (1) `prefix` is set to "myoidc:" and `claim` is set to "username". + If the JWT claim `username` contains value `userA`, the resulting + mapped value will be "myoidc:userA". + (2) `prefix` is set to "myoidc:" and `claim` is set to "email". If the + JWT `email` claim contains value "userA@myoidc.tld", the resulting + mapped value will be "myoidc:userA@myoidc.tld". + (3) `prefix` is unset, `issuerURL` is set to `https://myoidc.tld`, + the JWT claims include "username":"userA" and "email":"userA@myoidc.tld", + and `claim` is set to: + (a) "username": the mapped value will be "https://myoidc.tld#userA" + (b) "email": the mapped value will be "userA@myoidc.tld" + enum: + - "" + - NoPrefix + - Prefix + type: string + required: + - claim + type: object + x-kubernetes-validations: + - message: prefix must be set if prefixPolicy is 'Prefix', + but must remain unset otherwise + rule: 'self.prefixPolicy == ''Prefix'' ? has(self.prefix) + : !has(self.prefix)' + type: object + claimValidationRules: + description: ClaimValidationRules are rules that are applied + to validate token claims to authenticate users. + items: + description: TokenClaimValidationRule validates token + claims to authenticate users. + properties: + requiredClaim: + description: RequiredClaim allows configuring a required + claim name and its expected value + properties: + claim: + description: |- + Claim is a name of a required claim. Only claims with string values are + supported. + minLength: 1 + type: string + requiredValue: + description: RequiredValue is the required value + for the claim. + minLength: 1 + type: string + required: + - claim + - requiredValue + type: object + type: + default: RequiredClaim + description: Type sets the type of the validation + rule + enum: + - RequiredClaim + type: string + required: + - requiredClaim + - type + type: object + type: array + x-kubernetes-list-type: atomic + issuer: + description: Issuer describes attributes of the OIDC token + issuer + properties: + audiences: + description: |- + Audiences is an array of audiences that the token was issued for. + Valid tokens must include at least one of these values in their + "aud" claim. + Must be set to exactly one value. + items: + description: TokenAudience is the audience that the + token was issued for. + minLength: 1 + type: string + maxItems: 10 + minItems: 1 + type: array + x-kubernetes-list-type: set + issuerCertificateAuthority: + description: |- + CertificateAuthority is a reference to a config map in the + configuration namespace. The .data of the configMap must contain + the "ca-bundle.crt" key. + If unset, system trust is used instead. + properties: + name: + description: Name is the metadata.name of the referenced + object. + type: string + required: + - name + type: object + issuerURL: + description: |- + URL is the serving URL of the token issuer. + Must use the https:// scheme. + pattern: ^https:\/\/[^\s] + type: string + required: + - audiences + - issuerURL + type: object + name: + description: Name of the OIDC provider + minLength: 1 + type: string + oidcClients: + description: |- + OIDCClients contains configuration for the platform's clients that + need to request tokens from the issuer + items: + description: |- + OIDCClientConfig contains configuration for the platform's client that + need to request tokens from the issuer. + properties: + clientID: + description: ClientID is the identifier of the OIDC + client from the OIDC provider + minLength: 1 + type: string + clientSecret: + description: |- + ClientSecret refers to a secret that + contains the client secret in the `clientSecret` key of the `.data` field + properties: + name: + description: Name is the metadata.name of the + referenced object. + type: string + required: + - name + type: object + componentName: + description: |- + ComponentName is the name of the component that is supposed to consume this + client configuration + maxLength: 256 + minLength: 1 + type: string + componentNamespace: + description: |- + ComponentNamespace is the namespace of the component that is supposed to consume this + client configuration + maxLength: 63 + minLength: 1 + type: string + extraScopes: + description: ExtraScopes is an optional set of scopes + to request tokens with. + items: + type: string + type: array + x-kubernetes-list-type: set + required: + - clientID + - clientSecret + - componentName + - componentNamespace + type: object + maxItems: 20 + type: array + x-kubernetes-list-map-keys: + - componentNamespace + - componentName + x-kubernetes-list-type: map + required: + - issuer + - name + type: object + maxItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + managedOIDC: + default: true + description: ManagedOIDC indicates whether it is a Red Hat managed + or unmanaged (Customer hosted) OIDC Configuration. Default is + true. + type: boolean + required: + - managedOIDC + type: object + operatorRoleConfig: + description: OperatorRoleConfig defines cluster-specific operator + IAM roles based on your cluster configuration. + properties: + oidcConfigId: + description: Registered OIDC configuration ID to add its issuer + URL as the trusted relationship to the operator roles.'' + type: string + permissionsBoundaryARN: + description: The ARN of the policy that is used to set the permissions + boundary for the operator roles. + type: string + prefix: + description: ' User-defined prefix for generated AWS operator + policies.' + maxLength: 4 + type: string + sharedVPCConfig: + description: SharedVPCConfig is used to set up shared VPC. + properties: + routeRoleARN: + description: ' Role ARN associated with the private hosted + zone used for Hosted Control Plane cluster shared VPC, this + role contains policies to be used with Route 53' + type: string + vpcEndpointRoleArn: + description: ' Role ARN associated with the shared VPC used + for Hosted Control Plane clusters, this role contains policies + to be used with the VPC endpoint' + type: string + type: object + required: + - oidcConfigId + - prefix + type: object + region: + type: string + required: + - accountRoleConfig + - oidcConfig + - operatorRoleConfig + type: object + status: + description: ROSARoleConfigStatus defines the observed state of ROSARoleConfig + properties: + accountRolesRef: + description: Created Account roles that can be used to + properties: + installerRoleARN: + description: InstallerRoleARN is an AWS IAM role that OpenShift + Cluster Manager will assume to create the cluster.. + type: string + supportRoleARN: + description: |- + SupportRoleARN is an AWS IAM role used by Red Hat SREs to enable + access to the cluster account in order to provide support. + type: string + workerRoleARN: + description: WorkerRoleARN is an AWS IAM role that will be attached + to worker instances. + type: string + required: + - installerRoleARN + - supportRoleARN + type: object + conditions: + description: Conditions provide observations of the operational state + of a Cluster API resource. + items: + description: Condition defines an observation of a Cluster API resource + operational state. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when + the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This field may be empty. + maxLength: 10240 + minLength: 1 + type: string + reason: + description: |- + reason is the reason for the condition's last transition in CamelCase. + The specific API may choose whether or not this field is considered a guaranteed API. + This field may be empty. + maxLength: 256 + minLength: 1 + type: string + severity: + description: |- + severity provides an explicit classification of Reason code, so the users or machines can immediately + understand the current situation and act accordingly. + The Severity field MUST be set only when Status=False. + maxLength: 32 + type: string + status: + description: status of the condition, one of True, False, Unknown. + type: string + type: + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions + can be useful (see .node.status.conditions), the ability to deconflict is important. + maxLength: 256 + minLength: 1 + type: string + required: + - lastTransitionTime + - status + - type + type: object + type: array + oidcID: + description: ID of created OIDC config + type: string + oidcProviderARN: + description: Create OIDC provider for operators to authenticate against + in an STS cluster. + type: string + operatorRolesRef: + description: AWS IAM roles used to perform credential requests by + the openshift operators. + properties: + controlPlaneOperatorARN: + description: "ControlPlaneOperatorARN is an ARN value referencing + a role appropriate for the Control Plane Operator.\n\nThe following + is an example of a valid policy document:\n\n{\n\t\"Version\": + \"2012-10-17\",\n\t\"Statement\": [\n\t\t{\n\t\t\t\"Effect\": + \"Allow\",\n\t\t\t\"Action\": [\n\t\t\t\t\"ec2:CreateVpcEndpoint\",\n\t\t\t\t\"ec2:DescribeVpcEndpoints\",\n\t\t\t\t\"ec2:ModifyVpcEndpoint\",\n\t\t\t\t\"ec2:DeleteVpcEndpoints\",\n\t\t\t\t\"ec2:CreateTags\",\n\t\t\t\t\"route53:ListHostedZones\",\n\t\t\t\t\"ec2:CreateSecurityGroup\",\n\t\t\t\t\"ec2:AuthorizeSecurityGroupIngress\",\n\t\t\t\t\"ec2:AuthorizeSecurityGroupEgress\",\n\t\t\t\t\"ec2:DeleteSecurityGroup\",\n\t\t\t\t\"ec2:RevokeSecurityGroupIngress\",\n\t\t\t\t\"ec2:RevokeSecurityGroupEgress\",\n\t\t\t\t\"ec2:DescribeSecurityGroups\",\n\t\t\t\t\"ec2:DescribeVpcs\",\n\t\t\t],\n\t\t\t\"Resource\": + \"*\"\n\t\t},\n\t\t{\n\t\t\t\"Effect\": \"Allow\",\n\t\t\t\"Action\": + [\n\t\t\t\t\"route53:ChangeResourceRecordSets\",\n\t\t\t\t\"route53:ListResourceRecordSets\"\n\t\t\t],\n\t\t\t\"Resource\": + \"arn:aws:route53:::%s\"\n\t\t}\n\t]\n}" + type: string + imageRegistryARN: + description: "ImageRegistryARN is an ARN value referencing a role + appropriate for the Image Registry Operator.\n\nThe following + is an example of a valid policy document:\n\n{\n\t\"Version\": + \"2012-10-17\",\n\t\"Statement\": [\n\t\t{\n\t\t\t\"Effect\": + \"Allow\",\n\t\t\t\"Action\": [\n\t\t\t\t\"s3:CreateBucket\",\n\t\t\t\t\"s3:DeleteBucket\",\n\t\t\t\t\"s3:PutBucketTagging\",\n\t\t\t\t\"s3:GetBucketTagging\",\n\t\t\t\t\"s3:PutBucketPublicAccessBlock\",\n\t\t\t\t\"s3:GetBucketPublicAccessBlock\",\n\t\t\t\t\"s3:PutEncryptionConfiguration\",\n\t\t\t\t\"s3:GetEncryptionConfiguration\",\n\t\t\t\t\"s3:PutLifecycleConfiguration\",\n\t\t\t\t\"s3:GetLifecycleConfiguration\",\n\t\t\t\t\"s3:GetBucketLocation\",\n\t\t\t\t\"s3:ListBucket\",\n\t\t\t\t\"s3:GetObject\",\n\t\t\t\t\"s3:PutObject\",\n\t\t\t\t\"s3:DeleteObject\",\n\t\t\t\t\"s3:ListBucketMultipartUploads\",\n\t\t\t\t\"s3:AbortMultipartUpload\",\n\t\t\t\t\"s3:ListMultipartUploadParts\"\n\t\t\t],\n\t\t\t\"Resource\": + \"*\"\n\t\t}\n\t]\n}" + type: string + ingressARN: + description: "The referenced role must have a trust relationship + that allows it to be assumed via web identity.\nhttps://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_oidc.html.\nExample:\n{\n\t\t\"Version\": + \"2012-10-17\",\n\t\t\"Statement\": [\n\t\t\t{\n\t\t\t\t\"Effect\": + \"Allow\",\n\t\t\t\t\"Principal\": {\n\t\t\t\t\t\"Federated\": + \"{{ .ProviderARN }}\"\n\t\t\t\t},\n\t\t\t\t\t\"Action\": \"sts:AssumeRoleWithWebIdentity\",\n\t\t\t\t\"Condition\": + {\n\t\t\t\t\t\"StringEquals\": {\n\t\t\t\t\t\t\"{{ .ProviderName + }}:sub\": {{ .ServiceAccounts }}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t]\n\t}\n\nIngressARN + is an ARN value referencing a role appropriate for the Ingress + Operator.\n\nThe following is an example of a valid policy document:\n\n{\n\t\"Version\": + \"2012-10-17\",\n\t\"Statement\": [\n\t\t{\n\t\t\t\"Effect\": + \"Allow\",\n\t\t\t\"Action\": [\n\t\t\t\t\"elasticloadbalancing:DescribeLoadBalancers\",\n\t\t\t\t\"tag:GetResources\",\n\t\t\t\t\"route53:ListHostedZones\"\n\t\t\t],\n\t\t\t\"Resource\": + \"*\"\n\t\t},\n\t\t{\n\t\t\t\"Effect\": \"Allow\",\n\t\t\t\"Action\": + [\n\t\t\t\t\"route53:ChangeResourceRecordSets\"\n\t\t\t],\n\t\t\t\"Resource\": + [\n\t\t\t\t\"arn:aws:route53:::PUBLIC_ZONE_ID\",\n\t\t\t\t\"arn:aws:route53:::PRIVATE_ZONE_ID\"\n\t\t\t]\n\t\t}\n\t]\n}" + type: string + kmsProviderARN: + type: string + kubeCloudControllerARN: + description: |- + KubeCloudControllerARN is an ARN value referencing a role appropriate for the KCM/KCC. + Source: https://cloud-provider-aws.sigs.k8s.io/prerequisites/#iam-policies + + The following is an example of a valid policy document: + + { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "autoscaling:DescribeAutoScalingGroups", + "autoscaling:DescribeLaunchConfigurations", + "autoscaling:DescribeTags", + "ec2:DescribeAvailabilityZones", + "ec2:DescribeInstances", + "ec2:DescribeImages", + "ec2:DescribeRegions", + "ec2:DescribeRouteTables", + "ec2:DescribeSecurityGroups", + "ec2:DescribeSubnets", + "ec2:DescribeVolumes", + "ec2:CreateSecurityGroup", + "ec2:CreateTags", + "ec2:CreateVolume", + "ec2:ModifyInstanceAttribute", + "ec2:ModifyVolume", + "ec2:AttachVolume", + "ec2:AuthorizeSecurityGroupIngress", + "ec2:CreateRoute", + "ec2:DeleteRoute", + "ec2:DeleteSecurityGroup", + "ec2:DeleteVolume", + "ec2:DetachVolume", + "ec2:RevokeSecurityGroupIngress", + "ec2:DescribeVpcs", + "elasticloadbalancing:AddTags", + "elasticloadbalancing:AttachLoadBalancerToSubnets", + "elasticloadbalancing:ApplySecurityGroupsToLoadBalancer", + "elasticloadbalancing:CreateLoadBalancer", + "elasticloadbalancing:CreateLoadBalancerPolicy", + "elasticloadbalancing:CreateLoadBalancerListeners", + "elasticloadbalancing:ConfigureHealthCheck", + "elasticloadbalancing:DeleteLoadBalancer", + "elasticloadbalancing:DeleteLoadBalancerListeners", + "elasticloadbalancing:DescribeLoadBalancers", + "elasticloadbalancing:DescribeLoadBalancerAttributes", + "elasticloadbalancing:DetachLoadBalancerFromSubnets", + "elasticloadbalancing:DeregisterInstancesFromLoadBalancer", + "elasticloadbalancing:ModifyLoadBalancerAttributes", + "elasticloadbalancing:RegisterInstancesWithLoadBalancer", + "elasticloadbalancing:SetLoadBalancerPoliciesForBackendServer", + "elasticloadbalancing:AddTags", + "elasticloadbalancing:CreateListener", + "elasticloadbalancing:CreateTargetGroup", + "elasticloadbalancing:DeleteListener", + "elasticloadbalancing:DeleteTargetGroup", + "elasticloadbalancing:DeregisterTargets", + "elasticloadbalancing:DescribeListeners", + "elasticloadbalancing:DescribeLoadBalancerPolicies", + "elasticloadbalancing:DescribeTargetGroups", + "elasticloadbalancing:DescribeTargetHealth", + "elasticloadbalancing:ModifyListener", + "elasticloadbalancing:ModifyTargetGroup", + "elasticloadbalancing:RegisterTargets", + "elasticloadbalancing:SetLoadBalancerPoliciesOfListener", + "iam:CreateServiceLinkedRole", + "kms:DescribeKey" + ], + "Resource": [ + "*" + ], + "Effect": "Allow" + } + ] + } + type: string + networkARN: + description: "NetworkARN is an ARN value referencing a role appropriate + for the Network Operator.\n\nThe following is an example of + a valid policy document:\n\n{\n\t\"Version\": \"2012-10-17\",\n\t\"Statement\": + [\n\t\t{\n\t\t\t\"Effect\": \"Allow\",\n\t\t\t\"Action\": [\n\t\t\t\t\"ec2:DescribeInstances\",\n + \ \"ec2:DescribeInstanceStatus\",\n \"ec2:DescribeInstanceTypes\",\n + \ \"ec2:UnassignPrivateIpAddresses\",\n \"ec2:AssignPrivateIpAddresses\",\n + \ \"ec2:UnassignIpv6Addresses\",\n \"ec2:AssignIpv6Addresses\",\n + \ \"ec2:DescribeSubnets\",\n \"ec2:DescribeNetworkInterfaces\"\n\t\t\t],\n\t\t\t\"Resource\": + \"*\"\n\t\t}\n\t]\n}" + type: string + nodePoolManagementARN: + description: "NodePoolManagementARN is an ARN value referencing + a role appropriate for the CAPI Controller.\n\nThe following + is an example of a valid policy document:\n\n{\n \"Version\": + \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n + \ \"ec2:AssociateRouteTable\",\n \"ec2:AttachInternetGateway\",\n + \ \"ec2:AuthorizeSecurityGroupIngress\",\n \"ec2:CreateInternetGateway\",\n + \ \"ec2:CreateNatGateway\",\n \"ec2:CreateRoute\",\n + \ \"ec2:CreateRouteTable\",\n \"ec2:CreateSecurityGroup\",\n + \ \"ec2:CreateSubnet\",\n \"ec2:CreateTags\",\n \"ec2:DeleteInternetGateway\",\n + \ \"ec2:DeleteNatGateway\",\n \"ec2:DeleteRouteTable\",\n + \ \"ec2:DeleteSecurityGroup\",\n \"ec2:DeleteSubnet\",\n + \ \"ec2:DeleteTags\",\n \"ec2:DescribeAccountAttributes\",\n + \ \"ec2:DescribeAddresses\",\n \"ec2:DescribeAvailabilityZones\",\n + \ \"ec2:DescribeImages\",\n \"ec2:DescribeInstances\",\n + \ \"ec2:DescribeInternetGateways\",\n \"ec2:DescribeNatGateways\",\n + \ \"ec2:DescribeNetworkInterfaces\",\n \"ec2:DescribeNetworkInterfaceAttribute\",\n + \ \"ec2:DescribeRouteTables\",\n \"ec2:DescribeSecurityGroups\",\n + \ \"ec2:DescribeSubnets\",\n \"ec2:DescribeVpcs\",\n + \ \"ec2:DescribeVpcAttribute\",\n \"ec2:DescribeVolumes\",\n + \ \"ec2:DetachInternetGateway\",\n \"ec2:DisassociateRouteTable\",\n + \ \"ec2:DisassociateAddress\",\n \"ec2:ModifyInstanceAttribute\",\n + \ \"ec2:ModifyNetworkInterfaceAttribute\",\n \"ec2:ModifySubnetAttribute\",\n + \ \"ec2:RevokeSecurityGroupIngress\",\n \"ec2:RunInstances\",\n + \ \"ec2:TerminateInstances\",\n \"tag:GetResources\",\n + \ \"ec2:CreateLaunchTemplate\",\n \"ec2:CreateLaunchTemplateVersion\",\n + \ \"ec2:DescribeLaunchTemplates\",\n \"ec2:DescribeLaunchTemplateVersions\",\n + \ \"ec2:DeleteLaunchTemplate\",\n \"ec2:DeleteLaunchTemplateVersions\"\n + \ ],\n \"Resource\": [\n \"*\"\n ],\n \"Effect\": + \"Allow\"\n },\n {\n \"Condition\": {\n \"StringLike\": + {\n \"iam:AWSServiceName\": \"elasticloadbalancing.amazonaws.com\"\n + \ }\n },\n \"Action\": [\n \"iam:CreateServiceLinkedRole\"\n + \ ],\n \"Resource\": [\n \"arn:*:iam::*:role/aws-service-role/elasticloadbalancing.amazonaws.com/AWSServiceRoleForElasticLoadBalancing\"\n + \ ],\n \"Effect\": \"Allow\"\n },\n {\n \"Action\": + [\n \"iam:PassRole\"\n ],\n \"Resource\": [\n + \ \"arn:*:iam::*:role/*-worker-role\"\n ],\n \"Effect\": + \"Allow\"\n },\n\t {\n\t \t\"Effect\": \"Allow\",\n\t \t\"Action\": + [\n\t \t\t\"kms:Decrypt\",\n\t \t\t\"kms:ReEncrypt\",\n\t + \ \t\t\"kms:GenerateDataKeyWithoutPlainText\",\n\t \t\t\"kms:DescribeKey\"\n\t + \ \t],\n\t \t\"Resource\": \"*\"\n\t },\n\t {\n\t \t\"Effect\": + \"Allow\",\n\t \t\"Action\": [\n\t \t\t\"kms:CreateGrant\"\n\t + \ \t],\n\t \t\"Resource\": \"*\",\n\t \t\"Condition\": {\n\t + \ \t\t\"Bool\": {\n\t \t\t\t\"kms:GrantIsForAWSResource\": + true\n\t \t\t}\n\t \t}\n\t }\n ]\n}" + type: string + storageARN: + description: "StorageARN is an ARN value referencing a role appropriate + for the Storage Operator.\n\nThe following is an example of + a valid policy document:\n\n{\n\t\"Version\": \"2012-10-17\",\n\t\"Statement\": + [\n\t\t{\n\t\t\t\"Effect\": \"Allow\",\n\t\t\t\"Action\": [\n\t\t\t\t\"ec2:AttachVolume\",\n\t\t\t\t\"ec2:CreateSnapshot\",\n\t\t\t\t\"ec2:CreateTags\",\n\t\t\t\t\"ec2:CreateVolume\",\n\t\t\t\t\"ec2:DeleteSnapshot\",\n\t\t\t\t\"ec2:DeleteTags\",\n\t\t\t\t\"ec2:DeleteVolume\",\n\t\t\t\t\"ec2:DescribeInstances\",\n\t\t\t\t\"ec2:DescribeSnapshots\",\n\t\t\t\t\"ec2:DescribeTags\",\n\t\t\t\t\"ec2:DescribeVolumes\",\n\t\t\t\t\"ec2:DescribeVolumesModifications\",\n\t\t\t\t\"ec2:DetachVolume\",\n\t\t\t\t\"ec2:ModifyVolume\"\n\t\t\t],\n\t\t\t\"Resource\": + \"*\"\n\t\t}\n\t]\n}" + type: string + required: + - controlPlaneOperatorARN + - imageRegistryARN + - ingressARN + - kmsProviderARN + - kubeCloudControllerARN + - networkARN + - nodePoolManagementARN + - storageARN + type: object + required: + - operatorRolesRef + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index b7fbdd0d73..fd969ec42c 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -24,6 +24,7 @@ resources: - bases/controlplane.cluster.x-k8s.io_rosacontrolplanes.yaml - bases/infrastructure.cluster.x-k8s.io_rosaclusters.yaml - bases/infrastructure.cluster.x-k8s.io_rosamachinepools.yaml +- bases/infrastructure.cluster.x-k8s.io_rosaroleconfigs.yaml # +kubebuilder:scaffold:crdkustomizeresource patchesStrategicMerge: @@ -38,6 +39,7 @@ patchesStrategicMerge: - patches/webhook_in_awsmanagedcontrolplanes.yaml - patches/webhook_in_eksconfigs.yaml - patches/webhook_in_eksconfigtemplates.yaml +- patches/webhook_in_rosaroleconfigs.yaml # +kubebuilder:scaffold:crdkustomizewebhookpatch # [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix. @@ -52,6 +54,7 @@ patchesStrategicMerge: - patches/cainjection_in_awsmanagedclusters.yaml - patches/cainjection_in_eksconfigs.yaml - patches/cainjection_in_eksconfigtemplates.yaml +- patches/cainjection_in_rosaroleconfigs.yaml # +kubebuilder:scaffold:crdkustomizecainjectionpatch # [LABEL] To enable label, uncomment all the sections with [LABEL] prefix. diff --git a/config/crd/patches/cainjection_in_rosaroleconfigs.yaml b/config/crd/patches/cainjection_in_rosaroleconfigs.yaml new file mode 100644 index 0000000000..459eb1b2ab --- /dev/null +++ b/config/crd/patches/cainjection_in_rosaroleconfigs.yaml @@ -0,0 +1,8 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +# CRD conversion requires k8s 1.13 or later. +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: rosaroleconfigs.infrastructure.cluster.x-k8s.io diff --git a/config/crd/patches/webhook_in_rosaroleconfigs.yaml b/config/crd/patches/webhook_in_rosaroleconfigs.yaml new file mode 100644 index 0000000000..436cdd22fd --- /dev/null +++ b/config/crd/patches/webhook_in_rosaroleconfigs.yaml @@ -0,0 +1,16 @@ +# The following patch enables conversion webhook for CRD +# CRD conversion requires k8s 1.13 or later. +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: rosaroleconfigs.infrastructure.cluster.x-k8s.io +spec: + conversion: + strategy: Webhook + webhook: + conversionReviewVersions: ["v1", "v1beta1"] + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert \ No newline at end of file diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index cb04a602d7..b35ffe5443 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -176,6 +176,7 @@ rules: - awsfargateprofiles/status - rosaclusters/status - rosamachinepools/status + - rosaroleconfigs/status verbs: - get - patch @@ -197,6 +198,7 @@ rules: - infrastructure.cluster.x-k8s.io resources: - awsmachines + - rosaroleconfigs verbs: - create - delete diff --git a/config/webhook/manifests.yaml b/config/webhook/manifests.yaml index 5eebfae968..f836f48aa6 100644 --- a/config/webhook/manifests.yaml +++ b/config/webhook/manifests.yaml @@ -223,6 +223,28 @@ webhooks: resources: - rosamachinepools sideEffects: None +- admissionReviewVersions: + - v1 + - v1beta1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /mutate-infrastructure-cluster-x-k8s-io-v1beta2-rosaroleconfig + failurePolicy: Fail + matchPolicy: Equivalent + name: default.rosaroleconfig.infrastructure.cluster.x-k8s.io + rules: + - apiGroups: + - infrastructure.cluster.x-k8s.io + apiVersions: + - v1beta2 + operations: + - CREATE + - UPDATE + resources: + - rosaroleconfigs + sideEffects: None - admissionReviewVersions: - v1 - v1beta1 @@ -559,6 +581,28 @@ webhooks: resources: - rosamachinepools sideEffects: None +- admissionReviewVersions: + - v1 + - v1beta1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /validate-infrastructure-cluster-x-k8s-io-v1beta2-rosaroleconfig + failurePolicy: Fail + matchPolicy: Equivalent + name: validation.rosaroleconfig.infrastructure.cluster.x-k8s.io + rules: + - apiGroups: + - infrastructure.cluster.x-k8s.io + apiVersions: + - v1beta2 + operations: + - CREATE + - UPDATE + resources: + - rosaroleconfigs + sideEffects: None - admissionReviewVersions: - v1 - v1beta1 diff --git a/controlplane/rosa/api/v1beta2/rosacontrolplane_types.go b/controlplane/rosa/api/v1beta2/rosacontrolplane_types.go index b7bc7a5706..b4e011255e 100644 --- a/controlplane/rosa/api/v1beta2/rosacontrolplane_types.go +++ b/controlplane/rosa/api/v1beta2/rosacontrolplane_types.go @@ -21,7 +21,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" infrav1 "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2" - expinfrav1 "sigs.k8s.io/cluster-api-provider-aws/v2/exp/api/v1beta2" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" ) @@ -327,7 +326,7 @@ type DefaultMachinePoolSpec struct { // Autoscaling specifies auto scaling behaviour for the default MachinePool. Autoscaling min/max value // must be equal or multiple of the availability zones count. // +optional - Autoscaling *expinfrav1.RosaMachinePoolAutoScaling `json:"autoscaling,omitempty"` + Autoscaling *AutoScaling `json:"autoscaling,omitempty"` // VolumeSize set the disk volume size for the default workers machine pool in Gib. The default is 300 GiB. // +kubebuilder:validation:Minimum=75 @@ -337,6 +336,14 @@ type DefaultMachinePoolSpec struct { VolumeSize int `json:"volumeSize,omitempty"` } +// AutoScaling specifies scaling options. +type AutoScaling struct { + // +kubebuilder:validation:Minimum=1 + MinReplicas int `json:"minReplicas,omitempty"` + // +kubebuilder:validation:Minimum=1 + MaxReplicas int `json:"maxReplicas,omitempty"` +} + // AWSRolesRef contains references to various AWS IAM roles required for operators to make calls against the AWS API. type AWSRolesRef struct { // The referenced role must have a trust relationship that allows it to be assumed via web identity. diff --git a/controlplane/rosa/api/v1beta2/zz_generated.deepcopy.go b/controlplane/rosa/api/v1beta2/zz_generated.deepcopy.go index 3e4dfdf8cf..4ba9ff5932 100644 --- a/controlplane/rosa/api/v1beta2/zz_generated.deepcopy.go +++ b/controlplane/rosa/api/v1beta2/zz_generated.deepcopy.go @@ -24,7 +24,6 @@ import ( "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" apiv1beta2 "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2" - expapiv1beta2 "sigs.k8s.io/cluster-api-provider-aws/v2/exp/api/v1beta2" "sigs.k8s.io/cluster-api/api/v1beta1" ) @@ -43,12 +42,27 @@ func (in *AWSRolesRef) DeepCopy() *AWSRolesRef { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AutoScaling) DeepCopyInto(out *AutoScaling) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AutoScaling. +func (in *AutoScaling) DeepCopy() *AutoScaling { + if in == nil { + return nil + } + out := new(AutoScaling) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DefaultMachinePoolSpec) DeepCopyInto(out *DefaultMachinePoolSpec) { *out = *in if in.Autoscaling != nil { in, out := &in.Autoscaling, &out.Autoscaling - *out = new(expapiv1beta2.RosaMachinePoolAutoScaling) + *out = new(AutoScaling) **out = **in } } diff --git a/exp/api/v1beta2/rosamachinepool_types.go b/exp/api/v1beta2/rosamachinepool_types.go index 0dc3af30ed..a3286a4a2e 100644 --- a/exp/api/v1beta2/rosamachinepool_types.go +++ b/exp/api/v1beta2/rosamachinepool_types.go @@ -22,6 +22,7 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" infrav1 "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2" + rosacontrolplanev1 "sigs.k8s.io/cluster-api-provider-aws/v2/controlplane/rosa/api/v1beta2" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" ) @@ -79,7 +80,7 @@ type RosaMachinePoolSpec struct { // Autoscaling specifies auto scaling behaviour for this MachinePool. // required if Replicas is not configured // +optional - Autoscaling *RosaMachinePoolAutoScaling `json:"autoscaling,omitempty"` + Autoscaling *rosacontrolplanev1.AutoScaling `json:"autoscaling,omitempty"` // TuningConfigs specifies the names of the tuning configs to be applied to this MachinePool. // Tuning configs must already exist. @@ -139,14 +140,6 @@ type RosaTaint struct { Effect corev1.TaintEffect `json:"effect"` } -// RosaMachinePoolAutoScaling specifies scaling options. -type RosaMachinePoolAutoScaling struct { - // +kubebuilder:validation:Minimum=1 - MinReplicas int `json:"minReplicas,omitempty"` - // +kubebuilder:validation:Minimum=1 - MaxReplicas int `json:"maxReplicas,omitempty"` -} - // RosaUpdateConfig specifies update configuration type RosaUpdateConfig struct { // RollingUpdate specifies MaxUnavailable & MaxSurge number of nodes during update. diff --git a/exp/api/v1beta2/rosaroleconfig_types.go b/exp/api/v1beta2/rosaroleconfig_types.go new file mode 100644 index 0000000000..85a3a03a60 --- /dev/null +++ b/exp/api/v1beta2/rosaroleconfig_types.go @@ -0,0 +1,167 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta2 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + infrav1 "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2" + rosacontrolplanev1 "sigs.k8s.io/cluster-api-provider-aws/v2/controlplane/rosa/api/v1beta2" + clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// ROSARoleConfigSpec defines the desired state of ROSARoleConfig +type ROSARoleConfigSpec struct { + AccountRoleConfig AccountRoleConfig `json:"accountRoleConfig"` + OperatorRoleConfig OperatorRoleConfig `json:"operatorRoleConfig"` + OIDCConfig OIDCConfig `json:"oidcConfig"` + IdentityRef *infrav1.AWSIdentityReference `json:"identityRef,omitempty"` + Region string `json:"region,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:resource:path=rosaroleconfigs,scope=Namespaced,categories=cluster-api,shortName=rosarole +// +kubebuilder:storageversion +// +kubebuilder:subresource:status + +// ROSARoleConfig is the Schema for the rosaroleconfigs API +type ROSARoleConfig struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec ROSARoleConfigSpec `json:"spec,omitempty"` + Status ROSARoleConfigStatus `json:"status,omitempty"` +} + +// AccountRoleConfig defines account-wide IAM roles before creating your ROSA cluster. +type AccountRoleConfig struct { + // User-defined prefix for all generated AWS resources + // +kubebuilder:validation:MaxLength:=4 + // +kubebuilder:validation:Required + Prefix string `json:"prefix"` + // The ARN of the policy that is used to set the permissions boundary for the account roles. + // +optional + PermissionsBoundaryARN string `json:"permissionsBoundaryARN,omitempty"` + // The arn path for the account/operator roles as well as their policies. + // +optional + Path string `json:"path,omitempty"` + // Version of OpenShift that will be used to setup policy tag, for example "4.11" + // +kubebuilder:validation:Required + Version string `json:"version"` + // SharedVPCConfig is used to set up shared VPC. + // +optional + SharedVPCConfig SharedVPCConfig `json:"sharedVPCConfig,omitempty"` +} + +// OperatorRoleConfig defines cluster-specific operator IAM roles based on your cluster configuration. +type OperatorRoleConfig struct { + // User-defined prefix for generated AWS operator policies. + // +kubebuilder:validation:MaxLength:=4 + // +kubebuilder:validation:Required + Prefix string `json:"prefix"` + // The ARN of the policy that is used to set the permissions boundary for the operator roles. + // +optional + PermissionsBoundaryARN string `json:"permissionsBoundaryARN,omitempty"` + // Registered OIDC configuration ID to add its issuer URL as the trusted relationship to the operator roles.'' + // +kubebuilder:validation:Required + OIDCConfigID string `json:"oidcConfigId,omitempty"` + // SharedVPCConfig is used to set up shared VPC. + // +optional + SharedVPCConfig SharedVPCConfig `json:"sharedVPCConfig,omitempty"` +} + +// SharedVPCConfig is used to set up shared VPC. +type SharedVPCConfig struct { + // Role ARN associated with the private hosted zone used for Hosted Control Plane cluster shared VPC, this role contains policies to be used with Route 53 + RouteRoleARN string `json:"routeRoleARN,omitempty"` + // Role ARN associated with the shared VPC used for Hosted Control Plane clusters, this role contains policies to be used with the VPC endpoint + VPCEndpointRoleARN string `json:"vpcEndpointRoleArn,omitempty"` +} + +// OIDCConfig creates OIDC config in a S3 bucket for the client AWS account and populates it to be compliant with OIDC protocol. +// It also creates a Secret in Secrets Manager containing the private key. +type OIDCConfig struct { + // ManagedOIDC indicates whether it is a Red Hat managed or unmanaged (Customer hosted) OIDC Configuration. Default is true. + // +kubebuilder:default=true + ManagedOIDC bool `json:"managedOIDC"` + // ExternalAuthProviders are external OIDC identity providers that can issue tokens for this cluster. + // Can only be set if "enableExternalAuthProviders" is set to "True". + // + // At most one provider can be configured. + // + // +listType=map + // +listMapKey=name + // +kubebuilder:validation:MaxItems=1 + ExternalAuthProviders []rosacontrolplanev1.ExternalAuthProvider `json:"externalAuthProviders,omitempty"` +} + +// ROSARoleConfigStatus defines the observed state of ROSARoleConfig +type ROSARoleConfigStatus struct { + // ID of created OIDC config + OIDCID string `json:"oidcID,omitempty"` + // Create OIDC provider for operators to authenticate against in an STS cluster. + OIDCProviderARN string `json:"oidcProviderARN,omitempty"` + // Created Account roles that can be used to + AccountRolesRef AccountRolesRef `json:"accountRolesRef,omitempty"` + // AWS IAM roles used to perform credential requests by the openshift operators. + OperatorRolesRef rosacontrolplanev1.AWSRolesRef `json:"operatorRolesRef"` + Conditions clusterv1.Conditions `json:"conditions,omitempty"` +} + +// AccountRolesRef defscribes ARNs used as Account roles. +type AccountRolesRef struct { + // InstallerRoleARN is an AWS IAM role that OpenShift Cluster Manager will assume to create the cluster.. + InstallerRoleARN string `json:"installerRoleARN"` + // SupportRoleARN is an AWS IAM role used by Red Hat SREs to enable + // access to the cluster account in order to provide support. + SupportRoleARN string `json:"supportRoleARN"` + // WorkerRoleARN is an AWS IAM role that will be attached to worker instances. + WorkerRoleARN string `json:"workerRoleARN,omitempty"` +} + +// ROSARoleConfigList contains a list of ROSARoleConfig +// +kubebuilder:object:root=true +type ROSARoleConfigList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []ROSARoleConfig `json:"items"` +} + +const ( + // RosaRoleConfigReadyCondition condition reports on the successful reconciliation of RosaRoleConfig. + RosaRoleConfigReadyCondition clusterv1.ConditionType = "RosaRoleConfigReady" + + // RosaRoleConfigDeletionFailedReason used to report failures while deleting RosaRoleConfig. + RosaRoleConfigDeletionFailedReason = "DeletionFailed" +) + +// GetConditions returns the observations of the operational state of the RosaNetwork resource. +func (r *ROSARoleConfig) GetConditions() clusterv1.Conditions { + return r.Status.Conditions +} + +// SetConditions sets the underlying service state of the RosaRoleConfig to the predescribed clusterv1.Conditions. +func (r *ROSARoleConfig) SetConditions(conditions clusterv1.Conditions) { + r.Status.Conditions = conditions +} + +func init() { + SchemeBuilder.Register(&ROSARoleConfig{}, &ROSARoleConfigList{}) +} diff --git a/exp/api/v1beta2/rosaroleconfig_webhook.go b/exp/api/v1beta2/rosaroleconfig_webhook.go new file mode 100644 index 0000000000..e0b3feb589 --- /dev/null +++ b/exp/api/v1beta2/rosaroleconfig_webhook.go @@ -0,0 +1,48 @@ +package v1beta2 + +import ( + "context" + + runtime "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/webhook" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" +) + +// SetupWebhookWithManager will setup the webhooks for the ROSARoleConfig. +func (r *ROSARoleConfig) SetupWebhookWithManager(mgr ctrl.Manager) error { + w := new(rosaRoleConfigWebhook) + return ctrl.NewWebhookManagedBy(mgr). + For(r). + WithValidator(w). + WithDefaulter(w). + Complete() +} + +// +kubebuilder:webhook:verbs=create;update,path=/validate-infrastructure-cluster-x-k8s-io-v1beta2-rosaroleconfig,mutating=false,failurePolicy=fail,matchPolicy=Equivalent,groups=infrastructure.cluster.x-k8s.io,resources=rosaroleconfigs,versions=v1beta2,name=validation.rosaroleconfig.infrastructure.cluster.x-k8s.io,sideEffects=None,admissionReviewVersions=v1;v1beta1 +// +kubebuilder:webhook:verbs=create;update,path=/mutate-infrastructure-cluster-x-k8s-io-v1beta2-rosaroleconfig,mutating=true,failurePolicy=fail,matchPolicy=Equivalent,groups=infrastructure.cluster.x-k8s.io,resources=rosaroleconfigs,versions=v1beta2,name=default.rosaroleconfig.infrastructure.cluster.x-k8s.io,sideEffects=None,admissionReviewVersions=v1;v1beta1 + +type rosaRoleConfigWebhook struct{} + +var _ webhook.CustomDefaulter = &rosaRoleConfigWebhook{} +var _ webhook.CustomValidator = &rosaRoleConfigWebhook{} + +// ValidateCreate implements admission.Validator. +func (r *rosaRoleConfigWebhook) ValidateCreate(ctx context.Context, obj runtime.Object) (warnings admission.Warnings, err error) { + return nil, nil +} + +// ValidateUpdate implements admission.Validator. +func (r *rosaRoleConfigWebhook) ValidateUpdate(ctx context.Context, old runtime.Object, updated runtime.Object) (warnings admission.Warnings, err error) { + return nil, nil +} + +// ValidateDelete implements admission.Validator. +func (r *rosaRoleConfigWebhook) ValidateDelete(ctx context.Context, obj runtime.Object) (warnings admission.Warnings, err error) { + return nil, nil +} + +// Default implements admission.Defaulter. +func (r *rosaRoleConfigWebhook) Default(ctx context.Context, obj runtime.Object) error { + return nil +} diff --git a/exp/api/v1beta2/zz_generated.deepcopy.go b/exp/api/v1beta2/zz_generated.deepcopy.go index 6885eb4c64..44fe62bd66 100644 --- a/exp/api/v1beta2/zz_generated.deepcopy.go +++ b/exp/api/v1beta2/zz_generated.deepcopy.go @@ -25,6 +25,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" apiv1beta2 "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2" + rosaapiv1beta2 "sigs.k8s.io/cluster-api-provider-aws/v2/controlplane/rosa/api/v1beta2" "sigs.k8s.io/cluster-api/api/v1beta1" ) @@ -621,6 +622,37 @@ func (in *AWSManagedMachinePoolStatus) DeepCopy() *AWSManagedMachinePoolStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AccountRoleConfig) DeepCopyInto(out *AccountRoleConfig) { + *out = *in + out.SharedVPCConfig = in.SharedVPCConfig +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AccountRoleConfig. +func (in *AccountRoleConfig) DeepCopy() *AccountRoleConfig { + if in == nil { + return nil + } + out := new(AccountRoleConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AccountRolesRef) DeepCopyInto(out *AccountRolesRef) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AccountRolesRef. +func (in *AccountRolesRef) DeepCopy() *AccountRolesRef { + if in == nil { + return nil + } + out := new(AccountRolesRef) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AutoScalingGroup) DeepCopyInto(out *AutoScalingGroup) { *out = *in @@ -891,6 +923,44 @@ func (in *MixedInstancesPolicy) DeepCopy() *MixedInstancesPolicy { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OIDCConfig) DeepCopyInto(out *OIDCConfig) { + *out = *in + if in.ExternalAuthProviders != nil { + in, out := &in.ExternalAuthProviders, &out.ExternalAuthProviders + *out = make([]rosaapiv1beta2.ExternalAuthProvider, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OIDCConfig. +func (in *OIDCConfig) DeepCopy() *OIDCConfig { + if in == nil { + return nil + } + out := new(OIDCConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OperatorRoleConfig) DeepCopyInto(out *OperatorRoleConfig) { + *out = *in + out.SharedVPCConfig = in.SharedVPCConfig +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OperatorRoleConfig. +func (in *OperatorRoleConfig) DeepCopy() *OperatorRoleConfig { + if in == nil { + return nil + } + out := new(OperatorRoleConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Overrides) DeepCopyInto(out *Overrides) { *out = *in @@ -1129,6 +1199,112 @@ func (in *ROSAMachinePoolList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ROSARoleConfig) DeepCopyInto(out *ROSARoleConfig) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ROSARoleConfig. +func (in *ROSARoleConfig) DeepCopy() *ROSARoleConfig { + if in == nil { + return nil + } + out := new(ROSARoleConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ROSARoleConfig) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ROSARoleConfigList) DeepCopyInto(out *ROSARoleConfigList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ROSARoleConfig, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ROSARoleConfigList. +func (in *ROSARoleConfigList) DeepCopy() *ROSARoleConfigList { + if in == nil { + return nil + } + out := new(ROSARoleConfigList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ROSARoleConfigList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ROSARoleConfigSpec) DeepCopyInto(out *ROSARoleConfigSpec) { + *out = *in + out.AccountRoleConfig = in.AccountRoleConfig + out.OperatorRoleConfig = in.OperatorRoleConfig + in.OIDCConfig.DeepCopyInto(&out.OIDCConfig) + if in.IdentityRef != nil { + in, out := &in.IdentityRef, &out.IdentityRef + *out = new(apiv1beta2.AWSIdentityReference) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ROSARoleConfigSpec. +func (in *ROSARoleConfigSpec) DeepCopy() *ROSARoleConfigSpec { + if in == nil { + return nil + } + out := new(ROSARoleConfigSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ROSARoleConfigStatus) DeepCopyInto(out *ROSARoleConfigStatus) { + *out = *in + out.AccountRolesRef = in.AccountRolesRef + out.OperatorRolesRef = in.OperatorRolesRef + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make(v1beta1.Conditions, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ROSARoleConfigStatus. +func (in *ROSARoleConfigStatus) DeepCopy() *ROSARoleConfigStatus { + if in == nil { + return nil + } + out := new(ROSARoleConfigStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RefreshPreferences) DeepCopyInto(out *RefreshPreferences) { *out = *in @@ -1189,21 +1365,6 @@ func (in *RollingUpdate) DeepCopy() *RollingUpdate { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RosaMachinePoolAutoScaling) DeepCopyInto(out *RosaMachinePoolAutoScaling) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RosaMachinePoolAutoScaling. -func (in *RosaMachinePoolAutoScaling) DeepCopy() *RosaMachinePoolAutoScaling { - if in == nil { - return nil - } - out := new(RosaMachinePoolAutoScaling) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RosaMachinePoolSpec) DeepCopyInto(out *RosaMachinePoolSpec) { *out = *in @@ -1228,7 +1389,7 @@ func (in *RosaMachinePoolSpec) DeepCopyInto(out *RosaMachinePoolSpec) { } if in.Autoscaling != nil { in, out := &in.Autoscaling, &out.Autoscaling - *out = new(RosaMachinePoolAutoScaling) + *out = new(rosaapiv1beta2.AutoScaling) **out = **in } if in.TuningConfigs != nil { @@ -1335,6 +1496,21 @@ func (in *RosaUpdateConfig) DeepCopy() *RosaUpdateConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SharedVPCConfig) DeepCopyInto(out *SharedVPCConfig) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SharedVPCConfig. +func (in *SharedVPCConfig) DeepCopy() *SharedVPCConfig { + if in == nil { + return nil + } + out := new(SharedVPCConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SuspendProcessesTypes) DeepCopyInto(out *SuspendProcessesTypes) { *out = *in diff --git a/exp/controllers/rosamachinepool_controller.go b/exp/controllers/rosamachinepool_controller.go index aab404cf42..c70cc46969 100644 --- a/exp/controllers/rosamachinepool_controller.go +++ b/exp/controllers/rosamachinepool_controller.go @@ -542,7 +542,7 @@ func nodePoolToRosaMachinePoolSpec(nodePool *cmv1.NodePool) expinfrav1.RosaMachi } if nodePool.Autoscaling() != nil { - spec.Autoscaling = &expinfrav1.RosaMachinePoolAutoScaling{ + spec.Autoscaling = &rosacontrolplanev1.AutoScaling{ MinReplicas: nodePool.Autoscaling().MinReplica(), MaxReplicas: nodePool.Autoscaling().MaxReplica(), } diff --git a/exp/controllers/rosaroleconfig_controller.go b/exp/controllers/rosaroleconfig_controller.go new file mode 100644 index 0000000000..71b38285f2 --- /dev/null +++ b/exp/controllers/rosaroleconfig_controller.go @@ -0,0 +1,93 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controllers + +import ( + "context" + "errors" + "fmt" + + "github.com/aws/aws-sdk-go/service/sts/stsiface" + "github.com/go-logr/logr" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + + expinfrav1 "sigs.k8s.io/cluster-api-provider-aws/v2/exp/api/v1beta2" + "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud" + "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/scope" + "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/logger" + "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/rosa" + "sigs.k8s.io/cluster-api/util/predicates" +) + +// ROSARoleConfigReconciler reconciles a ROSARoleConfig object. +type ROSARoleConfigReconciler struct { + client.Client + Log logr.Logger + Scheme *runtime.Scheme + WatchFilterValue string + NewStsClient func(cloud.ScopeUsage, cloud.Session, logger.Wrapper, runtime.Object) stsiface.STSAPI + NewOCMClient func(ctx context.Context, rosaScope *scope.ROSAControlPlaneScope) (rosa.OCMClient, error) + Endpoints []scope.ServiceEndpoint +} + +// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=rosaroleconfigs,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=rosaroleconfigs/status,verbs=get;update;patch + +func (r *ROSARoleConfigReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res ctrl.Result, reterr error) { + roleConfig := &expinfrav1.ROSARoleConfig{} + if err := r.Get(ctx, req.NamespacedName, roleConfig); err != nil { + if apierrors.IsNotFound(err) { + return ctrl.Result{}, nil + } + return ctrl.Result{Requeue: true}, nil + } + roleConfigScope, err := scope.NewRosaRoleConfigScope(scope.RosaRoleConfigScopeParams{ + Client: r.Client, + RosaRoleConfig: roleConfig, + ControllerName: "rosaroleconfig", + Endpoints: r.Endpoints, + Logger: logger.NewLogger(r.Log), + }) + if err != nil { + return ctrl.Result{}, fmt.Errorf("failed to create rosaroleconfig scope: %w", err) + } + + // Always close the scope + defer func() { + if err := roleConfigScope.Close(); err != nil { + reterr = errors.Join(reterr, err) + } + }() + + return ctrl.Result{}, nil +} + +func (r *ROSARoleConfigReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { + log := logger.FromContext(ctx) + r.NewOCMClient = rosa.NewWrappedOCMClient + r.NewStsClient = scope.NewSTSClient + + return ctrl.NewControllerManagedBy(mgr). + For(&expinfrav1.ROSARoleConfig{}). + WithOptions(options). + WithEventFilter(predicates.ResourceHasFilterLabel(mgr.GetScheme(), log.GetLogger(), r.WatchFilterValue)). + Complete(r) +} diff --git a/exp/controllers/rosaroleconfig_controller_test.go b/exp/controllers/rosaroleconfig_controller_test.go new file mode 100644 index 0000000000..53b665c0f4 --- /dev/null +++ b/exp/controllers/rosaroleconfig_controller_test.go @@ -0,0 +1,55 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controllers + +import ( + "context" + "testing" + + . "github.com/onsi/gomega" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + + expinfrav1 "sigs.k8s.io/cluster-api-provider-aws/v2/exp/api/v1beta2" +) + +func TestROSARoleConfigReconciler_Reconcile(t *testing.T) { + g := NewWithT(t) + + ctx := context.TODO() + + rosaRoleConfig := &expinfrav1.ROSARoleConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-rosa-role", + Namespace: "test-namespace"}, + Spec: expinfrav1.ROSARoleConfigSpec{}, + } + + // Setup the reconciler with these mocks + reconciler := &ROSARoleConfigReconciler{ + Client: testEnv.Client, + } + + // Call the Reconcile function + req := ctrl.Request{} + req.NamespacedName = types.NamespacedName{Name: rosaRoleConfig.Name, Namespace: rosaRoleConfig.Namespace} + _, errReconcile := reconciler.Reconcile(ctx, req) + + // Assertions + g.Expect(errReconcile).ToNot(HaveOccurred()) +} diff --git a/go.mod b/go.mod index 8894dc3953..580ca52f07 100644 --- a/go.mod +++ b/go.mod @@ -29,9 +29,10 @@ require ( github.com/google/gofuzz v1.2.0 github.com/onsi/ginkgo/v2 v2.23.3 github.com/onsi/gomega v1.36.3 - github.com/openshift-online/ocm-common v0.0.12 - github.com/openshift-online/ocm-sdk-go v0.1.447 - github.com/openshift/rosa v1.2.48-rc1 + github.com/openshift-online/ocm-api-model/clientapi v0.0.0-20250619114224-37dc3401307a + github.com/openshift-online/ocm-common v0.0.21 + github.com/openshift-online/ocm-sdk-go v0.1.469 + github.com/openshift/rosa v1.2.52 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.19.1 github.com/sergi/go-diff v1.3.1 @@ -176,6 +177,7 @@ require ( github.com/onsi/ginkgo v1.16.5 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0-rc5 // indirect + github.com/openshift-online/ocm-api-model/model v0.0.0-20250619114224-37dc3401307a // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/prometheus/client_model v0.6.1 // indirect diff --git a/go.sum b/go.sum index 4d51df71d6..0db99ea448 100644 --- a/go.sum +++ b/go.sum @@ -300,10 +300,10 @@ github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/itchyny/gojq v0.12.7 h1:hYPTpeWfrJ1OT+2j6cvBScbhl0TkdwGM4bc66onUSOQ= -github.com/itchyny/gojq v0.12.7/go.mod h1:ZdvNHVlzPgUf8pgjnuDTmGfHA/21KoutQUJ3An/xNuw= -github.com/itchyny/timefmt-go v0.1.3 h1:7M3LGVDsqcd0VZH2U+x393obrzZisp7C0uEe921iRkU= -github.com/itchyny/timefmt-go v0.1.3/go.mod h1:0osSSCQSASBJMsIZnhAaF1C2fCBTJZXrnj37mG8/c+A= +github.com/itchyny/gojq v0.12.9 h1:biKpbKwMxVYhCU1d6mR7qMr3f0Hn9F5k5YykCVb3gmM= +github.com/itchyny/gojq v0.12.9/go.mod h1:T4Ip7AETUXeGpD+436m+UEl3m3tokRgajd5pRfsR5oE= +github.com/itchyny/timefmt-go v0.1.4 h1:hFEfWVdwsEi+CY8xY2FtgWHGQaBaC3JeHd+cve0ynVM= +github.com/itchyny/timefmt-go v0.1.4/go.mod h1:nEP7L+2YmAbT2kZ2HfSs1d8Xtw9LY8D2stDBckWakZ8= github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w= @@ -407,12 +407,16 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= -github.com/openshift-online/ocm-common v0.0.12 h1:2oGZCXd8O/nZVlM0pvTtm3hDGbW/ncTtvSLLB+nuQf0= -github.com/openshift-online/ocm-common v0.0.12/go.mod h1:6MWje2NFNJ3IWpGs7BYj6DWagWXHyp8EnmYY7XFTtI4= -github.com/openshift-online/ocm-sdk-go v0.1.447 h1:PLau6NVgTpwL+L5OcKrBZm+HbET34tjHbENd2GsFhRw= -github.com/openshift-online/ocm-sdk-go v0.1.447/go.mod h1:CiAu2jwl3ITKOxkeV0Qnhzv4gs35AmpIzVABQLtcI2Y= -github.com/openshift/rosa v1.2.48-rc1 h1:lmggIjrEQ5BgMVZbL3P/nFKUgxdxkdY5z6Zj3GrUl9M= -github.com/openshift/rosa v1.2.48-rc1/go.mod h1:lZaAjhCj+8oklBODolna8G0dqjlISq5+djzyfcx5gG0= +github.com/openshift-online/ocm-api-model/clientapi v0.0.0-20250619114224-37dc3401307a h1:6yb+WG4oqakYlBxMZptAF2ijLq+IQ1sG9e7yWbHyFgU= +github.com/openshift-online/ocm-api-model/clientapi v0.0.0-20250619114224-37dc3401307a/go.mod h1:fZwy5HY2URG9nrExvQeXrDU/08TGqZ16f8oymVEN5lo= +github.com/openshift-online/ocm-api-model/model v0.0.0-20250619114224-37dc3401307a h1:rGtR5YLBV/MxoDMoJdIqdgYbb8ljNC6ycMf1dog5LuM= +github.com/openshift-online/ocm-api-model/model v0.0.0-20250619114224-37dc3401307a/go.mod h1:B/fZxd88BTKig/rCCc189cENnFlEzxQslHHzoSgvo1I= +github.com/openshift-online/ocm-common v0.0.21 h1:rfp6cPPgVPk/Sm7xAsNFQHaK9Ng1bEOaGjPx0lI1hOA= +github.com/openshift-online/ocm-common v0.0.21/go.mod h1:VEkuZp9aqbXtetZ5ycND6QpvhykvTuBF3oPsVM1X3vI= +github.com/openshift-online/ocm-sdk-go v0.1.469 h1:PdtKbT007q9OjFHMznutIxPXaembpM/LL8tP7oMtc50= +github.com/openshift-online/ocm-sdk-go v0.1.469/go.mod h1:RjLocq1aHUZ4h7LxkAqZVCIrPgWOiaJN1qOtDHY6MA4= +github.com/openshift/rosa v1.2.52 h1:qShihQbzkms9J9ydr6OkppjKEpePXVNmBYckNXQcU48= +github.com/openshift/rosa v1.2.52/go.mod h1:mHAI0wqPRp6BGFmgS+m1xcFEMmi5C2oSdLEyQQ8tTvU= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= diff --git a/main.go b/main.go index 8d5e4ebc88..7a3b62d7e7 100644 --- a/main.go +++ b/main.go @@ -285,6 +285,14 @@ func main() { } } + if err = (&expcontrollers.ROSARoleConfigReconciler{ + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controllers").WithName("ROSARoleConfig"), + Scheme: mgr.GetScheme(), + }).SetupWithManager(ctx, mgr, controller.Options{MaxConcurrentReconciles: awsClusterConcurrency, RecoverPanic: ptr.To[bool](true)}); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "ROSARoleConfig") + os.Exit(1) + } // +kubebuilder:scaffold:builder if err := mgr.AddReadyzCheck("webhook", mgr.GetWebhookServer().StartedChecker()); err != nil { diff --git a/pkg/cloud/scope/rosaroleconfig.go b/pkg/cloud/scope/rosaroleconfig.go new file mode 100644 index 0000000000..009281e8f4 --- /dev/null +++ b/pkg/cloud/scope/rosaroleconfig.go @@ -0,0 +1,153 @@ +/* + Copyright The Kubernetes Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package scope + +import ( + "context" + + awsclient "github.com/aws/aws-sdk-go/aws/client" + "github.com/pkg/errors" + corev1 "k8s.io/api/core/v1" + "k8s.io/klog/v2" + "sigs.k8s.io/controller-runtime/pkg/client" + + infrav1 "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2" + expinfrav1 "sigs.k8s.io/cluster-api-provider-aws/v2/exp/api/v1beta2" + "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud" + "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/throttle" + "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/logger" + clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" + "sigs.k8s.io/cluster-api/util/patch" +) + +// RosaRoleConfigScopeParams defines the input parameters used to create a new RosaRoleConfigScope. +type RosaRoleConfigScopeParams struct { + Client client.Client + ControllerName string + Endpoints []ServiceEndpoint + Logger *logger.Logger + RosaRoleConfig *expinfrav1.ROSARoleConfig +} + +// RosaRoleConfigScope defines the basic context for an actuator to operate upon. +type RosaRoleConfigScope struct { + logger.Logger + Client client.Client + controllerName string + patchHelper *patch.Helper + RosaRoleConfig *expinfrav1.ROSARoleConfig + serviceLimiters throttle.ServiceLimiters + session awsclient.ConfigProvider +} + +// NewRosaRoleConfigScope creates a new RosaRoleConfigScope from the supplied parameters. +func NewRosaRoleConfigScope(params RosaRoleConfigScopeParams) (*RosaRoleConfigScope, error) { + if params.Logger == nil { + log := klog.Background() + params.Logger = logger.NewLogger(log) + } + + RosaRoleConfigScope := &RosaRoleConfigScope{ + Logger: *params.Logger, + Client: params.Client, + controllerName: params.ControllerName, + patchHelper: nil, + RosaRoleConfig: params.RosaRoleConfig, + } + + session, serviceLimiters, err := sessionForClusterWithRegion(params.Client, RosaRoleConfigScope, params.RosaRoleConfig.Spec.Region, params.Endpoints, params.Logger) + if err != nil { + return nil, errors.Errorf("failed to create aws session: %v", err) + } + + patchHelper, err := patch.NewHelper(params.RosaRoleConfig, params.Client) + if err != nil { + return nil, errors.Wrap(err, "failed to init patch helper") + } + + RosaRoleConfigScope.patchHelper = patchHelper + RosaRoleConfigScope.session = session + RosaRoleConfigScope.serviceLimiters = serviceLimiters + + return RosaRoleConfigScope, nil +} + +// IdentityRef returns the AWSIdentityReference object. +func (s *RosaRoleConfigScope) IdentityRef() *infrav1.AWSIdentityReference { + return s.RosaRoleConfig.Spec.IdentityRef +} + +// Session returns the AWS SDK session (used for creating clients). +func (s *RosaRoleConfigScope) Session() awsclient.ConfigProvider { + return s.session +} + +// ServiceLimiter returns the AWS SDK session (used for creating clients). +func (s *RosaRoleConfigScope) ServiceLimiter(service string) *throttle.ServiceLimiter { + if sl, ok := s.serviceLimiters[service]; ok { + return sl + } + return nil +} + +// ControllerName returns the name of the controller. +func (s *RosaRoleConfigScope) ControllerName() string { + return s.controllerName +} + +// InfraCluster returns the RosaRoleConfig object. +// The method is then used in session.go to set proper Conditions for the RosaRoleConfig object. +func (s *RosaRoleConfigScope) InfraCluster() cloud.ClusterObject { + return s.RosaRoleConfig +} + +// InfraClusterName returns the name of the RosaRoleConfig object. +// The method is then used in session.go to set the key to the AWS session cache. +func (s *RosaRoleConfigScope) InfraClusterName() string { + return s.RosaRoleConfig.Name +} + +// Namespace returns the namespace of the RosaRoleConfig object. +// The method is then used in session.go to set the key to the AWS session cache. +func (s *RosaRoleConfigScope) Namespace() string { + return s.RosaRoleConfig.Namespace +} + +// GetClient Returns RosaRoleConfigScope client. +func (s *RosaRoleConfigScope) GetClient() client.Client { + return s.Client +} + +// PatchObject persists the RosaRoleConfig configuration and status. +func (s *RosaRoleConfigScope) PatchObject() error { + return s.patchHelper.Patch( + context.TODO(), + s.RosaRoleConfig, + patch.WithOwnedConditions{Conditions: []clusterv1.ConditionType{ + expinfrav1.RosaRoleConfigReadyCondition, + }}) +} + +// Close closes the current scope persisting the RosaRoleConfig configuration and status. +func (s *RosaRoleConfigScope) Close() error { + return s.PatchObject() +} + +// CredentialsSecret returns the CredentialsSecret object. +func (s *RosaRoleConfigScope) CredentialsSecret() *corev1.Secret { + return nil +} diff --git a/pkg/rosa/ocmclient.go b/pkg/rosa/ocmclient.go index ca7fa81fb1..73cd6f6db1 100644 --- a/pkg/rosa/ocmclient.go +++ b/pkg/rosa/ocmclient.go @@ -30,10 +30,11 @@ type OCMClient interface { GetCluster(clusterKey string, creator *aws.Creator) (*v1.Cluster, error) GetControlPlaneUpgradePolicies(clusterID string) (controlPlaneUpgradePolicies []*v1.ControlPlaneUpgradePolicy, err error) GetHTPasswdUserList(clusterID string, htpasswdIDPId string) (*v1.HTPasswdUserList, error) + GetHypershiftNodePoolUpgrade(clusterID string, clusterKey string, nodePoolID string) (*v1.NodePool, *v1.NodePoolUpgradePolicy, error) GetIdentityProviders(clusterID string) ([]*v1.IdentityProvider, error) GetMissingGateAgreementsHypershift(clusterID string, upgradePolicy *v1.ControlPlaneUpgradePolicy) ([]*v1.VersionGate, error) GetNodePool(clusterID string, nodePoolID string) (*v1.NodePool, bool, error) - GetHypershiftNodePoolUpgrade(clusterID string, clusterKey string, nodePoolID string) (*v1.NodePool, *v1.NodePoolUpgradePolicy, error) + GetPolicies(policyType string) (map[string]*v1.AWSSTSPolicy, error) GetUser(clusterID string, group string, username string) (*v1.User, error) ScheduleHypershiftControlPlaneUpgrade(clusterID string, upgradePolicy *v1.ControlPlaneUpgradePolicy) (*v1.ControlPlaneUpgradePolicy, error) ScheduleNodePoolUpgrade(clusterID string, nodePoolID string, upgradePolicy *v1.NodePoolUpgradePolicy) (*v1.NodePoolUpgradePolicy, error) @@ -103,6 +104,10 @@ func (c *ocmclient) GetCluster(clusterKey string, creator *aws.Creator) (*v1.Clu return c.ocmClient.GetCluster(clusterKey, creator) } +func (c *ocmclient) GetPolicies(policyType string) (map[string]*v1.AWSSTSPolicy, error) { + return c.ocmClient.GetPolicies(policyType) +} + func (c *ocmclient) GetUser(clusterID string, group string, username string) (*v1.User, error) { return c.ocmClient.GetUser(clusterID, group, username) } diff --git a/test/mocks/ocm_client_mock.go b/test/mocks/ocm_client_mock.go index 4e948cf639..b2a1a597a1 100644 --- a/test/mocks/ocm_client_mock.go +++ b/test/mocks/ocm_client_mock.go @@ -24,7 +24,7 @@ import ( reflect "reflect" gomock "github.com/golang/mock/gomock" - v1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" + v1 "github.com/openshift-online/ocm-api-model/clientapi/clustersmgmt/v1" aws "github.com/openshift/rosa/pkg/aws" ocm "github.com/openshift/rosa/pkg/ocm" ) @@ -290,6 +290,21 @@ func (mr *MockOCMClientMockRecorder) GetNodePool(arg0, arg1 interface{}) *gomock return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNodePool", reflect.TypeOf((*MockOCMClient)(nil).GetNodePool), arg0, arg1) } +// GetPolicies mocks base method. +func (m *MockOCMClient) GetPolicies(arg0 string) (map[string]*v1.AWSSTSPolicy, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetPolicies", arg0) + ret0, _ := ret[0].(map[string]*v1.AWSSTSPolicy) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetPolicies indicates an expected call of GetPolicies. +func (mr *MockOCMClientMockRecorder) GetPolicies(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPolicies", reflect.TypeOf((*MockOCMClient)(nil).GetPolicies), arg0) +} + // GetUser mocks base method. func (m *MockOCMClient) GetUser(arg0, arg1, arg2 string) (*v1.User, error) { m.ctrl.T.Helper()