diff --git a/apis/v1alpha1/types.go b/apis/v1alpha1/types.go index fe2b8cd..919089b 100644 --- a/apis/v1alpha1/types.go +++ b/apis/v1alpha1/types.go @@ -24,8 +24,15 @@ import ( // A ProviderConfigSpec defines the desired state of a Provider. type ProviderConfigSpec struct { - // Credentials required to authenticate to this provider. + // Credentials used to connect to the Kubernetes API. Typically a + // kubeconfig file. Use InjectedIdentity for in-cluster config. Credentials ProviderCredentials `json:"credentials"` + + // Identity used to authenticate to the Kubernetes API. The identity + // credentials can be used to supplement kubeconfig 'credentials', for + // example by configuring a bearer token source such as OAuth. + // +optional + Identity *Identity `json:"identity,omitempty"` } // ProviderCredentials required to authenticate. @@ -37,6 +44,23 @@ type ProviderCredentials struct { xpv1.CommonCredentialSelectors `json:",inline"` } +// IdentityType used to authenticate to the Kubernetes API. +type IdentityType string + +// Supported identity types. +const ( + IdentityTypeGoogleApplicationCredentials = "GoogleApplicationCredentials" +) + +// Identity used to authenticate. +type Identity struct { + // Type of identity. + // +kubebuilder:validation:Enum=GoogleApplicationCredentials + Type IdentityType `json:"type"` + + ProviderCredentials `json:",inline"` +} + // A ProviderConfigStatus defines the status of a Provider. type ProviderConfigStatus struct { xpv1.ProviderConfigStatus `json:",inline"` diff --git a/apis/v1alpha1/zz_generated.deepcopy.go b/apis/v1alpha1/zz_generated.deepcopy.go index 9bc0ec6..4442f14 100644 --- a/apis/v1alpha1/zz_generated.deepcopy.go +++ b/apis/v1alpha1/zz_generated.deepcopy.go @@ -24,6 +24,22 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Identity) DeepCopyInto(out *Identity) { + *out = *in + in.ProviderCredentials.DeepCopyInto(&out.ProviderCredentials) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Identity. +func (in *Identity) DeepCopy() *Identity { + if in == nil { + return nil + } + out := new(Identity) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ProviderConfig) DeepCopyInto(out *ProviderConfig) { *out = *in @@ -87,6 +103,11 @@ func (in *ProviderConfigList) DeepCopyObject() runtime.Object { func (in *ProviderConfigSpec) DeepCopyInto(out *ProviderConfigSpec) { *out = *in in.Credentials.DeepCopyInto(&out.Credentials) + if in.Identity != nil { + in, out := &in.Identity, &out.Identity + *out = new(Identity) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProviderConfigSpec. diff --git a/apis/v1beta1/types.go b/apis/v1beta1/types.go index 30aa6c5..426e8ff 100644 --- a/apis/v1beta1/types.go +++ b/apis/v1beta1/types.go @@ -24,8 +24,15 @@ import ( // A ProviderConfigSpec defines the desired state of a Provider. type ProviderConfigSpec struct { - // Credentials required to authenticate to this provider. + // Credentials used to connect to the Kubernetes API. Typically a + // kubeconfig file. Use InjectedIdentity for in-cluster config. Credentials ProviderCredentials `json:"credentials"` + + // Identity used to authenticate to the Kubernetes API. The identity + // credentials can be used to supplement kubeconfig 'credentials', for + // example by configuring a bearer token source such as OAuth. + // +optional + Identity *Identity `json:"identity,omitempty"` } // ProviderCredentials required to authenticate. @@ -37,6 +44,23 @@ type ProviderCredentials struct { xpv1.CommonCredentialSelectors `json:",inline"` } +// IdentityType used to authenticate to the Kubernetes API. +type IdentityType string + +// Supported identity types. +const ( + IdentityTypeGoogleApplicationCredentials = "GoogleApplicationCredentials" +) + +// Identity used to authenticate. +type Identity struct { + // Type of identity. + // +kubebuilder:validation:Enum=GoogleApplicationCredentials + Type IdentityType `json:"type"` + + ProviderCredentials `json:",inline"` +} + // A ProviderConfigStatus defines the status of a Provider. type ProviderConfigStatus struct { xpv1.ProviderConfigStatus `json:",inline"` diff --git a/apis/v1beta1/zz_generated.deepcopy.go b/apis/v1beta1/zz_generated.deepcopy.go index e2fec11..2683ff9 100644 --- a/apis/v1beta1/zz_generated.deepcopy.go +++ b/apis/v1beta1/zz_generated.deepcopy.go @@ -24,6 +24,22 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Identity) DeepCopyInto(out *Identity) { + *out = *in + in.ProviderCredentials.DeepCopyInto(&out.ProviderCredentials) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Identity. +func (in *Identity) DeepCopy() *Identity { + if in == nil { + return nil + } + out := new(Identity) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ProviderConfig) DeepCopyInto(out *ProviderConfig) { *out = *in @@ -87,6 +103,11 @@ func (in *ProviderConfigList) DeepCopyObject() runtime.Object { func (in *ProviderConfigSpec) DeepCopyInto(out *ProviderConfigSpec) { *out = *in in.Credentials.DeepCopyInto(&out.Credentials) + if in.Identity != nil { + in, out := &in.Identity, &out.Identity + *out = new(Identity) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProviderConfigSpec. diff --git a/examples/provider-config/provider-config-with-secret.yaml b/examples/provider-config/provider-config-with-secret.yaml index 0e20641..c6a2da4 100644 --- a/examples/provider-config/provider-config-with-secret.yaml +++ b/examples/provider-config/provider-config-with-secret.yaml @@ -1,11 +1,18 @@ apiVersion: helm.crossplane.io/v1beta1 kind: ProviderConfig metadata: - name: helm-provider + name: default spec: credentials: source: Secret secretRef: - name: cluster-config + name: cluster-credentials namespace: crossplane-system key: kubeconfig +# identity: +# type: GoogleApplicationCredentials +# source: Secret +# secretRef: +# name: gcp-credentials +# namespace: crossplane-system +# key: credentials.json diff --git a/go.mod b/go.mod index 61529e9..3f020e9 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/crossplane/crossplane-tools v0.0.0-20210320162312-1baca298c527 github.com/google/go-cmp v0.5.6 github.com/pkg/errors v0.9.1 + golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d gopkg.in/alecthomas/kingpin.v2 v2.2.6 helm.sh/helm/v3 v3.6.3 k8s.io/api v0.21.2 diff --git a/package/crds/helm.crossplane.io_providerconfigs.yaml b/package/crds/helm.crossplane.io_providerconfigs.yaml index 7e93d9d..bfe75b4 100644 --- a/package/crds/helm.crossplane.io_providerconfigs.yaml +++ b/package/crds/helm.crossplane.io_providerconfigs.yaml @@ -48,7 +48,8 @@ spec: description: A ProviderConfigSpec defines the desired state of a Provider. properties: credentials: - description: Credentials required to authenticate to this provider. + description: Credentials used to connect to the Kubernetes API. Typically + a kubeconfig file. Use InjectedIdentity for in-cluster config. properties: env: description: Env is a reference to an environment variable that @@ -100,6 +101,67 @@ spec: required: - source type: object + identity: + description: Identity used to authenticate to the Kubernetes API. + The identity credentials can be used to supplement kubeconfig 'credentials', + for example by configuring a bearer token source such as OAuth. + properties: + env: + description: Env is a reference to an environment variable that + contains credentials that must be used to connect to the provider. + properties: + name: + description: Name is the name of an environment variable. + type: string + required: + - name + type: object + fs: + description: Fs is a reference to a filesystem location that contains + credentials that must be used to connect to the provider. + properties: + path: + description: Path is a filesystem path. + type: string + required: + - path + type: object + secretRef: + description: A SecretRef is a reference to a secret key that contains + the credentials that must be used to connect to the provider. + properties: + key: + description: The key to select. + type: string + name: + description: Name of the secret. + type: string + namespace: + description: Namespace of the secret. + type: string + required: + - key + - name + - namespace + type: object + source: + description: Source of the provider credentials. + enum: + - None + - Secret + - InjectedIdentity + - Environment + - Filesystem + type: string + type: + description: Type of identity. + enum: + - GoogleApplicationCredentials + type: string + required: + - source + - type + type: object required: - credentials type: object @@ -181,7 +243,8 @@ spec: description: A ProviderConfigSpec defines the desired state of a Provider. properties: credentials: - description: Credentials required to authenticate to this provider. + description: Credentials used to connect to the Kubernetes API. Typically + a kubeconfig file. Use InjectedIdentity for in-cluster config. properties: env: description: Env is a reference to an environment variable that @@ -233,6 +296,67 @@ spec: required: - source type: object + identity: + description: Identity used to authenticate to the Kubernetes API. + The identity credentials can be used to supplement kubeconfig 'credentials', + for example by configuring a bearer token source such as OAuth. + properties: + env: + description: Env is a reference to an environment variable that + contains credentials that must be used to connect to the provider. + properties: + name: + description: Name is the name of an environment variable. + type: string + required: + - name + type: object + fs: + description: Fs is a reference to a filesystem location that contains + credentials that must be used to connect to the provider. + properties: + path: + description: Path is a filesystem path. + type: string + required: + - path + type: object + secretRef: + description: A SecretRef is a reference to a secret key that contains + the credentials that must be used to connect to the provider. + properties: + key: + description: The key to select. + type: string + name: + description: Name of the secret. + type: string + namespace: + description: Namespace of the secret. + type: string + required: + - key + - name + - namespace + type: object + source: + description: Source of the provider credentials. + enum: + - None + - Secret + - InjectedIdentity + - Environment + - Filesystem + type: string + type: + description: Type of identity. + enum: + - GoogleApplicationCredentials + type: string + required: + - source + - type + type: object required: - credentials type: object diff --git a/pkg/clients/client.go b/pkg/clients/client.go index 2ac4ad8..ab1e9ad 100644 --- a/pkg/clients/client.go +++ b/pkg/clients/client.go @@ -17,8 +17,6 @@ limitations under the License. package clients import ( - "fmt" - "github.com/pkg/errors" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" @@ -26,8 +24,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" ) -// NewRestConfig returns a rest config given a secret with connection information. -func NewRestConfig(kubeconfig []byte) (*rest.Config, error) { +// NewRESTConfig returns a REST config given a secret with connection information. +func NewRESTConfig(kubeconfig []byte) (*rest.Config, error) { ac, err := clientcmd.Load(kubeconfig) if err != nil { return nil, errors.Wrap(err, "failed to load kubeconfig") @@ -53,11 +51,14 @@ func restConfigFromAPIConfig(c *api.Config) (*rest.Config, error) { ctx := c.Contexts[c.CurrentContext] cluster := c.Clusters[ctx.Cluster] if cluster == nil { - return nil, errors.New(fmt.Sprintf("cluster for currentContext (%s) not found", c.CurrentContext)) + return nil, errors.Errorf("cluster for currentContext (%s) not found", c.CurrentContext) } user := c.AuthInfos[ctx.AuthInfo] if user == nil { - return nil, errors.New(fmt.Sprintf("auth info for currentContext (%s) not found", c.CurrentContext)) + // We don't require a user because it's possible user + // authorization configuration will be loaded from a separate + // set of identity credentials (e.g. Google Application Creds). + user = &api.AuthInfo{} } return &rest.Config{ Host: cluster.Server, diff --git a/pkg/clients/gke/gke.go b/pkg/clients/gke/gke.go new file mode 100644 index 0000000..93c5107 --- /dev/null +++ b/pkg/clients/gke/gke.go @@ -0,0 +1,50 @@ +/* +Copyright 2021 The Crossplane 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 gke contains utilities for authenticating to GKE clusters. +package gke + +import ( + "context" + "net/http" + + "github.com/pkg/errors" + "golang.org/x/oauth2" + "golang.org/x/oauth2/google" + "k8s.io/client-go/rest" +) + +// DefaultScopes for GKE authentication. +var DefaultScopes []string = []string{ + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/userinfo.email", +} + +// WrapRESTConfig configures the supplied REST config to use OAuth2 bearer +// tokens fetched using the supplied Google Application Credentials. +func WrapRESTConfig(ctx context.Context, rc *rest.Config, credentials []byte, scopes ...string) error { + creds, err := google.CredentialsFromJSON(ctx, credentials, scopes...) + if err != nil { + return errors.Wrap(err, "cannot load Google Application Credentials from JSON") + } + + // CredentialsFromJSON creates a TokenSource that handles token caching. + rc.Wrap(func(rt http.RoundTripper) http.RoundTripper { + return &oauth2.Transport{Source: creds.TokenSource, Base: rt} + }) + + return nil +} diff --git a/pkg/controller/release/release.go b/pkg/controller/release/release.go index e829cea..e4caa93 100644 --- a/pkg/controller/release/release.go +++ b/pkg/controller/release/release.go @@ -44,6 +44,7 @@ import ( "github.com/crossplane-contrib/provider-helm/apis/release/v1beta1" helmv1beta1 "github.com/crossplane-contrib/provider-helm/apis/v1beta1" "github.com/crossplane-contrib/provider-helm/pkg/clients" + "github.com/crossplane-contrib/provider-helm/pkg/clients/gke" helmClient "github.com/crossplane-contrib/provider-helm/pkg/clients/helm" ) @@ -62,30 +63,28 @@ const ( ) const ( - errNotRelease = "managed resource is not a Release custom resource" - errProviderConfigNotSet = "provider config is not set" - errProviderNotRetrieved = "provider could not be retrieved" - errCredSecretNotSet = "provider credentials secret is not set" - errNewKubernetesClient = "cannot create new Kubernetes client" - errProviderSecretNotRetrieved = "secret referred in provider could not be retrieved" - errProviderSecretValueForKeyNotFound = "value for key \"%s\" not found in provider credentials secret" - errFailedToGetLastRelease = "failed to get last helm release" - errLastReleaseIsNil = "last helm release is nil" - errFailedToCheckIfUpToDate = "failed to check if release is up to date" - errFailedToInstall = "failed to install release" - errFailedToUpgrade = "failed to upgrade release" - errFailedToUninstall = "failed to uninstall release" - errFailedToGetRepoCreds = "failed to get user name and password from secret reference" - errFailedToComposeValues = "failed to compose values" - errFailedToCreateRestConfig = "cannot create new rest config using provider secret" - errFailedToTrackUsage = "cannot track provider config usage" - errFailedToLoadPatches = "failed to load patches" - errFailedToUpdatePatchSha = "failed to update patch sha" - errFailedToSetName = "failed to update chart spec with the name from URL" - errFailedToSetVersion = "failed to update chart spec with the latest version" - errFailedToCreateNamespace = "failed to create namespace for release" - - errFmtUnsupportedCredSource = "unsupported credentials source %q" + errNotRelease = "managed resource is not a Release custom resource" + errProviderConfigNotSet = "provider config is not set" + errProviderNotRetrieved = "provider could not be retrieved" + errNewKubernetesClient = "cannot create new Kubernetes client" + errFailedToGetLastRelease = "failed to get last helm release" + errLastReleaseIsNil = "last helm release is nil" + errFailedToCheckIfUpToDate = "failed to check if release is up to date" + errFailedToInstall = "failed to install release" + errFailedToUpgrade = "failed to upgrade release" + errFailedToUninstall = "failed to uninstall release" + errFailedToGetRepoCreds = "failed to get user name and password from secret reference" + errFailedToComposeValues = "failed to compose values" + errFailedToExtractKubeconfig = "failed to extract kubeconfig" + errFailedToExtractGoogleCredentials = "failed to extract Google Application Credentials" + errFailedToInjectGoogleCredentials = "failed to wrap REST client with Google Application Credentials" + errFailedToCreateRESTConfig = "cannot create new rest config using provider secret" + errFailedToTrackUsage = "cannot track provider config usage" + errFailedToLoadPatches = "failed to load patches" + errFailedToUpdatePatchSha = "failed to update patch sha" + errFailedToSetName = "failed to update chart spec with the name from URL" + errFailedToSetVersion = "failed to update chart spec with the latest version" + errFailedToCreateNamespace = "failed to create namespace for release" ) // Setup adds a controller that reconciles Release managed resources. @@ -99,7 +98,10 @@ func Setup(mgr ctrl.Manager, l logging.Logger) error { logger: logger, client: mgr.GetClient(), usage: resource.NewProviderConfigUsageTracker(mgr.GetClient(), &helmv1beta1.ProviderConfigUsage{}), - newRestConfigFn: clients.NewRestConfig, + kcfgExtractorFn: resource.CommonCredentialExtractor, + gcpExtractorFn: resource.CommonCredentialExtractor, + gcpInjectorFn: gke.WrapRESTConfig, + newRestConfigFn: clients.NewRESTConfig, newKubeClientFn: clients.NewKubeClient, newHelmClientFn: helmClient.NewClient, }), @@ -116,9 +118,13 @@ func Setup(mgr ctrl.Manager, l logging.Logger) error { } type connector struct { - logger logging.Logger - client client.Client - usage resource.Tracker + logger logging.Logger + client client.Client + usage resource.Tracker + + kcfgExtractorFn func(ctx context.Context, src xpv1.CredentialsSource, c client.Client, ccs xpv1.CommonCredentialSelectors) ([]byte, error) + gcpExtractorFn func(ctx context.Context, src xpv1.CredentialsSource, c client.Client, ccs xpv1.CommonCredentialSelectors) ([]byte, error) + gcpInjectorFn func(ctx context.Context, rc *rest.Config, credentials []byte, scopes ...string) error newRestConfigFn func(kubeconfig []byte) (*rest.Config, error) newKubeClientFn func(config *rest.Config) (client.Client, error) newHelmClientFn func(log logging.Logger, config *rest.Config, namespace string, wait bool, timeout time.Duration) (helmClient.Client, error) @@ -151,34 +157,36 @@ func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.E var rc *rest.Config var err error - s := p.Spec.Credentials.Source - switch s { //nolint:exhaustive + switch pc := p.Spec.Credentials; pc.Source { //nolint:exhaustive case xpv1.CredentialsSourceInjectedIdentity: rc, err = rest.InClusterConfig() if err != nil { - return nil, errors.Wrap(err, errFailedToCreateRestConfig) + return nil, errors.Wrap(err, errFailedToCreateRESTConfig) } - case xpv1.CredentialsSourceSecret: - ref := p.Spec.Credentials.SecretRef - if ref == nil { - return nil, errors.New(errCredSecretNotSet) + default: + kc, err := c.kcfgExtractorFn(ctx, pc.Source, c.client, pc.CommonCredentialSelectors) + if err != nil { + return nil, errors.Wrap(err, errFailedToExtractKubeconfig) } - key := types.NamespacedName{Namespace: ref.Namespace, Name: ref.Name} - d, err := getSecretData(ctx, c.client, key) + rc, err = c.newRestConfigFn(kc) if err != nil { - return nil, errors.Wrap(err, errProviderSecretNotRetrieved) - } - kc, f := d[ref.Key] - if !f { - return nil, errors.Errorf(errProviderSecretValueForKeyNotFound, ref.Key) + return nil, errors.Wrap(err, errFailedToCreateRESTConfig) } - rc, err = c.newRestConfigFn(kc) + } + + // NOTE(negz): We don't currently check the identity type because at the + // time of writing there's only one valid value (Google App Creds), and + // that value is required. + if id := p.Spec.Identity; id != nil { + creds, err := c.gcpExtractorFn(ctx, id.Source, c.client, id.CommonCredentialSelectors) if err != nil { - return nil, errors.Wrap(err, errFailedToCreateRestConfig) + return nil, errors.Wrap(err, errFailedToExtractGoogleCredentials) + } + + if err := c.gcpInjectorFn(ctx, rc, creds, gke.DefaultScopes...); err != nil { + return nil, errors.Wrap(err, errFailedToInjectGoogleCredentials) } - default: - return nil, errors.Errorf(errFmtUnsupportedCredSource, s) } k, err := c.newKubeClientFn(rc) diff --git a/pkg/controller/release/release_test.go b/pkg/controller/release/release_test.go index 96b04a5..9308344 100644 --- a/pkg/controller/release/release_test.go +++ b/pkg/controller/release/release_test.go @@ -2,7 +2,6 @@ package release import ( "context" - "fmt" "testing" "time" @@ -30,13 +29,7 @@ import ( ) const ( - providerName = "helm-test" - providerSecretName = "helm-test-secret" - providerSecretNamespace = "helm-test-secret-namespace" - - providerSecretKey = "kubeconfig" - providerSecretData = "somethingsecret" - + providerName = "helm-test" testReleaseName = "test-release" ) @@ -123,27 +116,22 @@ func Test_connector_Connect(t *testing.T) { ObjectMeta: metav1.ObjectMeta{Name: providerName}, Spec: helmv1beta1.ProviderConfigSpec{ Credentials: helmv1beta1.ProviderCredentials{ - Source: xpv1.CredentialsSourceSecret, - CommonCredentialSelectors: xpv1.CommonCredentialSelectors{ - SecretRef: &xpv1.SecretKeySelector{ - SecretReference: xpv1.SecretReference{ - Name: providerSecretName, - Namespace: providerSecretNamespace, - }, - Key: providerSecretKey, - }, + Source: xpv1.CredentialsSourceNone, + }, + Identity: &helmv1beta1.Identity{ + Type: helmv1beta1.IdentityTypeGoogleApplicationCredentials, + ProviderCredentials: helmv1beta1.ProviderCredentials{ + Source: xpv1.CredentialsSourceNone, }, }, }, } - secret := corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{Namespace: providerSecretNamespace, Name: providerSecretName}, - Data: map[string][]byte{providerSecretKey: []byte(providerSecretData)}, - } - type args struct { client client.Client + kcfgExtractorFn func(ctx context.Context, src xpv1.CredentialsSource, c client.Client, ccs xpv1.CommonCredentialSelectors) ([]byte, error) + gcpExtractorFn func(ctx context.Context, src xpv1.CredentialsSource, c client.Client, ccs xpv1.CommonCredentialSelectors) ([]byte, error) + gcpInjectorFn func(ctx context.Context, rc *rest.Config, credentials []byte, scopes ...string) error newRestConfigFn func(kubeconfig []byte) (*rest.Config, error) newKubeClientFn func(config *rest.Config) (client.Client, error) newHelmClientFn func(log logging.Logger, config *rest.Config, namespace string, wait bool, timeout time.Duration) (helmClient.Client, error) @@ -192,47 +180,52 @@ func Test_connector_Connect(t *testing.T) { err: errors.Wrap(errBoom, errProviderNotRetrieved), }, }, - "UnsupportedCredentialSource": { + "FailedToExtractKubeconfig": { args: args{ client: &test.MockClient{ MockGet: func(ctx context.Context, key client.ObjectKey, obj client.Object) error { if key.Name == providerName { - pc := providerConfig.DeepCopy() - pc.Spec.Credentials.Source = xpv1.CredentialsSource("wat") - *obj.(*helmv1beta1.ProviderConfig) = *pc + *obj.(*helmv1beta1.ProviderConfig) = providerConfig return nil } - return nil + return errBoom }, }, + kcfgExtractorFn: func(ctx context.Context, src xpv1.CredentialsSource, c client.Client, ccs xpv1.CommonCredentialSelectors) ([]byte, error) { + return nil, errBoom + }, usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), mg: helmRelease(), }, want: want{ - err: errors.Errorf(errFmtUnsupportedCredSource, "wat"), + err: errors.Wrap(errBoom, errFailedToExtractKubeconfig), }, }, - "NoSecretRef": { + "FailedToCreateRestConfig": { args: args{ client: &test.MockClient{ MockGet: func(ctx context.Context, key client.ObjectKey, obj client.Object) error { if key.Name == providerName { - pc := providerConfig.DeepCopy() - pc.Spec.Credentials.SecretRef = nil - *obj.(*helmv1beta1.ProviderConfig) = *pc + *obj.(*helmv1beta1.ProviderConfig) = providerConfig return nil } - return nil + return errBoom }, }, + kcfgExtractorFn: func(ctx context.Context, src xpv1.CredentialsSource, c client.Client, ccs xpv1.CommonCredentialSelectors) ([]byte, error) { + return nil, nil + }, + newRestConfigFn: func(kubeconfig []byte) (config *rest.Config, err error) { + return nil, errBoom + }, usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), mg: helmRelease(), }, want: want{ - err: errors.New(errCredSecretNotSet), + err: errors.Wrap(errBoom, errFailedToCreateRESTConfig), }, }, - "FailedToGetProviderSecret": { + "FailedToExtractGoogleCredentials": { args: args{ client: &test.MockClient{ MockGet: func(ctx context.Context, key client.ObjectKey, obj client.Object) error { @@ -240,20 +233,26 @@ func Test_connector_Connect(t *testing.T) { *obj.(*helmv1beta1.ProviderConfig) = providerConfig return nil } - if key.Name == providerSecretName && key.Namespace == providerSecretNamespace { - return errBoom - } return errBoom }, }, + kcfgExtractorFn: func(ctx context.Context, src xpv1.CredentialsSource, c client.Client, ccs xpv1.CommonCredentialSelectors) ([]byte, error) { + return nil, nil + }, + newRestConfigFn: func(kubeconfig []byte) (config *rest.Config, err error) { + return nil, nil + }, + gcpExtractorFn: func(ctx context.Context, src xpv1.CredentialsSource, c client.Client, ccs xpv1.CommonCredentialSelectors) ([]byte, error) { + return nil, errBoom + }, usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), mg: helmRelease(), }, want: want{ - err: errors.Wrap(errors.Wrap(errBoom, fmt.Sprintf(errFailedToGetSecret, providerSecretNamespace)), errProviderSecretNotRetrieved), + err: errors.Wrap(errBoom, errFailedToExtractGoogleCredentials), }, }, - "FailedToCreateRestConfig": { + "FailedToInjectGoogleCredentials": { args: args{ client: &test.MockClient{ MockGet: func(ctx context.Context, key client.ObjectKey, obj client.Object) error { @@ -261,21 +260,26 @@ func Test_connector_Connect(t *testing.T) { *obj.(*helmv1beta1.ProviderConfig) = providerConfig return nil } - if key.Name == providerSecretName && key.Namespace == providerSecretNamespace { - *obj.(*corev1.Secret) = secret - return nil - } return errBoom }, }, + kcfgExtractorFn: func(ctx context.Context, src xpv1.CredentialsSource, c client.Client, ccs xpv1.CommonCredentialSelectors) ([]byte, error) { + return nil, nil + }, newRestConfigFn: func(kubeconfig []byte) (config *rest.Config, err error) { - return nil, errBoom + return nil, nil + }, + gcpExtractorFn: func(ctx context.Context, src xpv1.CredentialsSource, c client.Client, ccs xpv1.CommonCredentialSelectors) ([]byte, error) { + return nil, nil + }, + gcpInjectorFn: func(ctx context.Context, rc *rest.Config, credentials []byte, scopes ...string) error { + return errBoom }, usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), mg: helmRelease(), }, want: want{ - err: errors.Wrap(errBoom, errFailedToCreateRestConfig), + err: errors.Wrap(errBoom, errFailedToInjectGoogleCredentials), }, }, "FailedToCreateNewKubernetesClient": { @@ -286,8 +290,40 @@ func Test_connector_Connect(t *testing.T) { *obj.(*helmv1beta1.ProviderConfig) = providerConfig return nil } - if key.Name == providerSecretName && key.Namespace == providerSecretNamespace { - *obj.(*corev1.Secret) = secret + return errBoom + }, + MockStatusUpdate: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }, + }, + kcfgExtractorFn: func(ctx context.Context, src xpv1.CredentialsSource, c client.Client, ccs xpv1.CommonCredentialSelectors) ([]byte, error) { + return nil, nil + }, + gcpExtractorFn: func(ctx context.Context, src xpv1.CredentialsSource, c client.Client, ccs xpv1.CommonCredentialSelectors) ([]byte, error) { + return nil, nil + }, + gcpInjectorFn: func(ctx context.Context, rc *rest.Config, credentials []byte, scopes ...string) error { + return nil + }, + newRestConfigFn: func(kubeconfig []byte) (config *rest.Config, err error) { + return &rest.Config{}, nil + }, + newKubeClientFn: func(config *rest.Config) (c client.Client, err error) { + return nil, errBoom + }, + usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), + mg: helmRelease(), + }, + want: want{ + err: errors.Wrap(errBoom, errNewKubernetesClient), + }, + }, + "FailedToCreateNewHelmClient": { + args: args{ + client: &test.MockClient{ + MockGet: func(ctx context.Context, key client.ObjectKey, obj client.Object) error { + if key.Name == providerName { + *obj.(*helmv1beta1.ProviderConfig) = providerConfig return nil } return errBoom @@ -296,10 +332,22 @@ func Test_connector_Connect(t *testing.T) { return nil }, }, + kcfgExtractorFn: func(ctx context.Context, src xpv1.CredentialsSource, c client.Client, ccs xpv1.CommonCredentialSelectors) ([]byte, error) { + return nil, nil + }, + gcpExtractorFn: func(ctx context.Context, src xpv1.CredentialsSource, c client.Client, ccs xpv1.CommonCredentialSelectors) ([]byte, error) { + return nil, nil + }, + gcpInjectorFn: func(ctx context.Context, rc *rest.Config, credentials []byte, scopes ...string) error { + return nil + }, newRestConfigFn: func(kubeconfig []byte) (config *rest.Config, err error) { return &rest.Config{}, nil }, newKubeClientFn: func(config *rest.Config) (c client.Client, err error) { + return nil, nil + }, + newHelmClientFn: func(log logging.Logger, config *rest.Config, namespace string, wait bool, timeout time.Duration) (helmClient.Client, error) { return nil, errBoom }, usage: resource.TrackerFn(func(ctx context.Context, mg resource.Managed) error { return nil }), @@ -316,8 +364,6 @@ func Test_connector_Connect(t *testing.T) { switch t := obj.(type) { case *helmv1beta1.ProviderConfig: *t = providerConfig - case *corev1.Secret: - *t = secret default: return errBoom } @@ -327,6 +373,15 @@ func Test_connector_Connect(t *testing.T) { return nil }, }, + kcfgExtractorFn: func(ctx context.Context, src xpv1.CredentialsSource, c client.Client, ccs xpv1.CommonCredentialSelectors) ([]byte, error) { + return nil, nil + }, + gcpExtractorFn: func(ctx context.Context, src xpv1.CredentialsSource, c client.Client, ccs xpv1.CommonCredentialSelectors) ([]byte, error) { + return nil, nil + }, + gcpInjectorFn: func(ctx context.Context, rc *rest.Config, credentials []byte, scopes ...string) error { + return nil + }, newRestConfigFn: func(kubeconfig []byte) (config *rest.Config, err error) { return &rest.Config{}, nil }, @@ -349,6 +404,9 @@ func Test_connector_Connect(t *testing.T) { c := &connector{ logger: logging.NewNopLogger(), client: tc.args.client, + kcfgExtractorFn: tc.args.kcfgExtractorFn, + gcpExtractorFn: tc.args.gcpExtractorFn, + gcpInjectorFn: tc.args.gcpInjectorFn, newRestConfigFn: tc.args.newRestConfigFn, newKubeClientFn: tc.args.newKubeClientFn, newHelmClientFn: tc.args.newHelmClientFn,