mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-11-26 11:51:02 +00:00
parent
4504677233
commit
435f5ae207
2 changed files with 141 additions and 42 deletions
|
@ -31,11 +31,6 @@ import (
|
||||||
"github.com/woodpecker-ci/woodpecker/shared/constant"
|
"github.com/woodpecker-ci/woodpecker/shared/constant"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
workingSubDir = "work"
|
|
||||||
homeSubDir = "home"
|
|
||||||
)
|
|
||||||
|
|
||||||
// notAllowedEnvVarOverwrites are all env vars that can not be overwritten by step config
|
// notAllowedEnvVarOverwrites are all env vars that can not be overwritten by step config
|
||||||
var notAllowedEnvVarOverwrites = []string{
|
var notAllowedEnvVarOverwrites = []string{
|
||||||
"CI_NETRC_MACHINE",
|
"CI_NETRC_MACHINE",
|
||||||
|
@ -46,16 +41,23 @@ var notAllowedEnvVarOverwrites = []string{
|
||||||
"SHELL",
|
"SHELL",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type workflowState struct {
|
||||||
|
stepCMDs map[string]*exec.Cmd
|
||||||
|
baseDir string
|
||||||
|
homeDir string
|
||||||
|
workspaceDir string
|
||||||
|
}
|
||||||
|
|
||||||
type local struct {
|
type local struct {
|
||||||
// TODO: make cmd a cmd list to iterate over, the hard part is to have a common ReadCloser
|
workflows map[string]*workflowState
|
||||||
cmd *exec.Cmd
|
output io.ReadCloser
|
||||||
output io.ReadCloser
|
|
||||||
workflowBaseDir string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a new local Engine.
|
// New returns a new local Engine.
|
||||||
func New() types.Engine {
|
func New() types.Engine {
|
||||||
return &local{}
|
return &local{
|
||||||
|
workflows: make(map[string]*workflowState),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *local) Name() string {
|
func (e *local) Name() string {
|
||||||
|
@ -67,25 +69,52 @@ func (e *local) IsAvailable(context.Context) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *local) Load(context.Context) error {
|
func (e *local) Load(context.Context) error {
|
||||||
dir, err := os.MkdirTemp("", "woodpecker-local-*")
|
// TODO: download plugin-git binary if not exist
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
e.workflowBaseDir = dir
|
|
||||||
|
|
||||||
if err := os.Mkdir(filepath.Join(e.workflowBaseDir, workingSubDir), 0o700); err != nil {
|
return nil
|
||||||
return err
|
|
||||||
}
|
|
||||||
return os.Mkdir(filepath.Join(e.workflowBaseDir, homeSubDir), 0o700)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup the pipeline environment.
|
// Setup the pipeline environment.
|
||||||
func (e *local) Setup(_ context.Context, _ *types.Config) error {
|
func (e *local) Setup(_ context.Context, c *types.Config) error {
|
||||||
|
baseDir, err := os.MkdirTemp("", "woodpecker-local-*")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
state := &workflowState{
|
||||||
|
stepCMDs: make(map[string]*exec.Cmd),
|
||||||
|
baseDir: baseDir,
|
||||||
|
workspaceDir: filepath.Join(baseDir, "workspace"),
|
||||||
|
homeDir: filepath.Join(baseDir, "home"),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Mkdir(state.homeDir, 0o700); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Mkdir(state.workspaceDir, 0o700); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: copy plugin-git binary to homeDir and set PATH
|
||||||
|
|
||||||
|
workflowID, err := e.getWorkflowIDFromConfig(c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
e.workflows[workflowID] = state
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exec the pipeline step.
|
// Exec the pipeline step.
|
||||||
func (e *local) Exec(ctx context.Context, step *types.Step) error {
|
func (e *local) Exec(ctx context.Context, step *types.Step) error {
|
||||||
|
state, err := e.getWorkflowStateFromStep(step)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Get environment variables
|
// Get environment variables
|
||||||
env := os.Environ()
|
env := os.Environ()
|
||||||
for a, b := range step.Environment {
|
for a, b := range step.Environment {
|
||||||
|
@ -96,14 +125,13 @@ func (e *local) Exec(ctx context.Context, step *types.Step) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set HOME
|
// Set HOME
|
||||||
env = append(env, "HOME="+filepath.Join(e.workflowBaseDir, homeSubDir))
|
env = append(env, "HOME="+state.homeDir)
|
||||||
|
|
||||||
var command []string
|
var command []string
|
||||||
if step.Image == constant.DefaultCloneImage {
|
if step.Image == constant.DefaultCloneImage {
|
||||||
// Default clone step
|
// Default clone step
|
||||||
// TODO: use tmp HOME and insert netrc and delete it after clone
|
// TODO: use tmp HOME and insert netrc and delete it after clone
|
||||||
// TODO: download plugin-git binary if not exist
|
env = append(env, "CI_WORKSPACE="+state.workspaceDir)
|
||||||
env = append(env, "CI_WORKSPACE="+filepath.Join(e.workflowBaseDir, workingSubDir, step.Environment["CI_REPO"]))
|
|
||||||
command = append(command, "plugin-git")
|
command = append(command, "plugin-git")
|
||||||
} else {
|
} else {
|
||||||
// Use "image name" as run command
|
// Use "image name" as run command
|
||||||
|
@ -122,30 +150,33 @@ func (e *local) Exec(ctx context.Context, step *types.Step) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare command
|
// Prepare command
|
||||||
e.cmd = exec.CommandContext(ctx, command[0], command[1:]...)
|
cmd := exec.CommandContext(ctx, command[0], command[1:]...)
|
||||||
e.cmd.Env = env
|
cmd.Env = env
|
||||||
|
cmd.Dir = state.workspaceDir
|
||||||
|
|
||||||
// Prepare working directory
|
|
||||||
if step.Image == constant.DefaultCloneImage {
|
|
||||||
e.cmd.Dir = filepath.Join(e.workflowBaseDir, workingSubDir, step.Environment["CI_REPO_OWNER"])
|
|
||||||
} else {
|
|
||||||
e.cmd.Dir = filepath.Join(e.workflowBaseDir, workingSubDir, step.Environment["CI_REPO"])
|
|
||||||
}
|
|
||||||
err := os.MkdirAll(e.cmd.Dir, 0o700)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Get output and redirect Stderr to Stdout
|
// Get output and redirect Stderr to Stdout
|
||||||
e.output, _ = e.cmd.StdoutPipe()
|
e.output, _ = cmd.StdoutPipe()
|
||||||
e.cmd.Stderr = e.cmd.Stdout
|
cmd.Stderr = cmd.Stdout
|
||||||
|
|
||||||
return e.cmd.Start()
|
state.stepCMDs[step.Name] = cmd
|
||||||
|
|
||||||
|
return cmd.Start()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for the pipeline step to complete and returns
|
// Wait for the pipeline step to complete and returns
|
||||||
// the completion results.
|
// the completion results.
|
||||||
func (e *local) Wait(context.Context, *types.Step) (*types.State, error) {
|
func (e *local) Wait(_ context.Context, step *types.Step) (*types.State, error) {
|
||||||
err := e.cmd.Wait()
|
state, err := e.getWorkflowStateFromStep(step)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd, ok := state.stepCMDs[step.Name]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("step cmd %s not found", step.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = cmd.Wait()
|
||||||
ExitCode := 0
|
ExitCode := 0
|
||||||
|
|
||||||
var execExitError *exec.ExitError
|
var execExitError *exec.ExitError
|
||||||
|
@ -167,6 +198,69 @@ func (e *local) Tail(context.Context, *types.Step) (io.ReadCloser, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Destroy the pipeline environment.
|
// Destroy the pipeline environment.
|
||||||
func (e *local) Destroy(context.Context, *types.Config) error {
|
func (e *local) Destroy(_ context.Context, c *types.Config) error {
|
||||||
return os.RemoveAll(e.workflowBaseDir)
|
state, err := e.getWorkflowStateFromConfig(c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.RemoveAll(state.baseDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
workflowID, err := e.getWorkflowIDFromConfig(c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(e.workflows, workflowID)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *local) getWorkflowIDFromStep(step *types.Step) (string, error) {
|
||||||
|
prefix := strings.Split(step.Name, "_stage_")
|
||||||
|
if len(prefix) < 2 {
|
||||||
|
return "", fmt.Errorf("invalid step name %s", step.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return prefix[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *local) getWorkflowIDFromConfig(c *types.Config) (string, error) {
|
||||||
|
if len(c.Volumes) < 1 {
|
||||||
|
return "", fmt.Errorf("no volumes found in config")
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix := strings.Replace(c.Volumes[0].Name, "_default", "", 1)
|
||||||
|
return prefix, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *local) getWorkflowStateFromConfig(c *types.Config) (*workflowState, error) {
|
||||||
|
workflowID, err := e.getWorkflowIDFromConfig(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
state, ok := e.workflows[workflowID]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("workflow %s not found", workflowID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return state, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *local) getWorkflowStateFromStep(step *types.Step) (*workflowState, error) {
|
||||||
|
workflowID, err := e.getWorkflowIDFromStep(step)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
state, ok := e.workflows[workflowID]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("workflow %s not found", workflowID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return state, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,8 +8,13 @@ import (
|
||||||
// Engine defines a container orchestration backend and is used
|
// Engine defines a container orchestration backend and is used
|
||||||
// to create and manage container resources.
|
// to create and manage container resources.
|
||||||
type Engine interface {
|
type Engine interface {
|
||||||
|
// Name returns the name of the backend.
|
||||||
Name() string
|
Name() string
|
||||||
|
|
||||||
|
// Check if the backend is available.
|
||||||
IsAvailable(context.Context) bool
|
IsAvailable(context.Context) bool
|
||||||
|
|
||||||
|
// Load the backend engine.
|
||||||
Load(context.Context) error
|
Load(context.Context) error
|
||||||
|
|
||||||
// Setup the pipeline environment.
|
// Setup the pipeline environment.
|
||||||
|
|
Loading…
Reference in a new issue