github.com/wmuizelaar/kpt@v0.0.0-20221018115725-bd564717b2ed/commands/alpha/repo/reg/command.go (about)

     1  // Copyright 2022 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package reg
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"strings"
    21  
    22  	"github.com/GoogleContainerTools/kpt/internal/docs/generated/repodocs"
    23  	"github.com/GoogleContainerTools/kpt/internal/errors"
    24  	"github.com/GoogleContainerTools/kpt/internal/util/porch"
    25  	configapi "github.com/GoogleContainerTools/kpt/porch/api/porchconfig/v1alpha1"
    26  	"github.com/spf13/cobra"
    27  	coreapi "k8s.io/api/core/v1"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/cli-runtime/pkg/genericclioptions"
    30  	"sigs.k8s.io/controller-runtime/pkg/client"
    31  )
    32  
    33  const (
    34  	command = "cmdreporeg"
    35  )
    36  
    37  func newRunner(ctx context.Context, rcg *genericclioptions.ConfigFlags) *runner {
    38  	r := &runner{
    39  		ctx: ctx,
    40  		cfg: rcg,
    41  	}
    42  	c := &cobra.Command{
    43  		Use:     "reg REPOSITORY",
    44  		Aliases: []string{"register"},
    45  		Short:   repodocs.RegShort,
    46  		Long:    repodocs.RegShort + "\n" + repodocs.RegLong,
    47  		Example: repodocs.RegExamples,
    48  		PreRunE: r.preRunE,
    49  		RunE:    r.runE,
    50  		Hidden:  porch.HidePorchCommands,
    51  	}
    52  	r.Command = c
    53  
    54  	c.Flags().StringVar(&r.directory, "directory", "/", "Directory within the repository where to look for packages.")
    55  	c.Flags().StringVar(&r.branch, "branch", "main", "Branch in the repository where finalized packages are committed.")
    56  	c.Flags().BoolVar(&r.createBranch, "create-branch", false, "Create the package branch if it doesn't already exist.")
    57  	c.Flags().StringVar(&r.name, "name", "", "Name of the package repository. If unspecified, will use the name portion (last segment) of the repository URL.")
    58  	c.Flags().StringVar(&r.description, "description", "", "Brief description of the package repository.")
    59  	c.Flags().BoolVar(&r.deployment, "deployment", false, "Repository is a deployment repository; packages in a deployment repository are considered deployment-ready.")
    60  	c.Flags().StringVar(&r.username, "repo-basic-username", "", "Username for repository authentication using basic auth.")
    61  	c.Flags().StringVar(&r.password, "repo-basic-password", "", "Password for repository authentication using basic auth.")
    62  	c.Flags().BoolVar(&r.workloadIdentity, "repo-workload-identity", false, "Use workload identity for authentication with the repo")
    63  
    64  	return r
    65  }
    66  
    67  func NewCommand(ctx context.Context, rcg *genericclioptions.ConfigFlags) *cobra.Command {
    68  	return newRunner(ctx, rcg).Command
    69  }
    70  
    71  type runner struct {
    72  	ctx     context.Context
    73  	cfg     *genericclioptions.ConfigFlags
    74  	client  client.Client
    75  	Command *cobra.Command
    76  
    77  	// Flags
    78  	directory        string
    79  	branch           string
    80  	createBranch     bool
    81  	name             string
    82  	description      string
    83  	deployment       bool
    84  	username         string
    85  	password         string
    86  	workloadIdentity bool
    87  }
    88  
    89  func (r *runner) preRunE(cmd *cobra.Command, args []string) error {
    90  	const op errors.Op = command + ".preRunE"
    91  	client, err := porch.CreateClient(r.cfg)
    92  	if err != nil {
    93  		return errors.E(op, err)
    94  	}
    95  	r.client = client
    96  	return nil
    97  }
    98  
    99  func (r *runner) runE(cmd *cobra.Command, args []string) error {
   100  	const op errors.Op = command + ".runE"
   101  
   102  	if len(args) == 0 {
   103  		return errors.E(op, "repository is required positional argument")
   104  	}
   105  
   106  	repository := args[0]
   107  
   108  	var git *configapi.GitRepository
   109  	var oci *configapi.OciRepository
   110  	var rt configapi.RepositoryType
   111  
   112  	if strings.HasPrefix(repository, "oci://") {
   113  		rt = configapi.RepositoryTypeOCI
   114  		oci = &configapi.OciRepository{
   115  			Registry: repository[6:],
   116  		}
   117  		if r.name == "" {
   118  			r.name = porch.LastSegment(repository)
   119  		}
   120  	} else {
   121  		rt = configapi.RepositoryTypeGit
   122  		// TODO: better parsing.
   123  		// t, err := parse.GitParseArgs(r.ctx, []string{repository, "."})
   124  		// if err != nil {
   125  		// 	return errors.E(op, err)
   126  		// }
   127  		git = &configapi.GitRepository{
   128  			Repo:         repository,
   129  			Branch:       r.branch,
   130  			CreateBranch: r.createBranch,
   131  			Directory:    r.directory,
   132  		}
   133  
   134  		if r.name == "" {
   135  			r.name = porch.LastSegment(repository)
   136  		}
   137  	}
   138  
   139  	secret, err := r.buildAuthSecret()
   140  	if err != nil {
   141  		return err
   142  	}
   143  	if secret != nil {
   144  		if err := r.client.Create(r.ctx, secret); err != nil {
   145  			return errors.E(op, err)
   146  		}
   147  
   148  		if git != nil {
   149  			git.SecretRef.Name = secret.Name
   150  		}
   151  		if oci != nil {
   152  			oci.SecretRef.Name = secret.Name
   153  		}
   154  	}
   155  
   156  	if err := r.client.Create(r.ctx, &configapi.Repository{
   157  		TypeMeta: metav1.TypeMeta{
   158  			Kind:       "Repository",
   159  			APIVersion: configapi.GroupVersion.Identifier(),
   160  		},
   161  		ObjectMeta: metav1.ObjectMeta{
   162  			Name:      r.name,
   163  			Namespace: *r.cfg.Namespace,
   164  		},
   165  		Spec: configapi.RepositorySpec{
   166  			Description: r.description,
   167  			Type:        rt,
   168  			Content:     configapi.RepositoryContentPackage,
   169  			Deployment:  r.deployment,
   170  			Git:         git,
   171  			Oci:         oci,
   172  		},
   173  	}); err != nil {
   174  		return errors.E(op, err)
   175  	}
   176  
   177  	return nil
   178  }
   179  
   180  func (r *runner) buildAuthSecret() (*coreapi.Secret, error) {
   181  	var basicAuth bool
   182  	var workloadIdentity bool
   183  
   184  	if r.username != "" || r.password != "" {
   185  		basicAuth = true
   186  	}
   187  
   188  	workloadIdentity = r.workloadIdentity
   189  
   190  	if workloadIdentity && basicAuth {
   191  		return nil, fmt.Errorf("both username/password and workload identity specified")
   192  	}
   193  
   194  	switch {
   195  	case workloadIdentity:
   196  		return &coreapi.Secret{
   197  			TypeMeta: metav1.TypeMeta{
   198  				Kind:       "Secret",
   199  				APIVersion: coreapi.SchemeGroupVersion.Identifier(),
   200  			},
   201  			ObjectMeta: metav1.ObjectMeta{
   202  				Name:      fmt.Sprintf("%s-auth", r.name),
   203  				Namespace: *r.cfg.Namespace,
   204  			},
   205  			Data: map[string][]byte{},
   206  			Type: "kpt.dev/workload-identity-auth",
   207  		}, nil
   208  	case basicAuth:
   209  		return &coreapi.Secret{
   210  			TypeMeta: metav1.TypeMeta{
   211  				Kind:       "Secret",
   212  				APIVersion: coreapi.SchemeGroupVersion.Identifier(),
   213  			},
   214  			ObjectMeta: metav1.ObjectMeta{
   215  				Name:      fmt.Sprintf("%s-auth", r.name),
   216  				Namespace: *r.cfg.Namespace,
   217  			},
   218  			Data: map[string][]byte{
   219  				"username": []byte(r.username),
   220  				"password": []byte(r.password),
   221  			},
   222  			Type: coreapi.SecretTypeBasicAuth,
   223  		}, nil
   224  	}
   225  	return nil, nil
   226  }