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