github.com/wmuizelaar/kpt@v0.0.0-20221018115725-bd564717b2ed/commands/alpha/rpkg/update/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 update
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  
    21  	"github.com/GoogleContainerTools/kpt/internal/errors"
    22  	"github.com/GoogleContainerTools/kpt/internal/util/porch"
    23  	porchapi "github.com/GoogleContainerTools/kpt/porch/api/porch/v1alpha1"
    24  	"github.com/spf13/cobra"
    25  	"k8s.io/cli-runtime/pkg/genericclioptions"
    26  	"sigs.k8s.io/controller-runtime/pkg/client"
    27  )
    28  
    29  const (
    30  	command = "cmdrpkgupdate"
    31  
    32  	upstream   = "upstream"
    33  	downstream = "downstream"
    34  )
    35  
    36  func NewCommand(ctx context.Context, rcg *genericclioptions.ConfigFlags) *cobra.Command {
    37  	return newRunner(ctx, rcg).Command
    38  }
    39  
    40  func newRunner(ctx context.Context, rcg *genericclioptions.ConfigFlags) *runner {
    41  	r := &runner{
    42  		ctx: ctx,
    43  		cfg: rcg,
    44  	}
    45  	r.Command = &cobra.Command{
    46  		Use:     "update SOURCE_PACKAGE",
    47  		PreRunE: r.preRunE,
    48  		RunE:    r.runE,
    49  		Hidden:  porch.HidePorchCommands,
    50  	}
    51  	r.Command.Flags().StringVar(&r.revision, "revision", "", "Revision of the upstream package to update to.")
    52  	r.Command.Flags().StringVar(&r.discover, "discover", "",
    53  		`If set, search for available updates instead of performing an update. 
    54  Setting this to 'upstream' will discover upstream updates of downstream packages.
    55  Setting this to 'downstream' will discover downstream package revisions of upstream packages that need to be updated.`)
    56  	return r
    57  }
    58  
    59  type runner struct {
    60  	ctx     context.Context
    61  	cfg     *genericclioptions.ConfigFlags
    62  	client  client.Client
    63  	Command *cobra.Command
    64  
    65  	revision string // Target package revision
    66  	discover string // If set, discover updates rather than do updates
    67  
    68  	// there are multiple places where we need access to all package revisions, so
    69  	// we store it in the runner
    70  	prs []porchapi.PackageRevision
    71  }
    72  
    73  func (r *runner) preRunE(cmd *cobra.Command, args []string) error {
    74  	const op errors.Op = command + ".preRunE"
    75  	c, err := porch.CreateClient(r.cfg)
    76  	if err != nil {
    77  		return errors.E(op, err)
    78  	}
    79  	r.client = c
    80  
    81  	if r.discover == "" {
    82  		if len(args) < 1 {
    83  			return errors.E(op, fmt.Errorf("SOURCE_PACKAGE is a required positional argument"))
    84  		}
    85  		if len(args) > 1 {
    86  			return errors.E(op, fmt.Errorf("too many arguments; SOURCE_PACKAGE is the only accepted positional arguments"))
    87  		}
    88  		// TODO: This should use the latest available revision if one isn't specified.
    89  		if r.revision == "" {
    90  			return errors.E(op, fmt.Errorf("revision is required"))
    91  		}
    92  	} else if r.discover != upstream && r.discover != downstream {
    93  		return errors.E(op, fmt.Errorf("argument for 'discover' must be one of 'upstream' or 'downstream'"))
    94  	}
    95  
    96  	packageRevisionList := porchapi.PackageRevisionList{}
    97  	if err := r.client.List(r.ctx, &packageRevisionList, &client.ListOptions{}); err != nil {
    98  		return errors.E(op, err)
    99  	}
   100  	r.prs = packageRevisionList.Items
   101  
   102  	return nil
   103  }
   104  
   105  func (r *runner) runE(cmd *cobra.Command, args []string) error {
   106  	const op errors.Op = command + ".runE"
   107  
   108  	if r.discover == "" {
   109  		pr := r.findPackageRevision(args[0])
   110  		if pr == nil {
   111  			return errors.E(op, fmt.Errorf("could not find package revision %s", args[0]))
   112  		}
   113  		if err := r.doUpdate(pr); err != nil {
   114  			return errors.E(op, err)
   115  		}
   116  		if _, err := fmt.Fprintf(cmd.OutOrStdout(), "%s updated\n", pr.Name); err != nil {
   117  			return errors.E(op, err)
   118  		}
   119  	} else if err := r.discoverUpdates(cmd, args); err != nil {
   120  		return errors.E(op, err)
   121  	}
   122  	return nil
   123  }
   124  
   125  func (r *runner) doUpdate(pr *porchapi.PackageRevision) error {
   126  	cloneTask := r.findCloneTask(pr)
   127  	if cloneTask == nil {
   128  		return fmt.Errorf("upstream source not found for package rev %q; only cloned packages can be updated", pr.Spec.PackageName)
   129  	}
   130  
   131  	switch cloneTask.Clone.Upstream.Type {
   132  	case porchapi.RepositoryTypeGit:
   133  		cloneTask.Clone.Upstream.Git.Ref = r.revision
   134  	case porchapi.RepositoryTypeOCI:
   135  		return fmt.Errorf("update not implemented for oci packages")
   136  	default:
   137  		upstreamPr := r.findPackageRevision(cloneTask.Clone.Upstream.UpstreamRef.Name)
   138  		if upstreamPr == nil {
   139  			return fmt.Errorf("upstream package revision %s no longer exists", cloneTask.Clone.Upstream.UpstreamRef.Name)
   140  		}
   141  		newUpstreamPr := r.findPackageRevisionForRef(upstreamPr.Spec.PackageName)
   142  		if newUpstreamPr == nil {
   143  			return fmt.Errorf("revision %s does not exist for package %s", r.revision, pr.Spec.PackageName)
   144  		}
   145  		newTask := porchapi.Task{
   146  			Type: porchapi.TaskTypeUpdate,
   147  			Update: &porchapi.PackageUpdateTaskSpec{
   148  				Upstream: cloneTask.Clone.Upstream,
   149  			},
   150  		}
   151  		newTask.Update.Upstream.UpstreamRef.Name = newUpstreamPr.Name
   152  		pr.Spec.Tasks = append(pr.Spec.Tasks, newTask)
   153  	}
   154  
   155  	return r.client.Update(r.ctx, pr)
   156  }
   157  
   158  func (r *runner) findPackageRevision(prName string) *porchapi.PackageRevision {
   159  	for i := range r.prs {
   160  		pr := r.prs[i]
   161  		if pr.Name == prName {
   162  			return &pr
   163  		}
   164  	}
   165  	return nil
   166  }
   167  
   168  func (r *runner) findCloneTask(pr *porchapi.PackageRevision) *porchapi.Task {
   169  	if len(pr.Spec.Tasks) == 0 {
   170  		return nil
   171  	}
   172  	firstTask := pr.Spec.Tasks[0]
   173  	if firstTask.Type == porchapi.TaskTypeClone {
   174  		return &firstTask
   175  	}
   176  	return nil
   177  }
   178  
   179  func (r *runner) findPackageRevisionForRef(name string) *porchapi.PackageRevision {
   180  	for i := range r.prs {
   181  		pr := r.prs[i]
   182  		if pr.Spec.PackageName == name && pr.Spec.Revision == r.revision {
   183  			return &pr
   184  		}
   185  	}
   186  	return nil
   187  }