1package commands 2 3import ( 4 "fmt" 5 "net/url" 6 "os" 7 "os/signal" 8 "strconv" 9 "syscall" 10 "time" 11 12 "github.com/concourse/concourse/atc" 13 "github.com/concourse/concourse/fly/commands/internal/displayhelpers" 14 "github.com/concourse/concourse/fly/commands/internal/executehelpers" 15 "github.com/concourse/concourse/fly/commands/internal/flaghelpers" 16 "github.com/concourse/concourse/fly/commands/internal/templatehelpers" 17 "github.com/concourse/concourse/fly/config" 18 "github.com/concourse/concourse/fly/eventstream" 19 "github.com/concourse/concourse/fly/rc" 20 "github.com/concourse/concourse/fly/ui" 21 "github.com/concourse/concourse/fly/ui/progress" 22 "github.com/concourse/concourse/go-concourse/concourse" 23 "github.com/vbauerster/mpb/v4" 24) 25 26type ExecuteCommand struct { 27 TaskConfig atc.PathFlag `short:"c" long:"config" required:"true" description:"The task config to execute"` 28 Privileged bool `short:"p" long:"privileged" description:"Run the task with full privileges"` 29 IncludeIgnored bool ` long:"include-ignored" description:"Including .gitignored paths. Disregards .gitignore entries and uploads everything"` 30 Inputs []flaghelpers.InputPairFlag `short:"i" long:"input" value-name:"NAME=PATH" description:"An input to provide to the task (can be specified multiple times)"` 31 InputMappings []flaghelpers.InputMappingPairFlag `short:"m" long:"input-mapping" value-name:"[NAME=STRING]" description:"Map a resource to a different name as task input"` 32 InputsFrom flaghelpers.JobFlag `short:"j" long:"inputs-from" value-name:"PIPELINE/JOB" description:"A job to base the inputs on"` 33 Outputs []flaghelpers.OutputPairFlag `short:"o" long:"output" value-name:"NAME=PATH" description:"An output to fetch from the task (can be specified multiple times)"` 34 Image string `long:"image" description:"Image resource for the one-off build"` 35 Tags []string ` long:"tag" value-name:"TAG" description:"A tag for a specific environment (can be specified multiple times)"` 36 Var []flaghelpers.VariablePairFlag `short:"v" long:"var" value-name:"[NAME=STRING]" unquote:"false" description:"Specify a string value to set for a variable in the pipeline"` 37 YAMLVar []flaghelpers.YAMLVariablePairFlag `short:"y" long:"yaml-var" value-name:"[NAME=YAML]" unquote:"false" description:"Specify a YAML value to set for a variable in the pipeline"` 38 VarsFrom []atc.PathFlag `short:"l" long:"load-vars-from" description:"Variable flag that can be used for filling in template values in configuration from a YAML file"` 39} 40 41func (command *ExecuteCommand) Execute(args []string) error { 42 target, err := rc.LoadTarget(Fly.Target, Fly.Verbose) 43 if err != nil { 44 return err 45 } 46 47 err = target.Validate() 48 if err != nil { 49 return err 50 } 51 52 taskConfig, err := command.CreateTaskConfig(args) 53 if err != nil { 54 return err 55 } 56 57 planFactory := atc.NewPlanFactory(time.Now().Unix()) 58 59 inputs, inputMappings, imageResource, resourceTypes, err := executehelpers.DetermineInputs( 60 planFactory, 61 target.Team(), 62 taskConfig.Inputs, 63 command.Inputs, 64 command.InputMappings, 65 command.Image, 66 command.InputsFrom, 67 command.IncludeIgnored, 68 taskConfig.Platform, 69 command.Tags, 70 ) 71 if err != nil { 72 return err 73 } 74 75 if imageResource != nil { 76 taskConfig.ImageResource = imageResource 77 } 78 79 outputs, err := executehelpers.DetermineOutputs( 80 planFactory, 81 taskConfig.Outputs, 82 command.Outputs, 83 ) 84 if err != nil { 85 return err 86 } 87 88 plan, err := executehelpers.CreateBuildPlan( 89 planFactory, 90 target, 91 command.Privileged, 92 inputs, 93 inputMappings, 94 resourceTypes, 95 outputs, 96 taskConfig, 97 command.Tags, 98 ) 99 100 if err != nil { 101 return err 102 } 103 104 client := target.Client() 105 clientURL, err := url.Parse(client.URL()) 106 if err != nil { 107 return err 108 } 109 110 var build atc.Build 111 var buildURL *url.URL 112 113 if command.InputsFrom.PipelineName != "" { 114 build, err = target.Team().CreatePipelineBuild(command.InputsFrom.PipelineName, plan) 115 if err != nil { 116 return err 117 } 118 } else { 119 build, err = target.Team().CreateBuild(plan) 120 if err != nil { 121 return err 122 } 123 } 124 125 buildURL, err = url.Parse(fmt.Sprintf("/builds/%d", build.ID)) 126 if err != nil { 127 return err 128 } 129 130 fmt.Printf("executing build %d at %s \n", build.ID, clientURL.ResolveReference(buildURL)) 131 132 terminate := make(chan os.Signal, 1) 133 134 go abortOnSignal(client, terminate, build) 135 136 signal.Notify(terminate, syscall.SIGINT, syscall.SIGTERM) 137 138 eventSource, err := client.BuildEvents(strconv.Itoa(build.ID)) 139 if err != nil { 140 return err 141 } 142 143 renderOptions := eventstream.RenderOptions{} 144 145 exitCode := eventstream.Render(os.Stdout, eventSource, renderOptions) 146 eventSource.Close() 147 148 artifactList, err := client.ListBuildArtifacts(strconv.Itoa(build.ID)) 149 if err != nil { 150 return err 151 } 152 153 artifacts := map[string]atc.WorkerArtifact{} 154 155 for _, artifact := range artifactList { 156 artifacts[artifact.Name] = artifact 157 } 158 159 prog := progress.New() 160 161 for _, output := range outputs { 162 name := output.Name 163 path := output.Path 164 165 artifact, ok := artifacts[name] 166 if !ok { 167 continue 168 } 169 170 prog.Go("downloading "+output.Name, func(bar *mpb.Bar) error { 171 return executehelpers.Download(bar, target.Team(), artifact.ID, path) 172 }) 173 } 174 175 err = prog.Wait() 176 if err != nil { 177 displayhelpers.FailWithErrorf("downloading failed: %s", err) 178 return err 179 } 180 181 os.Exit(exitCode) 182 183 return nil 184} 185 186func (command *ExecuteCommand) CreateTaskConfig(args []string) (atc.TaskConfig, error) { 187 188 taskTemplate := templatehelpers.NewYamlTemplateWithParams( 189 command.TaskConfig, 190 command.VarsFrom, 191 command.Var, 192 command.YAMLVar, 193 ) 194 195 taskTemplateEvaluated, err := taskTemplate.Evaluate(false, false) 196 if err != nil { 197 return atc.TaskConfig{}, err 198 } 199 200 return config.OverrideTaskParams(taskTemplateEvaluated, args) 201} 202 203func abortOnSignal( 204 client concourse.Client, 205 terminate <-chan os.Signal, 206 build atc.Build, 207) { 208 <-terminate 209 210 fmt.Fprintf(ui.Stderr, "\naborting...\n") 211 212 err := client.AbortBuild(strconv.Itoa(build.ID)) 213 if err != nil { 214 fmt.Fprintln(ui.Stderr, "failed to abort:", err) 215 os.Exit(2) 216 } 217 218 // if told to terminate again, exit immediately 219 <-terminate 220 fmt.Fprintln(ui.Stderr, "exiting immediately") 221 os.Exit(2) 222} 223