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  }