github.com/yrj2011/jx-test-infra@v0.0.0-20190529031832-7a2065ee98eb/prow/clonerefs/options.go (about) 1 /* 2 Copyright 2018 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package clonerefs 18 19 import ( 20 "bytes" 21 "encoding/json" 22 "errors" 23 "flag" 24 "fmt" 25 "strings" 26 "text/template" 27 28 "k8s.io/apimachinery/pkg/util/sets" 29 "k8s.io/test-infra/prow/kube" 30 ) 31 32 // Options configures the clonerefs tool 33 // completely and may be provided using JSON 34 // or user-specified flags, but not both. 35 type Options struct { 36 // SrcRoot is the root directory under which 37 // all source code is cloned 38 SrcRoot string `json:"src_root"` 39 // Log is the log file to which clone records are written 40 Log string `json:"log"` 41 42 // GitUserName is an optional field that is used with 43 // `git config user.name` 44 GitUserName string `json:"git_user_name,omitempty"` 45 // GitUserEmail is an optional field that is used with 46 // `git config user.email` 47 GitUserEmail string `json:"git_user_email,omitempty"` 48 49 // GitRefs are the refs to clone 50 GitRefs []*kube.Refs `json:"refs"` 51 // KeyFiles are files containing SSH keys to be used 52 // when cloning. Will be added to `ssh-agent`. 53 KeyFiles []string `json:"key_files,omitempty"` 54 55 // MaxParallelWorkers determines how many repositories 56 // can be cloned in parallel. If 0, interpreted as no 57 // limit to parallelism 58 MaxParallelWorkers int `json:"max_parallel_workers,omitempty"` 59 60 // used to hold flag values 61 refs gitRefs 62 clonePath orgRepoFormat 63 cloneURI orgRepoFormat 64 keys stringSlice 65 } 66 67 // Validate ensures that the configuration options are valid 68 func (o *Options) Validate() error { 69 if o.SrcRoot == "" { 70 return errors.New("no source root specified") 71 } 72 73 if o.Log == "" { 74 return errors.New("no log file specified") 75 } 76 77 if len(o.GitRefs) == 0 { 78 return errors.New("no refs specified to clone") 79 } 80 81 seen := map[string]sets.String{} 82 for _, ref := range o.GitRefs { 83 if _, seenOrg := seen[ref.Org]; seenOrg { 84 if seen[ref.Org].Has(ref.Repo) { 85 return errors.New("sync config for %s/%s provided more than once") 86 } 87 seen[ref.Org].Insert(ref.Repo) 88 } else { 89 seen[ref.Org] = sets.NewString(ref.Repo) 90 } 91 } 92 93 return nil 94 } 95 96 const ( 97 // JSONConfigEnvVar is the environment variable that 98 // clonerefs expects to find a full JSON configuration 99 // in when run. 100 JSONConfigEnvVar = "CLONEREFS_OPTIONS" 101 // DefaultGitUserName is the default name used in git config 102 DefaultGitUserName = "ci-robot" 103 // DefaultGitUserEmail is the default email used in git config 104 DefaultGitUserEmail = "ci-robot@k8s.io" 105 ) 106 107 // ConfigVar exposes the environment variable used 108 // to store serialized configuration 109 func (o *Options) ConfigVar() string { 110 return JSONConfigEnvVar 111 } 112 113 // LoadConfig loads options from serialized config 114 func (o *Options) LoadConfig(config string) error { 115 return json.Unmarshal([]byte(config), o) 116 } 117 118 // BindOptions binds flags to options 119 func (o *Options) BindOptions(flags *flag.FlagSet) { 120 BindOptions(o, flags) 121 } 122 123 // Complete internalizes command line arguments 124 func (o *Options) Complete(args []string) { 125 o.GitRefs = o.refs.gitRefs 126 o.KeyFiles = o.keys.data 127 128 for _, ref := range o.GitRefs { 129 alias, err := o.clonePath.Execute(OrgRepo{Org: ref.Org, Repo: ref.Repo}) 130 if err != nil { 131 panic(err) 132 } 133 ref.PathAlias = alias 134 135 alias, err = o.cloneURI.Execute(OrgRepo{Org: ref.Org, Repo: ref.Repo}) 136 if err != nil { 137 panic(err) 138 } 139 ref.CloneURI = alias 140 } 141 } 142 143 // BindOptions adds flags to the FlagSet that populate 144 // the GCS upload options struct given. 145 func BindOptions(options *Options, fs *flag.FlagSet) { 146 fs.StringVar(&options.SrcRoot, "src-root", "", "Where to root source checkouts") 147 fs.StringVar(&options.Log, "log", "", "Where to write logs") 148 fs.StringVar(&options.GitUserName, "git-user-name", DefaultGitUserName, "Username to set in git config") 149 fs.StringVar(&options.GitUserEmail, "git-user-email", DefaultGitUserEmail, "Email to set in git config") 150 fs.Var(&options.refs, "repo", "Mapping of Git URI to refs to check out, can be provided more than once") 151 fs.Var(&options.keys, "ssh-key", "Path to SSH key to enable during cloning, can be provided more than once") 152 fs.Var(&options.clonePath, "clone-alias", "Format string for the path to clone to") 153 fs.Var(&options.cloneURI, "uri-prefix", "Format string for the URI prefix to clone from") 154 fs.IntVar(&options.MaxParallelWorkers, "max-workers", 0, "Maximum number of parallel workers, unset for unlimited.") 155 } 156 157 type gitRefs struct { 158 gitRefs []*kube.Refs 159 } 160 161 func (r *gitRefs) String() string { 162 representation := bytes.Buffer{} 163 for _, ref := range r.gitRefs { 164 fmt.Fprintf(&representation, "%s,%s=%s", ref.Org, ref.Repo, ref.String()) 165 } 166 return representation.String() 167 } 168 169 // Set parses out a kube.Refs from the user string. 170 // The following example shows all possible fields: 171 // org,repo=base-ref:base-sha[,pull-number:pull-sha]... 172 // For the base ref and every pull number, the SHAs 173 // are optional and any number of them may be set or 174 // unset. 175 func (r *gitRefs) Set(value string) error { 176 gitRef, err := ParseRefs(value) 177 if err != nil { 178 return err 179 } 180 r.gitRefs = append(r.gitRefs, gitRef) 181 return nil 182 } 183 184 type stringSlice struct { 185 data []string 186 } 187 188 func (r *stringSlice) String() string { 189 return strings.Join(r.data, ",") 190 } 191 192 // Set records the value passed 193 func (r *stringSlice) Set(value string) error { 194 r.data = append(r.data, value) 195 return nil 196 } 197 198 type orgRepoFormat struct { 199 raw string 200 format *template.Template 201 } 202 203 func (a *orgRepoFormat) String() string { 204 return a.raw 205 } 206 207 // Set parses out overrides from user input 208 func (a *orgRepoFormat) Set(value string) error { 209 templ, err := template.New("format").Parse(value) 210 if err != nil { 211 return err 212 } 213 a.raw = value 214 a.format = templ 215 return nil 216 } 217 218 // OrgRepo hold both an org and repo name. 219 type OrgRepo struct { 220 Org, Repo string 221 } 222 223 func (a *orgRepoFormat) Execute(data OrgRepo) (string, error) { 224 if a.format != nil { 225 output := bytes.Buffer{} 226 err := a.format.Execute(&output, data) 227 return output.String(), err 228 } 229 return "", nil 230 } 231 232 // Encode will encode the set of options in the format that 233 // is expected for the configuration environment variable 234 func Encode(options Options) (string, error) { 235 encoded, err := json.Marshal(options) 236 return string(encoded), err 237 }