github.com/wmuizelaar/kpt@v0.0.0-20221018115725-bd564717b2ed/commands/alpha/rpkg/push/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 push 16 17 import ( 18 "bytes" 19 "context" 20 "fmt" 21 "io" 22 "io/fs" 23 "os" 24 "path" 25 "path/filepath" 26 27 "github.com/GoogleContainerTools/kpt/internal/docs/generated/rpkgdocs" 28 "github.com/GoogleContainerTools/kpt/internal/errors" 29 "github.com/GoogleContainerTools/kpt/internal/printer" 30 "github.com/GoogleContainerTools/kpt/internal/util/porch" 31 porchapi "github.com/GoogleContainerTools/kpt/porch/api/porch/v1alpha1" 32 "github.com/spf13/cobra" 33 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 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 "sigs.k8s.io/kustomize/kyaml/yaml" 40 ) 41 42 const ( 43 command = "cmdrpkgpush" 44 ) 45 46 func newRunner(ctx context.Context, rcg *genericclioptions.ConfigFlags) *runner { 47 r := &runner{ 48 ctx: ctx, 49 cfg: rcg, 50 } 51 c := &cobra.Command{ 52 Use: "push PACKAGE [DIR]", 53 Aliases: []string{"sink", "write"}, 54 SuggestFor: []string{}, 55 Short: rpkgdocs.PushShort, 56 Long: rpkgdocs.PushShort + "\n" + rpkgdocs.PushLong, 57 Example: rpkgdocs.PushExamples, 58 PreRunE: r.preRunE, 59 RunE: r.runE, 60 Hidden: porch.HidePorchCommands, 61 } 62 r.Command = c 63 return r 64 } 65 66 func NewCommand(ctx context.Context, rcg *genericclioptions.ConfigFlags) *cobra.Command { 67 return newRunner(ctx, rcg).Command 68 } 69 70 type runner struct { 71 ctx context.Context 72 cfg *genericclioptions.ConfigFlags 73 client client.Client 74 Command *cobra.Command 75 printer printer.Printer 76 } 77 78 func (r *runner) preRunE(cmd *cobra.Command, args []string) error { 79 const op errors.Op = command + ".preRunE" 80 config, err := r.cfg.ToRESTConfig() 81 if err != nil { 82 return errors.E(op, err) 83 } 84 85 scheme, err := createScheme() 86 if err != nil { 87 return errors.E(op, err) 88 } 89 90 c, err := client.New(config, client.Options{Scheme: scheme}) 91 if err != nil { 92 return errors.E(op, err) 93 } 94 95 r.client = c 96 r.printer = printer.FromContextOrDie(r.ctx) 97 return nil 98 } 99 100 func (r *runner) runE(cmd *cobra.Command, args []string) error { 101 const op errors.Op = command + ".runE" 102 103 if len(args) == 0 { 104 return errors.E(op, "PACKAGE is a required positional argument") 105 } 106 107 packageName := args[0] 108 var resources map[string]string 109 var err error 110 111 if len(args) > 1 { 112 resources, err = readFromDir(args[1]) 113 } else { 114 resources, err = readFromReader(cmd.InOrStdin()) 115 } 116 if err != nil { 117 return errors.E(op, err) 118 } 119 120 if err := r.client.Update(r.ctx, &porchapi.PackageRevisionResources{ 121 TypeMeta: metav1.TypeMeta{ 122 Kind: "PackageRevisionResources", 123 APIVersion: porchapi.SchemeGroupVersion.Identifier(), 124 }, 125 ObjectMeta: metav1.ObjectMeta{ 126 Name: packageName, 127 Namespace: *r.cfg.Namespace, 128 }, 129 Spec: porchapi.PackageRevisionResourcesSpec{ 130 Resources: resources, 131 }, 132 }); err != nil { 133 return errors.E(op, err) 134 } 135 return nil 136 } 137 138 func readFromDir(dir string) (map[string]string, error) { 139 resources := map[string]string{} 140 if err := filepath.Walk(dir, func(path string, info fs.FileInfo, err error) error { 141 if err != nil { 142 return err 143 } 144 if !info.Mode().IsRegular() { 145 return nil 146 } 147 rel, err := filepath.Rel(dir, path) 148 if err != nil { 149 return err 150 } 151 contents, err := os.ReadFile(path) 152 if err != nil { 153 return err 154 } 155 resources[rel] = string(contents) 156 return nil 157 }); err != nil { 158 return nil, err 159 } 160 return resources, nil 161 } 162 163 func readFromReader(in io.Reader) (map[string]string, error) { 164 rw := &resourceWriter{ 165 resources: map[string]string{}, 166 } 167 168 if err := (kio.Pipeline{ 169 Inputs: []kio.Reader{&kio.ByteReader{ 170 Reader: in, 171 PreserveSeqIndent: true, 172 WrapBareSeqNode: true, 173 }}, 174 Outputs: []kio.Writer{rw}, 175 }.Execute()); err != nil { 176 return nil, err 177 } 178 return rw.resources, nil 179 } 180 181 func createScheme() (*runtime.Scheme, error) { 182 scheme := runtime.NewScheme() 183 184 for _, api := range (runtime.SchemeBuilder{ 185 porchapi.AddToScheme, 186 }) { 187 if err := api(scheme); err != nil { 188 return nil, err 189 } 190 } 191 return scheme, nil 192 } 193 194 type resourceWriter struct { 195 resources map[string]string 196 } 197 198 var _ kio.Writer = &resourceWriter{} 199 200 func (w *resourceWriter) Write(nodes []*yaml.RNode) error { 201 paths := map[string][]*yaml.RNode{} 202 for _, node := range nodes { 203 path := getPath(node) 204 paths[path] = append(paths[path], node) 205 } 206 207 buf := &bytes.Buffer{} 208 for path, nodes := range paths { 209 bw := kio.ByteWriter{ 210 Writer: buf, 211 ClearAnnotations: []string{ 212 kioutil.PathAnnotation, 213 kioutil.IndexAnnotation, 214 }, 215 } 216 if err := bw.Write(nodes); err != nil { 217 return err 218 } 219 w.resources[path] = buf.String() 220 buf.Reset() 221 } 222 return nil 223 } 224 225 func getPath(node *yaml.RNode) string { 226 ann := node.GetAnnotations() 227 if path, ok := ann[kioutil.PathAnnotation]; ok { 228 return path 229 } 230 ns := node.GetNamespace() 231 if ns == "" { 232 ns = "non-namespaced" 233 } 234 name := node.GetName() 235 if name == "" { 236 name = "unnamed" 237 } 238 // TODO: harden for escaping etc. 239 return path.Join(ns, fmt.Sprintf("%s.yaml", name)) 240 }