| OLD | NEW |
| 1 // Copyright 2016 The LUCI Authors. All rights reserved. | 1 // Copyright 2016 The LUCI Authors. All rights reserved. |
| 2 // Use of this source code is governed under the Apache License, Version 2.0 | 2 // Use of this source code is governed under the Apache License, Version 2.0 |
| 3 // that can be found in the LICENSE file. | 3 // that can be found in the LICENSE file. |
| 4 | 4 |
| 5 package main | 5 package main |
| 6 | 6 |
| 7 import ( | 7 import ( |
| 8 "encoding/json" | 8 "encoding/json" |
| 9 "fmt" | 9 "fmt" |
| 10 "io/ioutil" | 10 "io/ioutil" |
| 11 "net/url" | 11 "net/url" |
| 12 "os" | 12 "os" |
| 13 "os/exec" | 13 "os/exec" |
| 14 "path" | 14 "path" |
| 15 "strconv" |
| 15 "strings" | 16 "strings" |
| 16 "syscall" | 17 "syscall" |
| 17 | 18 |
| 18 "github.com/maruel/subcommands" | 19 "github.com/maruel/subcommands" |
| 19 "golang.org/x/net/context" | 20 "golang.org/x/net/context" |
| 20 | 21 |
| 21 "github.com/luci/luci-go/common/cli" | 22 "github.com/luci/luci-go/common/cli" |
| 23 "github.com/luci/luci-go/common/clock" |
| 22 "github.com/luci/luci-go/common/ctxcmd" | 24 "github.com/luci/luci-go/common/ctxcmd" |
| 23 ) | 25 ) |
| 24 | 26 |
| 25 // BootstrapStepName is the name of kitchen's step where it makes preparations | 27 // BootstrapStepName is the name of kitchen's step where it makes preparations |
| 26 // for running a recipe, e.g. fetches a repository. | 28 // for running a recipe, e.g. fetches a repository. |
| 27 const BootstrapStepName = "recipe bootstrap" | 29 const BootstrapStepName = "recipe bootstrap" |
| 28 | 30 |
| 29 // cmdCook checks out a repository at a revision and runs a recipe. | 31 // cmdCook checks out a repository at a revision and runs a recipe. |
| 30 var cmdCook = &subcommands.Command{ | 32 var cmdCook = &subcommands.Command{ |
| 31 UsageLine: "cook -repository <repository URL> -revision <revision> -reci
pe <recipe>", | 33 UsageLine: "cook -repository <repository URL> -revision <revision> -reci
pe <recipe>", |
| (...skipping 22 matching lines...) Expand all Loading... |
| 54 "The working directory for recipe execution. Defaults to
a temp dir.") | 56 "The working directory for recipe execution. Defaults to
a temp dir.") |
| 55 fs.StringVar(&c.Properties, "properties", "", | 57 fs.StringVar(&c.Properties, "properties", "", |
| 56 "A json string containing the properties. Mutually exclu
sive with -properties-file.") | 58 "A json string containing the properties. Mutually exclu
sive with -properties-file.") |
| 57 fs.StringVar(&c.PropertiesFile, "properties-file", "", | 59 fs.StringVar(&c.PropertiesFile, "properties-file", "", |
| 58 "A file containing a json string of properties. Mutually
exclusive with -properties.") | 60 "A file containing a json string of properties. Mutually
exclusive with -properties.") |
| 59 fs.StringVar( | 61 fs.StringVar( |
| 60 &c.OutputResultJSONFile, | 62 &c.OutputResultJSONFile, |
| 61 "output-result-json", | 63 "output-result-json", |
| 62 "", | 64 "", |
| 63 "The file to write the JSON serialized returned value of
the recipe to") | 65 "The file to write the JSON serialized returned value of
the recipe to") |
| 66 fs.BoolVar( |
| 67 &c.Timestamps, |
| 68 "timestamps", |
| 69 false, |
| 70 "If true, print CURRENT_TIMESTAMP annotations.") |
| 64 return &c | 71 return &c |
| 65 }, | 72 }, |
| 66 } | 73 } |
| 67 | 74 |
| 68 type cookRun struct { | 75 type cookRun struct { |
| 69 subcommands.CommandRunBase | 76 subcommands.CommandRunBase |
| 70 | 77 |
| 71 RepositoryURL string | 78 RepositoryURL string |
| 72 Revision string | 79 Revision string |
| 73 Recipe string | 80 Recipe string |
| 74 CheckoutDir string | 81 CheckoutDir string |
| 75 Workdir string | 82 Workdir string |
| 76 Properties string | 83 Properties string |
| 77 PropertiesFile string | 84 PropertiesFile string |
| 78 OutputResultJSONFile string | 85 OutputResultJSONFile string |
| 86 Timestamps bool |
| 79 } | 87 } |
| 80 | 88 |
| 81 func (c *cookRun) validateFlags() error { | 89 func (c *cookRun) validateFlags() error { |
| 82 // Validate Repository. | 90 // Validate Repository. |
| 83 if c.RepositoryURL == "" { | 91 if c.RepositoryURL == "" { |
| 84 return fmt.Errorf("-repository is required") | 92 return fmt.Errorf("-repository is required") |
| 85 } | 93 } |
| 86 repoURL, err := url.Parse(c.RepositoryURL) | 94 repoURL, err := url.Parse(c.RepositoryURL) |
| 87 if err != nil { | 95 if err != nil { |
| 88 return fmt.Errorf("invalid repository %q: %s", repoURL, err) | 96 return fmt.Errorf("invalid repository %q: %s", repoURL, err) |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 123 c.Workdir = tempWorkdir | 131 c.Workdir = tempWorkdir |
| 124 } | 132 } |
| 125 | 133 |
| 126 recipe := recipeRun{ | 134 recipe := recipeRun{ |
| 127 repositoryPath: c.CheckoutDir, | 135 repositoryPath: c.CheckoutDir, |
| 128 workDir: c.Workdir, | 136 workDir: c.Workdir, |
| 129 recipe: c.Recipe, | 137 recipe: c.Recipe, |
| 130 propertiesJSON: c.Properties, | 138 propertiesJSON: c.Properties, |
| 131 propertiesFile: c.PropertiesFile, | 139 propertiesFile: c.PropertiesFile, |
| 132 outputResultJSONFile: c.OutputResultJSONFile, | 140 outputResultJSONFile: c.OutputResultJSONFile, |
| 141 timestamps: c.Timestamps, |
| 133 } | 142 } |
| 134 recipeCmd, err := recipe.Command() | 143 recipeCmd, err := recipe.Command() |
| 135 if err != nil { | 144 if err != nil { |
| 136 return 0, err | 145 return 0, err |
| 137 } | 146 } |
| 138 | 147 |
| 139 fmt.Printf("Running command %q %q in %q\n", | 148 fmt.Printf("Running command %q %q in %q\n", |
| 140 recipeCmd.Path, recipeCmd.Args, recipeCmd.Dir) | 149 recipeCmd.Path, recipeCmd.Args, recipeCmd.Dir) |
| 141 | 150 |
| 142 recipeCtxCmd := ctxcmd.CtxCmd{Cmd: recipeCmd} | 151 recipeCtxCmd := ctxcmd.CtxCmd{Cmd: recipeCmd} |
| (...skipping 25 matching lines...) Expand all Loading... |
| 168 err = fmt.Errorf("unexpected arguments %v\n", args) | 177 err = fmt.Errorf("unexpected arguments %v\n", args) |
| 169 } else { | 178 } else { |
| 170 err = c.validateFlags() | 179 err = c.validateFlags() |
| 171 } | 180 } |
| 172 if err != nil { | 181 if err != nil { |
| 173 fmt.Fprintln(os.Stderr, err) | 182 fmt.Fprintln(os.Stderr, err) |
| 174 c.Flags.Usage() | 183 c.Flags.Usage() |
| 175 return 1 | 184 return 1 |
| 176 } | 185 } |
| 177 | 186 |
| 187 if c.Timestamps { |
| 188 annotateTime(ctx) |
| 189 } |
| 178 annotate("SEED_STEP", BootstrapStepName) | 190 annotate("SEED_STEP", BootstrapStepName) |
| 179 annotate("STEP_CURSOR", BootstrapStepName) | 191 annotate("STEP_CURSOR", BootstrapStepName) |
| 180 annotate("STEP_STARTED") | 192 annotate("STEP_STARTED") |
| 193 defer func() { |
| 194 annotate("STEP_CURSOR", BootstrapStepName) |
| 195 if c.Timestamps { |
| 196 annotateTime(ctx) |
| 197 } |
| 198 annotate("STEP_CLOSED") |
| 199 if c.Timestamps { |
| 200 annotateTime(ctx) |
| 201 } |
| 202 }() |
| 203 |
| 181 props, err := parseProperties(c.Properties, c.PropertiesFile) | 204 props, err := parseProperties(c.Properties, c.PropertiesFile) |
| 182 if err != nil { | 205 if err != nil { |
| 183 fmt.Fprintln(os.Stderr, err) | 206 fmt.Fprintln(os.Stderr, err) |
| 184 return 1 | 207 return 1 |
| 185 } | 208 } |
| 186 for k, v := range props { | 209 for k, v := range props { |
| 187 // Order is not stable, but that is okay. | 210 // Order is not stable, but that is okay. |
| 188 jv, err := json.Marshal(v) | 211 jv, err := json.Marshal(v) |
| 189 if err != nil { | 212 if err != nil { |
| 190 fmt.Fprintln(os.Stderr, err) | 213 fmt.Fprintln(os.Stderr, err) |
| 191 return 1 | 214 return 1 |
| 192 } | 215 } |
| 193 annotate("SET_BUILD_PROPERTY", k, string(jv)) | 216 annotate("SET_BUILD_PROPERTY", k, string(jv)) |
| 194 } | 217 } |
| 195 | 218 |
| 196 recipeExitCode, err := c.run(ctx) | 219 recipeExitCode, err := c.run(ctx) |
| 197 annotate("STEP_CURSOR", BootstrapStepName) | |
| 198 if err != nil { | 220 if err != nil { |
| 199 if err != context.Canceled { | 221 if err != context.Canceled { |
| 200 fmt.Fprintln(os.Stderr, err) | 222 fmt.Fprintln(os.Stderr, err) |
| 201 } | 223 } |
| 202 return -1 | 224 return -1 |
| 203 } | 225 } |
| 204 annotate("STEP_CLOSED") | |
| 205 return recipeExitCode | 226 return recipeExitCode |
| 206 } | 227 } |
| 207 | 228 |
| 208 func parseProperties(properties, propertiesFile string) (result map[string]inter
face{}, err error) { | 229 func parseProperties(properties, propertiesFile string) (result map[string]inter
face{}, err error) { |
| 209 if properties != "" { | 230 if properties != "" { |
| 210 err = json.Unmarshal([]byte(properties), &result) | 231 err = json.Unmarshal([]byte(properties), &result) |
| 211 if err != nil { | 232 if err != nil { |
| 212 err = fmt.Errorf("could not parse properties %s\n%s", pr
operties, err) | 233 err = fmt.Errorf("could not parse properties %s\n%s", pr
operties, err) |
| 213 } | 234 } |
| 214 return | 235 return |
| 215 } | 236 } |
| 216 if propertiesFile != "" { | 237 if propertiesFile != "" { |
| 217 b, err := ioutil.ReadFile(propertiesFile) | 238 b, err := ioutil.ReadFile(propertiesFile) |
| 218 if err != nil { | 239 if err != nil { |
| 219 err = fmt.Errorf("could not read properties file %s\n%s"
, propertiesFile, err) | 240 err = fmt.Errorf("could not read properties file %s\n%s"
, propertiesFile, err) |
| 220 return nil, err | 241 return nil, err |
| 221 } | 242 } |
| 222 err = json.Unmarshal(b, &result) | 243 err = json.Unmarshal(b, &result) |
| 223 if err != nil { | 244 if err != nil { |
| 224 err = fmt.Errorf("could not parse JSON from file %s\n%s\
n%s", | 245 err = fmt.Errorf("could not parse JSON from file %s\n%s\
n%s", |
| 225 propertiesFile, b, err) | 246 propertiesFile, b, err) |
| 226 } | 247 } |
| 227 } | 248 } |
| 228 return | 249 return |
| 229 } | 250 } |
| 230 | 251 |
| 252 func annotateTime(ctx context.Context) { |
| 253 timestamp := clock.Get(ctx).Now().Unix() |
| 254 annotate("CURRENT_TIMESTAMP", strconv.FormatInt(timestamp, 10)) |
| 255 } |
| 256 |
| 231 func annotate(args ...string) { | 257 func annotate(args ...string) { |
| 232 fmt.Printf("@@@%s@@@\n", strings.Join(args, "@")) | 258 fmt.Printf("@@@%s@@@\n", strings.Join(args, "@")) |
| 233 } | 259 } |
| OLD | NEW |