get.porter.sh/porter@v1.3.0/pkg/exec/builder/action.go (about) 1 package builder 2 3 import ( 4 "bufio" 5 "context" 6 "fmt" 7 "io" 8 9 "get.porter.sh/porter/pkg/runtime" 10 "get.porter.sh/porter/pkg/tracing" 11 "get.porter.sh/porter/pkg/yaml" 12 ) 13 14 // UnmarshalAction handles unmarshaling any action, given a pointer to a slice of Steps. 15 // Iterate over the results and cast back to the Steps to use the results. 16 // 17 // var steps []Step 18 // results, err := UnmarshalAction(unmarshal, &steps) 19 // if err != nil { 20 // return err 21 // } 22 // 23 // for _, result := range results { 24 // step := result.(*[]Step) 25 // a.Steps = append(a.Steps, *step...) 26 // } 27 func UnmarshalAction(unmarshal func(interface{}) error, builder BuildableAction) (map[string][]interface{}, error) { 28 actionMap := map[string][]interface{}{} 29 err := unmarshal(&actionMap) 30 if err != nil { 31 return nil, fmt.Errorf("could not unmarshal yaml into an action map of exec steps: %w", err) 32 } 33 34 return unmarshalActionMap(actionMap, builder) 35 } 36 37 func unmarshalActionMap(actionMap map[string][]interface{}, builder BuildableAction) (map[string][]interface{}, error) { 38 results := make(map[string][]interface{}) 39 for actionIndex, stepMaps := range actionMap { 40 // Figure out the string representation of the action 41 // examples: 42 // install: -> "install" 43 // true: -> "true" YAML is weird, this is why we use Sprintf and not .(string) 44 name := fmt.Sprintf("%v", actionIndex) 45 46 // Unmarshal the steps 47 b, err := yaml.Marshal(stepMaps) 48 if err != nil { 49 return nil, err 50 } 51 52 steps := builder.MakeSteps() 53 err = yaml.Unmarshal(b, steps) 54 if err != nil { 55 return nil, err 56 } 57 58 result, ok := results[name] 59 if !ok { 60 result = make([]interface{}, 0, 1) 61 } 62 results[name] = append(result, steps) 63 } 64 65 return results, nil 66 } 67 68 // LoadAction reads input from stdin or a command file and uses the specified unmarshal function 69 // to unmarshal it into a typed Action. 70 // The unmarshal function is responsible for calling yaml.Unmarshal and passing in a reference to an appropriate 71 // Action instance. 72 // 73 // Example: 74 // 75 // var action Action 76 // err := builder.LoadAction(m.Context, opts.File, func(contents []byte) (interface{}, error) { 77 // err := yaml.Unmarshal(contents, &action) 78 // return &action, err 79 // }) 80 func LoadAction(ctx context.Context, cfg runtime.RuntimeConfig, commandFile string, unmarshal func([]byte) (interface{}, error)) error { 81 _, span := tracing.StartSpan(ctx) 82 defer span.EndSpan() 83 84 contents, err := readInputFromStdinOrFile(cfg, commandFile) 85 if err != nil { 86 return span.Error(err) 87 } 88 89 _, err = unmarshal(contents) 90 if err != nil { 91 return span.Error(fmt.Errorf("could not unmarshal input:\n %s: %w", string(contents), err)) 92 } 93 94 return nil 95 } 96 97 func readInputFromStdinOrFile(cfg runtime.RuntimeConfig, commandFile string) ([]byte, error) { 98 var b []byte 99 var err error 100 if commandFile == "" { 101 reader := bufio.NewReader(cfg.In) 102 b, err = io.ReadAll(reader) 103 } else { 104 b, err = cfg.FileSystem.ReadFile(commandFile) 105 } 106 107 if err != nil { 108 source := "STDIN" 109 if commandFile == "" { 110 source = commandFile 111 } 112 return nil, fmt.Errorf("could not load input from %s: %w", source, err) 113 } 114 return b, nil 115 }