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 }