From e5fb0d37654c55235f486ff70051897779b47a8f Mon Sep 17 00:00:00 2001 From: Andrew Lavery Date: Thu, 26 Oct 2017 15:50:33 -0700 Subject: [PATCH] Allow setting global timeout on command line Allow setting timeouts within specs if they are lower than the global timeout --- cmd/generate.go | 4 +++- plans/byte-source.go | 13 ++++++++++++- plans/stream-source.go | 13 ++++++++++++- plans/structured-source.go | 13 ++++++++++++- plugins/core/planners/read-command.go | 5 +++++ plugins/core/planners/read-file.go | 5 +++++ plugins/docker/planners/daemon.go | 6 ++++++ plugins/docker/planners/inspect.go | 18 ++++++++++-------- plugins/docker/planners/logs.go | 18 ++++++++++-------- plugins/docker/planners/read-file.go | 18 ++++++++++-------- plugins/docker/planners/run-command.go | 4 ++++ spec/parse.go | 2 +- 12 files changed, 90 insertions(+), 29 deletions(-) diff --git a/cmd/generate.go b/cmd/generate.go index aa252f768..c5136befe 100644 --- a/cmd/generate.go +++ b/cmd/generate.go @@ -38,6 +38,7 @@ var generateCmd = &cobra.Command{ var bundlePath string var skipDefault bool +var timeoutSeconds int func init() { RootCmd.AddCommand(generateCmd) @@ -54,6 +55,7 @@ func init() { generateCmd.Flags().StringVar(&bundlePath, "out", "supportbundle.tar.gz", "Path where the generated bundle should be stored") generateCmd.Flags().BoolVarP(&skipDefault, "skipdefault", "s", false, "If present, skip the default support bundle files") + generateCmd.Flags().IntVar(&timeoutSeconds, "timeout", 60, "The overall support bundle generation timeout") } func generate(cmd *cobra.Command, args []string) error { @@ -96,7 +98,7 @@ func generate(cmd *cobra.Command, args []string) error { var tasks = planner.Plan(specs) - if err := bundle.Generate(tasks, time.Duration(time.Second*15), bundlePath); err != nil { + if err := bundle.Generate(tasks, time.Duration(time.Second*time.Duration(timeoutSeconds)), bundlePath); err != nil { jww.ERROR.Fatal(err) } diff --git a/plans/byte-source.go b/plans/byte-source.go index 06ba16d09..1292b5d40 100644 --- a/plans/byte-source.go +++ b/plans/byte-source.go @@ -3,6 +3,7 @@ package plans import ( "context" "errors" + "time" "github.com/replicatedcom/support-bundle/types" ) @@ -27,6 +28,9 @@ type ByteSource struct { // copy of the structured data. If HumanPath is defined and Parser is not, // it will get a copy of the raw data. HumanPath string + // If Timeout is defined, it will be used rather than the context provided + // to Exec. + Timeout time.Duration } func (task *ByteSource) Exec(ctx context.Context, rootDir string) []*types.Result { @@ -63,7 +67,14 @@ func (task *ByteSource) Exec(ctx context.Context, rootDir string) []*types.Resul return resultsWithErr(err, results) } - data, err := task.Producer(ctx) + useCtx := ctx + if task.Timeout != 0 { + var cancel context.CancelFunc + useCtx, cancel = context.WithTimeout(useCtx, task.Timeout) + defer cancel() + } + + data, err := task.Producer(useCtx) if err != nil { return resultsWithErr(err, results) } diff --git a/plans/stream-source.go b/plans/stream-source.go index 80d94c2fe..6a4f49da6 100644 --- a/plans/stream-source.go +++ b/plans/stream-source.go @@ -5,6 +5,7 @@ import ( "errors" "io" "os" + "time" "github.com/replicatedcom/support-bundle/types" ) @@ -21,6 +22,9 @@ type StreamSource struct { JSONPath string // If HumanPath is defined it will get a copy of the data HumanPath string + // If Timeout is defined, it will be used rather than the context provided + // to Exec. + Timeout time.Duration } func (task *StreamSource) Exec(ctx context.Context, rootDir string) []*types.Result { @@ -52,7 +56,14 @@ func (task *StreamSource) Exec(ctx context.Context, rootDir string) []*types.Res return results } - data, err := task.Producer(ctx) + useCtx := ctx + if task.Timeout != 0 { + var cancel context.CancelFunc + useCtx, cancel = context.WithTimeout(useCtx, task.Timeout) + defer cancel() + } + + data, err := task.Producer(useCtx) if err != nil { return resultsWithErr(err, results) } diff --git a/plans/structured-source.go b/plans/structured-source.go index 1bae94d1e..123cc5993 100644 --- a/plans/structured-source.go +++ b/plans/structured-source.go @@ -3,6 +3,7 @@ package plans import ( "context" "errors" + "time" "github.com/replicatedcom/support-bundle/types" ) @@ -23,6 +24,9 @@ type StructuredSource struct { // HumanPath is defined and a Template is not, it will get the data as // YAML. HumanPath string + // If Timeout is defined, it will be used rather than the context provided + // to Exec. + Timeout time.Duration } func (task *StructuredSource) Exec(ctx context.Context, rootDir string) []*types.Result { @@ -52,7 +56,14 @@ func (task *StructuredSource) Exec(ctx context.Context, rootDir string) []*types return resultsWithErr(err, results) } - data, err := task.Producer(ctx) + useCtx := ctx + if task.Timeout != 0 { + var cancel context.CancelFunc + useCtx, cancel = context.WithTimeout(useCtx, task.Timeout) + defer cancel() + } + + data, err := task.Producer(useCtx) if err != nil { return resultsWithErr(err, results) } diff --git a/plugins/core/planners/read-command.go b/plugins/core/planners/read-command.go index 41a882a92..7d7d9e46a 100644 --- a/plugins/core/planners/read-command.go +++ b/plugins/core/planners/read-command.go @@ -2,6 +2,7 @@ package planners import ( "errors" + "time" "github.com/replicatedcom/support-bundle/plans" "github.com/replicatedcom/support-bundle/plugins/core/producers" @@ -23,5 +24,9 @@ func ReadCommand(spec types.Spec) []types.Task { HumanPath: spec.Human, } + if spec.TimeoutSeconds != 0 { + task.Timeout = time.Duration(spec.TimeoutSeconds) * time.Second + } + return []types.Task{task} } diff --git a/plugins/core/planners/read-file.go b/plugins/core/planners/read-file.go index 7f5d5ac32..3155ec7f1 100644 --- a/plugins/core/planners/read-file.go +++ b/plugins/core/planners/read-file.go @@ -2,6 +2,7 @@ package planners import ( "errors" + "time" "github.com/replicatedcom/support-bundle/plans" "github.com/replicatedcom/support-bundle/plugins/core/producers" @@ -23,5 +24,9 @@ func ReadFile(spec types.Spec) []types.Task { HumanPath: spec.Human, } + if spec.TimeoutSeconds != 0 { + task.Timeout = time.Duration(spec.TimeoutSeconds) * time.Second + } + return []types.Task{task} } diff --git a/plugins/docker/planners/daemon.go b/plugins/docker/planners/daemon.go index 2fcf2fadf..9e7838e5a 100644 --- a/plugins/docker/planners/daemon.go +++ b/plugins/docker/planners/daemon.go @@ -2,6 +2,7 @@ package planners import ( "path/filepath" + "time" "github.com/replicatedcom/support-bundle/plans" "github.com/replicatedcom/support-bundle/types" @@ -32,6 +33,11 @@ func (d *Docker) Daemon(spec types.Spec) []types.Task { HumanPath: maybePath(spec.Human, "docker_ps_all"), } + if spec.TimeoutSeconds != 0 { + info.Timeout = time.Duration(spec.TimeoutSeconds) * time.Second + ps.Timeout = time.Duration(spec.TimeoutSeconds) * time.Second + } + return []types.Task{ info, ps, diff --git a/plugins/docker/planners/inspect.go b/plugins/docker/planners/inspect.go index 991e00cf5..69c472318 100644 --- a/plugins/docker/planners/inspect.go +++ b/plugins/docker/planners/inspect.go @@ -2,6 +2,7 @@ package planners import ( "errors" + "time" "github.com/replicatedcom/support-bundle/plans" "github.com/replicatedcom/support-bundle/types" @@ -15,20 +16,21 @@ func (d *Docker) Inspect(spec types.Spec) []types.Task { return []types.Task{task} } + producer := d.producers.Inspect(spec.Config.ContainerID) + + if spec.Config.ContainerName != "" { + producer = d.producers.InspectName(spec.Config.ContainerName) + } + task := &plans.StructuredSource{ - Producer: d.producers.Inspect(spec.Config.ContainerID), + Producer: producer, RawPath: spec.Raw, JSONPath: spec.JSON, HumanPath: spec.Human, } - if spec.Config.ContainerName != "" { - task = &plans.StructuredSource{ - Producer: d.producers.InspectName(spec.Config.ContainerName), - RawPath: spec.Raw, - JSONPath: spec.JSON, - HumanPath: spec.Human, - } + if spec.TimeoutSeconds != 0 { + task.Timeout = time.Duration(spec.TimeoutSeconds) * time.Second } return []types.Task{task} diff --git a/plugins/docker/planners/logs.go b/plugins/docker/planners/logs.go index aa632e915..d43bb2da7 100644 --- a/plugins/docker/planners/logs.go +++ b/plugins/docker/planners/logs.go @@ -2,6 +2,7 @@ package planners import ( "errors" + "time" "github.com/replicatedcom/support-bundle/plans" "github.com/replicatedcom/support-bundle/types" @@ -15,20 +16,21 @@ func (d *Docker) Logs(spec types.Spec) []types.Task { return []types.Task{task} } + producer := d.producers.Logs(spec.Config.ContainerID) + + if spec.Config.ContainerName != "" { + producer = d.producers.LogsName(spec.Config.ContainerName) + } + task := &plans.StreamSource{ - Producer: d.producers.Logs(spec.Config.ContainerID), + Producer: producer, RawPath: spec.Raw, JSONPath: spec.JSON, HumanPath: spec.Human, } - if spec.Config.ContainerName != "" { - task = &plans.StreamSource{ - Producer: d.producers.LogsName(spec.Config.ContainerName), - RawPath: spec.Raw, - JSONPath: spec.JSON, - HumanPath: spec.Human, - } + if spec.TimeoutSeconds != 0 { + task.Timeout = time.Duration(spec.TimeoutSeconds) * time.Second } return []types.Task{task} diff --git a/plugins/docker/planners/read-file.go b/plugins/docker/planners/read-file.go index afdcb0976..2949aea09 100644 --- a/plugins/docker/planners/read-file.go +++ b/plugins/docker/planners/read-file.go @@ -2,6 +2,7 @@ package planners import ( "errors" + "time" "github.com/replicatedcom/support-bundle/plans" "github.com/replicatedcom/support-bundle/types" @@ -15,20 +16,21 @@ func (d *Docker) ReadFile(spec types.Spec) []types.Task { return []types.Task{task} } + producer := d.producers.ReadFile(spec.Config.ContainerID, spec.Config.FilePath) + + if spec.Config.ContainerName != "" { + producer = d.producers.ReadFileByName(spec.Config.ContainerName, spec.Config.FilePath) + } + task := &plans.StreamSource{ - Producer: d.producers.ReadFile(spec.Config.ContainerID, spec.Config.FilePath), + Producer: producer, RawPath: spec.Raw, JSONPath: spec.JSON, HumanPath: spec.Human, } - if spec.Config.ContainerName != "" { - task = &plans.StreamSource{ - Producer: d.producers.ReadFileByName(spec.Config.ContainerName, spec.Config.FilePath), - RawPath: spec.Raw, - JSONPath: spec.JSON, - HumanPath: spec.Human, - } + if spec.TimeoutSeconds != 0 { + task.Timeout = time.Duration(spec.TimeoutSeconds) * time.Second } return []types.Task{task} diff --git a/plugins/docker/planners/run-command.go b/plugins/docker/planners/run-command.go index 7489f2992..8dff94ac9 100644 --- a/plugins/docker/planners/run-command.go +++ b/plugins/docker/planners/run-command.go @@ -33,6 +33,10 @@ func (d *Docker) RunCommand(spec types.Spec) []types.Task { // } // } + // if spec.TimeoutSeconds != 0 { + // task.Timeout = time.Duration(spec.TimeoutSeconds) * time.Second + // } + err := errors.New("This task type not yet implemented") task := plans.PreparedError(err, spec) diff --git a/spec/parse.go b/spec/parse.go index 970cf28cf..8f4b40d8d 100644 --- a/spec/parse.go +++ b/spec/parse.go @@ -12,7 +12,7 @@ type Doc struct { func Parse(doc []byte) ([]types.Spec, error) { d := &Doc{} - if err := yaml.Unmarshal(doc, d); err != nil { + if err := yaml.UnmarshalStrict(doc, d); err != nil { return nil, errors.Wrap(err, "parse yaml spec") }