github.com/SamarSidharth/kpt@v0.0.0-20231122062228-c7d747ae3ace/commands/alpha/rpkg/pull/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 pull 16 17 import ( 18 "context" 19 "io" 20 "os" 21 "path/filepath" 22 "sort" 23 "strings" 24 25 "github.com/GoogleContainerTools/kpt/commands/alpha/rpkg/util" 26 "github.com/GoogleContainerTools/kpt/internal/docs/generated/rpkgdocs" 27 "github.com/GoogleContainerTools/kpt/internal/errors" 28 "github.com/GoogleContainerTools/kpt/internal/util/cmdutil" 29 "github.com/GoogleContainerTools/kpt/internal/util/porch" 30 kptfilev1 "github.com/GoogleContainerTools/kpt/pkg/api/kptfile/v1" 31 "github.com/GoogleContainerTools/kpt/pkg/printer" 32 porchapi "github.com/GoogleContainerTools/kpt/porch/api/porch/v1alpha1" 33 "github.com/spf13/cobra" 34 "k8s.io/apimachinery/pkg/runtime" 35 "k8s.io/cli-runtime/pkg/genericclioptions" 36 "sigs.k8s.io/controller-runtime/pkg/client" 37 "sigs.k8s.io/kustomize/kyaml/kio" 38 "sigs.k8s.io/kustomize/kyaml/kio/kioutil" 39 ) 40 41 const ( 42 command = "cmdrpkgpull" 43 ) 44 45 func newRunner(ctx context.Context, rcg *genericclioptions.ConfigFlags) *runner { 46 r := &runner{ 47 ctx: ctx, 48 cfg: rcg, 49 } 50 c := &cobra.Command{ 51 Use: "pull PACKAGE [DIR]", 52 Aliases: []string{"source", "read"}, 53 SuggestFor: []string{}, 54 Short: rpkgdocs.PullShort, 55 Long: rpkgdocs.PullShort + "\n" + rpkgdocs.PullLong, 56 Example: rpkgdocs.PullExamples, 57 PreRunE: r.preRunE, 58 RunE: r.runE, 59 Hidden: porch.HidePorchCommands, 60 } 61 r.Command = c 62 return r 63 } 64 65 func NewCommand(ctx context.Context, rcg *genericclioptions.ConfigFlags) *cobra.Command { 66 return newRunner(ctx, rcg).Command 67 } 68 69 type runner struct { 70 ctx context.Context 71 cfg *genericclioptions.ConfigFlags 72 client client.Client 73 Command *cobra.Command 74 printer printer.Printer 75 } 76 77 func (r *runner) preRunE(_ *cobra.Command, _ []string) error { 78 const op errors.Op = command + ".preRunE" 79 config, err := r.cfg.ToRESTConfig() 80 if err != nil { 81 return errors.E(op, err) 82 } 83 84 scheme, err := createScheme() 85 if err != nil { 86 return errors.E(op, err) 87 } 88 89 c, err := client.New(config, client.Options{Scheme: scheme}) 90 if err != nil { 91 return errors.E(op, err) 92 } 93 94 r.client = c 95 r.printer = printer.FromContextOrDie(r.ctx) 96 return nil 97 } 98 99 func (r *runner) runE(_ *cobra.Command, args []string) error { 100 const op errors.Op = command + ".runE" 101 102 if len(args) == 0 { 103 return errors.E(op, "PACKAGE is a required positional argument") 104 } 105 106 packageName := args[0] 107 108 var resources porchapi.PackageRevisionResources 109 if err := r.client.Get(r.ctx, client.ObjectKey{ 110 Namespace: *r.cfg.Namespace, 111 Name: packageName, 112 }, &resources); err != nil { 113 return errors.E(op, err) 114 } 115 116 if err := util.AddRevisionMetadata(&resources); err != nil { 117 return errors.E(op, err) 118 } 119 120 if len(args) > 1 { 121 if err := writeToDir(resources.Spec.Resources, args[1]); err != nil { 122 return errors.E(op, err) 123 } 124 } else { 125 if err := writeToWriter(resources.Spec.Resources, r.printer.OutStream()); err != nil { 126 return errors.E(op, err) 127 } 128 } 129 return nil 130 } 131 132 func writeToDir(resources map[string]string, dir string) error { 133 if err := cmdutil.CheckDirectoryNotPresent(dir); err != nil { 134 return err 135 } 136 if err := os.MkdirAll(dir, 0755); err != nil { 137 return err 138 } 139 140 for k, v := range resources { 141 f := filepath.Join(dir, k) 142 d := filepath.Dir(f) 143 if err := os.MkdirAll(d, 0755); err != nil { 144 return err 145 } 146 if err := os.WriteFile(f, []byte(v), 0644); err != nil { 147 return err 148 } 149 } 150 return nil 151 } 152 153 func writeToWriter(resources map[string]string, out io.Writer) error { 154 keys := make([]string, 0, len(resources)) 155 for k := range resources { 156 if !includeFile(k) { 157 continue 158 } 159 keys = append(keys, k) 160 } 161 sort.Strings(keys) 162 163 // Create kio readers 164 inputs := []kio.Reader{} 165 for _, k := range keys { 166 v := resources[k] 167 inputs = append(inputs, &kio.ByteReader{ 168 Reader: strings.NewReader(v), 169 SetAnnotations: map[string]string{ 170 kioutil.PathAnnotation: k, 171 }, 172 DisableUnwrapping: true, 173 }) 174 } 175 176 return kio.Pipeline{ 177 Inputs: inputs, 178 Outputs: []kio.Writer{ 179 kio.ByteWriter{ 180 Writer: out, 181 KeepReaderAnnotations: true, 182 WrappingKind: kio.ResourceListKind, 183 WrappingAPIVersion: kio.ResourceListAPIVersion, 184 Sort: true, 185 }, 186 }, 187 }.Execute() 188 } 189 190 func createScheme() (*runtime.Scheme, error) { 191 scheme := runtime.NewScheme() 192 193 for _, api := range (runtime.SchemeBuilder{ 194 porchapi.AddToScheme, 195 }) { 196 if err := api(scheme); err != nil { 197 return nil, err 198 } 199 } 200 return scheme, nil 201 } 202 203 var matchResourceContents = append(kio.MatchAll, kptfilev1.KptFileName, kptfilev1.RevisionMetaDataFileName) 204 205 func includeFile(path string) bool { 206 for _, m := range matchResourceContents { 207 // Only use the filename for the check for whether we should 208 // include the file. 209 f := filepath.Base(path) 210 if matched, err := filepath.Match(m, f); err == nil && matched { 211 return true 212 } 213 } 214 return false 215 }