github.com/wmuizelaar/kpt@v0.0.0-20221018115725-bd564717b2ed/commands/alpha/repo/reg/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 reg 16 17 import ( 18 "context" 19 "fmt" 20 "strings" 21 22 "github.com/GoogleContainerTools/kpt/internal/docs/generated/repodocs" 23 "github.com/GoogleContainerTools/kpt/internal/errors" 24 "github.com/GoogleContainerTools/kpt/internal/util/porch" 25 configapi "github.com/GoogleContainerTools/kpt/porch/api/porchconfig/v1alpha1" 26 "github.com/spf13/cobra" 27 coreapi "k8s.io/api/core/v1" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "k8s.io/cli-runtime/pkg/genericclioptions" 30 "sigs.k8s.io/controller-runtime/pkg/client" 31 ) 32 33 const ( 34 command = "cmdreporeg" 35 ) 36 37 func newRunner(ctx context.Context, rcg *genericclioptions.ConfigFlags) *runner { 38 r := &runner{ 39 ctx: ctx, 40 cfg: rcg, 41 } 42 c := &cobra.Command{ 43 Use: "reg REPOSITORY", 44 Aliases: []string{"register"}, 45 Short: repodocs.RegShort, 46 Long: repodocs.RegShort + "\n" + repodocs.RegLong, 47 Example: repodocs.RegExamples, 48 PreRunE: r.preRunE, 49 RunE: r.runE, 50 Hidden: porch.HidePorchCommands, 51 } 52 r.Command = c 53 54 c.Flags().StringVar(&r.directory, "directory", "/", "Directory within the repository where to look for packages.") 55 c.Flags().StringVar(&r.branch, "branch", "main", "Branch in the repository where finalized packages are committed.") 56 c.Flags().BoolVar(&r.createBranch, "create-branch", false, "Create the package branch if it doesn't already exist.") 57 c.Flags().StringVar(&r.name, "name", "", "Name of the package repository. If unspecified, will use the name portion (last segment) of the repository URL.") 58 c.Flags().StringVar(&r.description, "description", "", "Brief description of the package repository.") 59 c.Flags().BoolVar(&r.deployment, "deployment", false, "Repository is a deployment repository; packages in a deployment repository are considered deployment-ready.") 60 c.Flags().StringVar(&r.username, "repo-basic-username", "", "Username for repository authentication using basic auth.") 61 c.Flags().StringVar(&r.password, "repo-basic-password", "", "Password for repository authentication using basic auth.") 62 c.Flags().BoolVar(&r.workloadIdentity, "repo-workload-identity", false, "Use workload identity for authentication with the repo") 63 64 return r 65 } 66 67 func NewCommand(ctx context.Context, rcg *genericclioptions.ConfigFlags) *cobra.Command { 68 return newRunner(ctx, rcg).Command 69 } 70 71 type runner struct { 72 ctx context.Context 73 cfg *genericclioptions.ConfigFlags 74 client client.Client 75 Command *cobra.Command 76 77 // Flags 78 directory string 79 branch string 80 createBranch bool 81 name string 82 description string 83 deployment bool 84 username string 85 password string 86 workloadIdentity bool 87 } 88 89 func (r *runner) preRunE(cmd *cobra.Command, args []string) error { 90 const op errors.Op = command + ".preRunE" 91 client, err := porch.CreateClient(r.cfg) 92 if err != nil { 93 return errors.E(op, err) 94 } 95 r.client = client 96 return nil 97 } 98 99 func (r *runner) runE(cmd *cobra.Command, args []string) error { 100 const op errors.Op = command + ".runE" 101 102 if len(args) == 0 { 103 return errors.E(op, "repository is required positional argument") 104 } 105 106 repository := args[0] 107 108 var git *configapi.GitRepository 109 var oci *configapi.OciRepository 110 var rt configapi.RepositoryType 111 112 if strings.HasPrefix(repository, "oci://") { 113 rt = configapi.RepositoryTypeOCI 114 oci = &configapi.OciRepository{ 115 Registry: repository[6:], 116 } 117 if r.name == "" { 118 r.name = porch.LastSegment(repository) 119 } 120 } else { 121 rt = configapi.RepositoryTypeGit 122 // TODO: better parsing. 123 // t, err := parse.GitParseArgs(r.ctx, []string{repository, "."}) 124 // if err != nil { 125 // return errors.E(op, err) 126 // } 127 git = &configapi.GitRepository{ 128 Repo: repository, 129 Branch: r.branch, 130 CreateBranch: r.createBranch, 131 Directory: r.directory, 132 } 133 134 if r.name == "" { 135 r.name = porch.LastSegment(repository) 136 } 137 } 138 139 secret, err := r.buildAuthSecret() 140 if err != nil { 141 return err 142 } 143 if secret != nil { 144 if err := r.client.Create(r.ctx, secret); err != nil { 145 return errors.E(op, err) 146 } 147 148 if git != nil { 149 git.SecretRef.Name = secret.Name 150 } 151 if oci != nil { 152 oci.SecretRef.Name = secret.Name 153 } 154 } 155 156 if err := r.client.Create(r.ctx, &configapi.Repository{ 157 TypeMeta: metav1.TypeMeta{ 158 Kind: "Repository", 159 APIVersion: configapi.GroupVersion.Identifier(), 160 }, 161 ObjectMeta: metav1.ObjectMeta{ 162 Name: r.name, 163 Namespace: *r.cfg.Namespace, 164 }, 165 Spec: configapi.RepositorySpec{ 166 Description: r.description, 167 Type: rt, 168 Content: configapi.RepositoryContentPackage, 169 Deployment: r.deployment, 170 Git: git, 171 Oci: oci, 172 }, 173 }); err != nil { 174 return errors.E(op, err) 175 } 176 177 return nil 178 } 179 180 func (r *runner) buildAuthSecret() (*coreapi.Secret, error) { 181 var basicAuth bool 182 var workloadIdentity bool 183 184 if r.username != "" || r.password != "" { 185 basicAuth = true 186 } 187 188 workloadIdentity = r.workloadIdentity 189 190 if workloadIdentity && basicAuth { 191 return nil, fmt.Errorf("both username/password and workload identity specified") 192 } 193 194 switch { 195 case workloadIdentity: 196 return &coreapi.Secret{ 197 TypeMeta: metav1.TypeMeta{ 198 Kind: "Secret", 199 APIVersion: coreapi.SchemeGroupVersion.Identifier(), 200 }, 201 ObjectMeta: metav1.ObjectMeta{ 202 Name: fmt.Sprintf("%s-auth", r.name), 203 Namespace: *r.cfg.Namespace, 204 }, 205 Data: map[string][]byte{}, 206 Type: "kpt.dev/workload-identity-auth", 207 }, nil 208 case basicAuth: 209 return &coreapi.Secret{ 210 TypeMeta: metav1.TypeMeta{ 211 Kind: "Secret", 212 APIVersion: coreapi.SchemeGroupVersion.Identifier(), 213 }, 214 ObjectMeta: metav1.ObjectMeta{ 215 Name: fmt.Sprintf("%s-auth", r.name), 216 Namespace: *r.cfg.Namespace, 217 }, 218 Data: map[string][]byte{ 219 "username": []byte(r.username), 220 "password": []byte(r.password), 221 }, 222 Type: coreapi.SecretTypeBasicAuth, 223 }, nil 224 } 225 return nil, nil 226 }