diff --git a/cli/pkg/kubectl/bind-apiservice/plugin/binder.go b/cli/pkg/kubectl/bind-apiservice/plugin/binder.go index c903b4391..93864ab43 100644 --- a/cli/pkg/kubectl/bind-apiservice/plugin/binder.go +++ b/cli/pkg/kubectl/bind-apiservice/plugin/binder.go @@ -65,10 +65,6 @@ func NewBinder(config *rest.Config, opts *BinderOptions) *Binder { } } -// TODO: bindFromFile and bindFromResponse can likely share a lot of code. This slow is bit repetitive -// but keeps the two paths separate for clarity. But it needs love. -// https://github.com/kube-bind/kube-bind/issues/360 - func (b *Binder) BindFromFile(ctx context.Context) ([]*kubebindv1alpha2.APIServiceBinding, error) { // Generate the kubectl command that would be equivalent remoteFlags := "" @@ -81,54 +77,19 @@ func (b *Binder) BindFromFile(ctx context.Context) ([]*kubebindv1alpha2.APIServi fmt.Fprintf(b.opts.IOStreams.ErrOut, "✨ Use \"-o yaml\" and \"--dry-run\" to get the APIServiceExportRequest.\n") fmt.Fprintf(b.opts.IOStreams.ErrOut, " and pass it to \"kubectl bind apiservice\" directly. Great for automation.\n") - // Ensure client side namespace exists - err := b.ensureClientSideNamespaceExists(ctx) - if err != nil { - return nil, fmt.Errorf("failed to ensure kube-bind namespace exists: %w", err) - } - remoteKubeconfig, _, _, err := b.getRemoteKubeconfig(ctx, "", "") if err != nil { return nil, err } - // Copy kubeconfig into local cluster - remoteHost, remoteNamespace, err := base.ParseRemoteKubeconfig([]byte(remoteKubeconfig)) + remoteKubeconfigResolved, remoteNamespaceActual, remoteConfig, err := b.setupEnvironment(ctx, []byte(remoteKubeconfig)) if err != nil { return nil, err } - - kubeClient, err := kubeclient.NewForConfig(b.config) - if err != nil { - return nil, fmt.Errorf("failed to create kube client: %w", err) - } - - secretName, err := base.FindRemoteKubeconfig(ctx, kubeClient, remoteNamespace, remoteHost) - if err != nil { - return nil, err - } - - secret, created, err := base.EnsureKubeconfigSecret(ctx, remoteKubeconfig, secretName, kubeClient) - if err != nil { - return nil, err - } - - if created { - fmt.Fprintf(b.opts.IOStreams.ErrOut, "🔒 Created secret %s/%s for host %s, namespace %s\n", "kube-bind", secret.Name, remoteHost, remoteNamespace) - } else { - fmt.Fprintf(b.opts.IOStreams.ErrOut, "🔒 Updated secret %s/%s for host %s, namespace %s\n", "kube-bind", secret.Name, remoteHost, remoteNamespace) - } - if b.opts.DryRun { return nil, nil } - // Get remote kubeconfig - remoteKubeconfig, remoteNamespaceActual, remoteConfig, err := b.getRemoteKubeconfig(ctx, secret.Namespace, secret.Name) - if err != nil { - return nil, fmt.Errorf("failed to get remote kubeconfig: %w", err) - } - data, err := b.getRequestManifest() if err != nil { return nil, fmt.Errorf("failed to get request manifest: %w", err) @@ -139,61 +100,68 @@ func (b *Binder) BindFromFile(ctx context.Context) ([]*kubebindv1alpha2.APIServi return nil, fmt.Errorf("failed to unmarshal request manifest: %w", err) } - // Deploy konnector if needed - if err := b.deployKonnector(ctx); err != nil { - return nil, fmt.Errorf("failed to deploy konnector: %w", err) + return b.processRequests(ctx, []*kubebindv1alpha2.APIServiceExportRequest{request}, remoteConfig, remoteNamespaceActual, remoteKubeconfigResolved) +} +// BindFromResponse processes a BindingResourceResponse and creates all necessary bindings +func (b *Binder) BindFromResponse(ctx context.Context, response *kubebindv1alpha2.BindingResourceResponse) ([]*kubebindv1alpha2.APIServiceBinding, error) { + if response == nil || response.Authentication.OAuth2CodeGrant == nil { + return nil, fmt.Errorf("unexpected response: authentication.oauth2CodeGrant is nil") } - // Create bindings for all requests - result, err := b.createServiceExportRequest(ctx, remoteConfig, remoteNamespaceActual, request) + remoteKubeconfigResolved, remoteNamespaceActual, remoteConfig, err := b.setupEnvironment(ctx, response.Kubeconfig) if err != nil { - return nil, fmt.Errorf("failed to create service export request: %w", err) + return nil, err } - - secretName, err = b.createKubeconfigSecret(ctx, remoteConfig.Host, remoteNamespaceActual, remoteKubeconfig) - if err != nil { - return nil, fmt.Errorf("failed to create kubeconfig secret: %w", err) + if b.opts.DryRun { + return nil, nil } - results, err := b.createAPIServiceBindings(ctx, result, secretName) - if err != nil { - return nil, fmt.Errorf("failed to create API service bindings: %w", err) + // Extract the requests + apiRequests := make([]*kubebindv1alpha2.APIServiceExportRequest, len(response.Requests)) + for i, request := range response.Requests { + var meta metav1.TypeMeta + if err := json.Unmarshal(request.Raw, &meta); err != nil { + return nil, fmt.Errorf("unexpected response: failed to unmarshal request #%d: %v", i, err) + } + if got, expected := meta.APIVersion, kubebindv1alpha2.SchemeGroupVersion.String(); got != expected { + return nil, fmt.Errorf("unexpected response: request #%d is not %s, got %s", i, expected, got) + } + var apiRequest kubebindv1alpha2.APIServiceExportRequest + if err := json.Unmarshal(request.Raw, &apiRequest); err != nil { + return nil, fmt.Errorf("failed to unmarshal api request #%d: %v", i+1, err) + } + apiRequests[i] = &apiRequest } - return results, nil + return b.processRequests(ctx, apiRequests, remoteConfig, remoteNamespaceActual, remoteKubeconfigResolved) } -// BindFromResponse processes a BindingResourceResponse and creates all necessary bindings -func (b *Binder) BindFromResponse(ctx context.Context, response *kubebindv1alpha2.BindingResourceResponse) ([]*kubebindv1alpha2.APIServiceBinding, error) { - if response == nil || response.Authentication.OAuth2CodeGrant == nil { - return nil, fmt.Errorf("unexpected response: authentication.oauth2CodeGrant is nil") - } - +func (b *Binder) setupEnvironment(ctx context.Context, remoteKubeconfigBytes []byte) (string, string, *rest.Config, error) { // Ensure client side namespace exists err := b.ensureClientSideNamespaceExists(ctx) if err != nil { - return nil, fmt.Errorf("failed to ensure kube-bind namespace exists: %w", err) + return "", "", nil, fmt.Errorf("failed to ensure kube-bind namespace exists: %w", err) } // Copy kubeconfig into local cluster - remoteHost, remoteNamespace, err := base.ParseRemoteKubeconfig(response.Kubeconfig) + remoteHost, remoteNamespace, err := base.ParseRemoteKubeconfig(remoteKubeconfigBytes) if err != nil { - return nil, err + return "", "", nil, err } kubeClient, err := kubeclient.NewForConfig(b.config) if err != nil { - return nil, fmt.Errorf("failed to create kube client: %w", err) + return "", "", nil, fmt.Errorf("failed to create kube client: %w", err) } secretName, err := base.FindRemoteKubeconfig(ctx, kubeClient, remoteNamespace, remoteHost) if err != nil { - return nil, err + return "", "", nil, err } - secret, created, err := base.EnsureKubeconfigSecret(ctx, string(response.Kubeconfig), secretName, kubeClient) + secret, created, err := base.EnsureKubeconfigSecret(ctx, string(remoteKubeconfigBytes), secretName, kubeClient) if err != nil { - return nil, err + return "", "", nil, err } if created { @@ -203,32 +171,19 @@ func (b *Binder) BindFromResponse(ctx context.Context, response *kubebindv1alpha } if b.opts.DryRun { - return nil, nil + return "", "", nil, nil } // Get remote kubeconfig remoteKubeconfig, remoteNamespaceActual, remoteConfig, err := b.getRemoteKubeconfig(ctx, secret.Namespace, secret.Name) if err != nil { - return nil, fmt.Errorf("failed to get remote kubeconfig: %w", err) + return "", "", nil, fmt.Errorf("failed to get remote kubeconfig: %w", err) } - // Extract the requests - apiRequests := make([]*kubebindv1alpha2.APIServiceExportRequest, len(response.Requests)) - for i, request := range response.Requests { - var meta metav1.TypeMeta - if err := json.Unmarshal(request.Raw, &meta); err != nil { - return nil, fmt.Errorf("unexpected response: failed to unmarshal request #%d: %v", i, err) - } - if got, expected := meta.APIVersion, kubebindv1alpha2.SchemeGroupVersion.String(); got != expected { - return nil, fmt.Errorf("unexpected response: request #%d is not %s, got %s", i, expected, got) - } - var apiRequest kubebindv1alpha2.APIServiceExportRequest - if err := json.Unmarshal(request.Raw, &apiRequest); err != nil { - return nil, fmt.Errorf("failed to unmarshal api request #%d: %v", i+1, err) - } - apiRequests[i] = &apiRequest - } + return remoteKubeconfig, remoteNamespaceActual, remoteConfig, nil +} +func (b *Binder) processRequests(ctx context.Context, apiRequests []*kubebindv1alpha2.APIServiceExportRequest, remoteConfig *rest.Config, remoteNamespaceActual string, remoteKubeconfig string) ([]*kubebindv1alpha2.APIServiceBinding, error) { // Deploy konnector if needed if err := b.deployKonnector(ctx); err != nil { return nil, fmt.Errorf("failed to deploy konnector: %w", err)