github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/fly/commands/internal/flaghelpers/pipeline_flag.go (about) 1 package flaghelpers 2 3 import ( 4 "errors" 5 "fmt" 6 "strings" 7 8 "github.com/pf-qiu/concourse/v6/vars" 9 "github.com/jessevdk/go-flags" 10 "sigs.k8s.io/yaml" 11 12 "github.com/pf-qiu/concourse/v6/atc" 13 "github.com/pf-qiu/concourse/v6/fly/rc" 14 "github.com/pf-qiu/concourse/v6/go-concourse/concourse" 15 ) 16 17 type PipelineFlag struct { 18 Name string 19 InstanceVars atc.InstanceVars 20 } 21 22 func (flag *PipelineFlag) Validate() ([]concourse.ConfigWarning, error) { 23 var warnings []concourse.ConfigWarning 24 if flag != nil { 25 if strings.Contains(flag.Name, "/") { 26 return nil, errors.New("pipeline name cannot contain '/'") 27 } 28 29 if warning := atc.ValidateIdentifier(flag.Name, "pipeline"); warning != nil { 30 warnings = append(warnings, concourse.ConfigWarning{ 31 Type: warning.Type, 32 Message: warning.Message, 33 }) 34 } 35 } 36 return warnings, nil 37 } 38 39 func (flag *PipelineFlag) Ref() atc.PipelineRef { 40 return atc.PipelineRef{Name: flag.Name, InstanceVars: flag.InstanceVars} 41 } 42 43 func (flag *PipelineFlag) UnmarshalFlag(value string) error { 44 if !strings.Contains(value, "/") { 45 flag.Name = value 46 return nil 47 } 48 49 vs := strings.SplitN(value, "/", 2) 50 if len(vs) == 2 { 51 flag.Name = vs[0] 52 var err error 53 flag.InstanceVars, err = unmarshalInstanceVars(vs[1]) 54 if err != nil { 55 return err 56 } 57 } 58 return nil 59 } 60 61 func unmarshalInstanceVars(s string) (atc.InstanceVars, error) { 62 var kvPairs vars.KVPairs 63 for { 64 colonIndex, ok := findUnquoted(s, `"`, nextOccurrenceOf(':')) 65 if !ok { 66 break 67 } 68 rawKey := s[:colonIndex] 69 var kvPair vars.KVPair 70 var err error 71 kvPair.Ref, err = vars.ParseReference(rawKey) 72 if err != nil { 73 return nil, err 74 } 75 76 s = s[colonIndex+1:] 77 rawValue := []byte(s) 78 commaIndex, hasComma := findUnquoted(s, `"'`, nextOccurrenceOfOutsideOfYAML(',')) 79 if hasComma { 80 rawValue = rawValue[:commaIndex] 81 s = s[commaIndex+1:] 82 } 83 84 if err := yaml.Unmarshal(rawValue, &kvPair.Value, useNumber); err != nil { 85 return nil, fmt.Errorf("invalid value for key '%s': %w", rawKey, err) 86 } 87 kvPairs = append(kvPairs, kvPair) 88 89 if !hasComma { 90 break 91 } 92 } 93 if len(kvPairs) == 0 { 94 return nil, errors.New("argument format should be <pipeline>/<key:value>") 95 } 96 97 return atc.InstanceVars(kvPairs.Expand()), nil 98 } 99 100 func findUnquoted(s string, quoteset string, stop func(c rune) bool) (int, bool) { 101 var quoteChar rune 102 for i, c := range s { 103 if quoteChar == 0 { 104 if stop(c) { 105 return i, true 106 } 107 if strings.ContainsRune(quoteset, c) { 108 quoteChar = c 109 } 110 } else if c == quoteChar { 111 quoteChar = 0 112 } 113 } 114 return 0, false 115 } 116 117 func nextOccurrenceOf(r rune) func(rune) bool { 118 return func(c rune) bool { 119 return c == r 120 } 121 } 122 123 func nextOccurrenceOfOutsideOfYAML(r rune) func(rune) bool { 124 braceCount := 0 125 bracketCount := 0 126 return func(c rune) bool { 127 switch c { 128 case r: 129 if braceCount == 0 && bracketCount == 0 { 130 return true 131 } 132 case '{': 133 braceCount++ 134 case '}': 135 braceCount-- 136 case '[': 137 bracketCount++ 138 case ']': 139 bracketCount-- 140 } 141 return false 142 } 143 } 144 145 func (flag *PipelineFlag) Complete(match string) []flags.Completion { 146 fly := parseFlags() 147 148 target, err := rc.LoadTarget(fly.Target, false) 149 if err != nil { 150 return []flags.Completion{} 151 } 152 153 err = target.Validate() 154 if err != nil { 155 return []flags.Completion{} 156 } 157 158 pipelines, err := target.Team().ListPipelines() 159 if err != nil { 160 return []flags.Completion{} 161 } 162 163 comps := []flags.Completion{} 164 for _, pipeline := range pipelines { 165 if strings.HasPrefix(pipeline.Ref().String(), match) { 166 comps = append(comps, flags.Completion{Item: pipeline.Ref().String()}) 167 } 168 } 169 170 return comps 171 }