github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/exec/load_var_step.go (about)

     1  package exec
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"path/filepath"
     9  	"strings"
    10  
    11  	"code.cloudfoundry.org/lager"
    12  	"code.cloudfoundry.org/lager/lagerctx"
    13  	"sigs.k8s.io/yaml"
    14  
    15  	"github.com/concourse/baggageclaim"
    16  	"github.com/pf-qiu/concourse/v6/atc"
    17  	"github.com/pf-qiu/concourse/v6/atc/exec/artifact"
    18  	"github.com/pf-qiu/concourse/v6/atc/exec/build"
    19  	"github.com/pf-qiu/concourse/v6/atc/worker"
    20  	"github.com/pf-qiu/concourse/v6/tracing"
    21  )
    22  
    23  // LoadVarStep loads a value from a file and sets it as a build-local var.
    24  type LoadVarStep struct {
    25  	planID          atc.PlanID
    26  	plan            atc.LoadVarPlan
    27  	metadata        StepMetadata
    28  	delegateFactory BuildStepDelegateFactory
    29  	client          worker.Client
    30  }
    31  
    32  func NewLoadVarStep(
    33  	planID atc.PlanID,
    34  	plan atc.LoadVarPlan,
    35  	metadata StepMetadata,
    36  	delegateFactory BuildStepDelegateFactory,
    37  	client worker.Client,
    38  ) Step {
    39  	return &LoadVarStep{
    40  		planID:          planID,
    41  		plan:            plan,
    42  		metadata:        metadata,
    43  		delegateFactory: delegateFactory,
    44  		client:          client,
    45  	}
    46  }
    47  
    48  type UnspecifiedLoadVarStepFileError struct {
    49  	File string
    50  }
    51  
    52  // Error returns a human-friendly error message.
    53  func (err UnspecifiedLoadVarStepFileError) Error() string {
    54  	return fmt.Sprintf("file '%s' does not specify where the file lives", err.File)
    55  }
    56  
    57  type InvalidLocalVarFile struct {
    58  	File   string
    59  	Format string
    60  	Err    error
    61  }
    62  
    63  func (err InvalidLocalVarFile) Error() string {
    64  	return fmt.Sprintf("failed to parse %s in format %s: %s", err.File, err.Format, err.Err.Error())
    65  }
    66  
    67  func (step *LoadVarStep) Run(ctx context.Context, state RunState) (bool, error) {
    68  	delegate := step.delegateFactory.BuildStepDelegate(state)
    69  	ctx, span := delegate.StartSpan(ctx, "load_var", tracing.Attrs{
    70  		"name": step.plan.Name,
    71  	})
    72  
    73  	ok, err := step.run(ctx, state, delegate)
    74  	tracing.End(span, err)
    75  
    76  	return ok, err
    77  }
    78  
    79  func (step *LoadVarStep) run(ctx context.Context, state RunState, delegate BuildStepDelegate) (bool, error) {
    80  	logger := lagerctx.FromContext(ctx)
    81  	logger = logger.Session("load-var-step", lager.Data{
    82  		"step-name": step.plan.Name,
    83  		"job-id":    step.metadata.JobID,
    84  	})
    85  
    86  	delegate.Initializing(logger)
    87  	stdout := delegate.Stdout()
    88  	stderr := delegate.Stderr()
    89  
    90  	fmt.Fprintln(stderr, "\x1b[1;33mWARNING: the load_var step is experimental and subject to change!\x1b[0m")
    91  	fmt.Fprintln(stderr, "")
    92  	fmt.Fprintln(stderr, "\x1b[33mfollow RFC #27 for updates: https://github.com/concourse/rfcs/pull/27\x1b[0m")
    93  	fmt.Fprintln(stderr, "")
    94  
    95  	delegate.Starting(logger)
    96  
    97  	value, err := step.fetchVars(ctx, logger, step.plan.File, state)
    98  	if err != nil {
    99  		return false, err
   100  	}
   101  	fmt.Fprintf(stdout, "var %s fetched.\n", step.plan.Name)
   102  
   103  	state.AddLocalVar(step.plan.Name, value, !step.plan.Reveal)
   104  	fmt.Fprintf(stdout, "added var %s to build.\n", step.plan.Name)
   105  
   106  	delegate.Finished(logger, true)
   107  
   108  	return true, nil
   109  }
   110  
   111  func (step *LoadVarStep) fetchVars(
   112  	ctx context.Context,
   113  	logger lager.Logger,
   114  	file string,
   115  	state RunState,
   116  ) (interface{}, error) {
   117  
   118  	segs := strings.SplitN(file, "/", 2)
   119  	if len(segs) != 2 {
   120  		return nil, UnspecifiedLoadVarStepFileError{file}
   121  	}
   122  
   123  	artifactName := segs[0]
   124  	filePath := segs[1]
   125  
   126  	format, err := step.fileFormat(file)
   127  	if err != nil {
   128  		return nil, err
   129  	}
   130  	logger.Debug("figure-out-format", lager.Data{"format": format})
   131  
   132  	art, found := state.ArtifactRepository().ArtifactFor(build.ArtifactName(artifactName))
   133  	if !found {
   134  		return nil, UnknownArtifactSourceError{build.ArtifactName(artifactName), filePath}
   135  	}
   136  
   137  	stream, err := step.client.StreamFileFromArtifact(ctx, logger, art, filePath)
   138  	if err != nil {
   139  		if err == baggageclaim.ErrFileNotFound {
   140  			return nil, artifact.FileNotFoundError{
   141  				Name:     artifactName,
   142  				FilePath: filePath,
   143  			}
   144  		}
   145  
   146  		return nil, err
   147  	}
   148  
   149  	fileContent, err := ioutil.ReadAll(stream)
   150  	if err != nil {
   151  		return nil, err
   152  	}
   153  
   154  	var value interface{}
   155  	switch format {
   156  	case "json":
   157  		value = map[string]interface{}{}
   158  		err = json.Unmarshal(fileContent, &value)
   159  		if err != nil {
   160  			return nil, InvalidLocalVarFile{file, "json", err}
   161  		}
   162  	case "yml", "yaml":
   163  		value = map[string]interface{}{}
   164  		err = yaml.Unmarshal(fileContent, &value)
   165  		if err != nil {
   166  			return nil, InvalidLocalVarFile{file, "yaml", err}
   167  		}
   168  	case "trim":
   169  		value = strings.TrimSpace(string(fileContent))
   170  	case "raw":
   171  		value = string(fileContent)
   172  	default:
   173  		return nil, fmt.Errorf("unknown format %s, should never happen, ", format)
   174  	}
   175  
   176  	return value, nil
   177  }
   178  
   179  func (step *LoadVarStep) fileFormat(file string) (string, error) {
   180  	if step.isValidFormat(step.plan.Format) {
   181  		return step.plan.Format, nil
   182  	} else if step.plan.Format != "" {
   183  		return "", fmt.Errorf("invalid format %s", step.plan.Format)
   184  	}
   185  
   186  	fileExt := filepath.Ext(file)
   187  	format := strings.TrimPrefix(fileExt, ".")
   188  	if step.isValidFormat(format) {
   189  		return format, nil
   190  	}
   191  
   192  	return "trim", nil
   193  }
   194  
   195  func (step *LoadVarStep) isValidFormat(format string) bool {
   196  	switch format {
   197  	case "raw", "trim", "yml", "yaml", "json":
   198  		return true
   199  	}
   200  	return false
   201  }