codecatalyst-runner/pkg/actions/action_plan.go (178 lines of code) (raw):

package actions import ( "fmt" "os" "path/filepath" "strings" "github.com/aws/codecatalyst-runner-cli/command-runner/pkg/runner" "github.com/kballard/go-shellquote" "github.com/rs/zerolog/log" ) // NewActionPlanParams contains the parametes for the NewActionPlan function type NewActionPlanParams struct { Action *Action // Action to use to create a plan from ExecutionType runner.ExecutionType // Type of execution (shell or docker) WorkingDir string // Working directory for the plan ID string // override the id of the action Steps []string // override commands to run DependsOn []string // dependencies } const containerActionDir = "/codecatalyst/output/action" const DefaultCodeCatalystImage = "docker://public.ecr.aws/c8t2t1h8/al2/curated:1.3-x86_64-ec2" func CodeCatalystImage() string { if val, ok := os.LookupEnv("CATALYST_IMAGE"); ok { return val } return DefaultCodeCatalystImage } // NewActionPlan creates a new Plan from the given params func NewActionPlan(params *NewActionPlanParams) (runner.Plan, error) { var id string if params.ID != "" { id = params.ID } else { id = params.Action.ID } workingDir, err := filepath.Abs(params.WorkingDir) if err != nil { return nil, err } actionPlan := &actionPlan{ id: id, action: params.Action, commandGroups: make([]*runner.CommandGroup, 0), environmentConfiguration: &runner.EnvironmentConfiguration{ FileMaps: make([]*runner.FileMap, 0), Env: map[string]string{ "CATALYST_WORKFLOW_SPACE_NAME": "-", "CATALYST_WORKFLOW_SPACE_ID": "-", "CATALYST_WORKFLOW_PROJECT_NAME": "-", "CATALYST_WORKFLOW_PROJECT_ID": "-", "CI": "true", }, WorkingDir: workingDir, }, dependsOn: params.DependsOn, ExecutionType: params.ExecutionType, } if params.Action.Runs.Using == UsingTypeDocker { err = actionPlan.loadDockerAction(params.Action, params.Steps) } else { err = actionPlan.loadNodeAction(params.Action, params.Steps, params.ExecutionType) } return actionPlan, err } type actionPlan struct { id string dependsOn []string environmentConfiguration *runner.EnvironmentConfiguration commandGroups []*runner.CommandGroup action *Action ExecutionType runner.ExecutionType } func (ap *actionPlan) EnvironmentConfiguration() *runner.EnvironmentConfiguration { return ap.environmentConfiguration } func (ap *actionPlan) CommandGroups() []*runner.CommandGroup { return ap.commandGroups } func (ap *actionPlan) Action() *Action { return ap.action } func (ap *actionPlan) ID() string { return ap.id } func (ap *actionPlan) DependsOn() []string { return ap.dependsOn } func (ap *actionPlan) AddDependsOn(dependencies ...string) { ap.dependsOn = append(ap.dependsOn, dependencies...) } func newCommandGroup(image string, entrypoint string) (*runner.CommandGroup, error) { entrypointParts, err := shellquote.Split(entrypoint) if err != nil { return nil, err } return &runner.CommandGroup{ Image: image, Entrypoint: entrypointParts, }, nil } // ActionProvider exposes acces to an [Action] type ActionProvider interface { Action() *Action } func (ap *actionPlan) loadDockerAction(action *Action, steps []string) error { image := action.Runs.Image if !strings.HasPrefix(image, "docker://") { image = filepath.Join(action.Basedir, image) } for i, cmd := range []string{action.Runs.PreEntryPoint, action.Runs.Entrypoint, action.Runs.PostEntryPoint} { if cmd != "" { entrypoint := "/bin/cat" cg, err := newCommandGroup(image, entrypoint) if err != nil { return err } cg.Commands = append(cg.Commands, []string{cmd}) if i == 1 { // add steps to the main command group log.Debug().Msgf("steps: %+v", steps) for _, step := range steps { if step != "" { cg.Commands = append(cg.Commands, []string{step}) } } } log.Debug().Msgf("adding command group: %+v", cg) ap.commandGroups = append(ap.commandGroups, cg) } } return nil } func (ap *actionPlan) loadNodeAction(action *Action, steps []string, executionType runner.ExecutionType) error { var image, entrypoint string if executionType == runner.ExecutionTypeDocker || executionType == runner.ExecutionTypeFinch { switch action.Runs.Using { case UsingTypeNode12: image = CodeCatalystImage() case UsingTypeNode16: image = CodeCatalystImage() default: return fmt.Errorf("unsupported value for 'using': %s", action.Runs.Using) } entrypoint = "/bin/cat" ap.environmentConfiguration.FileMaps = append(ap.environmentConfiguration.FileMaps, &runner.FileMap{ SourcePath: action.Basedir, TargetPath: containerActionDir, Type: runner.FileMapTypeCopyInWithGitignore, }) } for i, command := range []string{action.Runs.Pre, action.Runs.Main, action.Runs.Post} { if command != "" { var fullCommand string switch executionType { case runner.ExecutionTypeDocker, runner.ExecutionTypeFinch: fullCommand = filepath.Join(containerActionDir, action.ID, command) ap.environmentConfiguration.Env["CATALYST_SOURCE_DIR_CawsCustomActionSource"] = containerActionDir case runner.ExecutionTypeShell: var err error fullCommand, err = filepath.Abs(fmt.Sprintf("%s/%s", action.Basedir, command)) ap.environmentConfiguration.Env["CATALYST_SOURCE_DIR_CawsCustomActionSource"] = action.Basedir if err != nil { return err } default: return fmt.Errorf("unsupported execution type: %s", executionType) } cg, err := newCommandGroup(image, entrypoint) if err != nil { return err } cg.Commands = append(cg.Commands, []string{"node", fullCommand}) if i == 1 { // add steps to the main command group for _, step := range steps { cg.Commands = append(cg.Commands, []string{step}) } } ap.commandGroups = append(ap.commandGroups, cg) } } return nil }