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  }