internal/run/run_batch.go (75 lines of code) (raw):

// Copyright 2024 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package run import ( "bytes" "context" "errors" "fmt" "strings" "text/template" "github.com/GoogleCloudPlatform/galog" ) var ( // ErrCommandTemplate is the error returned when a CommandSpec's Command // template is boggus. ErrCommandTemplate = errors.New("invalid command format template") // ErrTemplateError is the error returned when a CommandSpec's Error template // is boggus. ErrTemplateError = errors.New("invalid error format template") ) // CommandSpec defines a Command template and an Error template. The data // structure to be used with the templates is up to the user to define. type CommandSpec struct { // Command is the command template i.e: "echo '{{.MyDataString}}'". Command string // Error is the error template, if the command fails this template is // used to build the error message, i.e: "failed to parse file {{.FileName}}". Error string } // CommandSet is set of commands to be executed together, IOW a command batch. type CommandSet []CommandSpec // WithContext runs all the commands in a CommandSet, no command output is // handled. All commands are run as a batch. func (s CommandSet) WithContext(ctx context.Context, data any) error { for _, curr := range s { if err := curr.WithContext(ctx, data); err != nil { return err } } return nil } // commandFormat formats the CommandSpec's Command field. The data is passed in // to the template parsing and execution. func (c CommandSpec) commandFormat(data any) ([]string, error) { if len(strings.Trim(c.Command, " ")) == 0 { return nil, ErrCommandTemplate } tmpl, err := template.New("").Parse(c.Command) if err != nil { galog.Debugf("Error parsing command format: %v", err) return nil, ErrCommandTemplate } var buffer bytes.Buffer if err := tmpl.Execute(&buffer, data); err != nil { galog.Debugf("Error executing command format: %v", err) return nil, ErrCommandTemplate } return strings.Split(buffer.String(), " "), nil } // errorFormat formats the CommandSpec's Error field. The data is passed in to // the template parsing and execution. func (c CommandSpec) errorFormat(data any) (string, error) { tmpl, err := template.New("").Parse(c.Error) if err != nil { galog.Debugf("Error parsing error format: %v", err) return "", ErrTemplateError } var buffer bytes.Buffer if err := tmpl.Execute(&buffer, data); err != nil { galog.Debugf("Error executing error format: %v", err) return "", ErrTemplateError } return buffer.String(), nil } // WithContext runs a CommandSpec command, no command output is handled. func (c CommandSpec) WithContext(ctx context.Context, data any) error { tokens, err := c.commandFormat(data) if err != nil { return err } errorMsg, err := c.errorFormat(data) if err != nil { return err } opts := Options{ Name: tokens[0], Args: tokens[1:], OutputType: OutputNone, } if _, err := Client.WithContext(ctx, opts); err != nil { return fmt.Errorf("%w; %s: %d", err, errorMsg, len(tokens)) } return nil }