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 }