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