241 lines
7.4 KiB
Go
241 lines
7.4 KiB
Go
// Copyright 2024 Woodpecker Authors
|
|
//
|
|
// 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
|
|
//
|
|
// http://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.
|
|
|
|
//go:build test
|
|
// +build test
|
|
|
|
package dummy
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/rs/zerolog/log"
|
|
"github.com/urfave/cli/v3"
|
|
|
|
backend "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types"
|
|
)
|
|
|
|
type dummy struct {
|
|
kv sync.Map
|
|
}
|
|
|
|
const (
|
|
// Step names to control behavior of dummy backend.
|
|
WorkflowSetupFailUUID = "WorkflowSetupShouldFail"
|
|
EnvKeyStepSleep = "SLEEP"
|
|
EnvKeyStepType = "EXPECT_TYPE"
|
|
EnvKeyStepStartFail = "STEP_START_FAIL"
|
|
EnvKeyStepExitCode = "STEP_EXIT_CODE"
|
|
EnvKeyStepTailFail = "STEP_TAIL_FAIL"
|
|
EnvKeyStepOOMKilled = "STEP_OOM_KILLED"
|
|
|
|
// Internal const.
|
|
stepStateStarted = "started"
|
|
stepStateDone = "done"
|
|
testServiceTimeout = 1 * time.Second
|
|
)
|
|
|
|
// New returns a dummy backend.
|
|
func New() backend.Backend {
|
|
return &dummy{
|
|
kv: sync.Map{},
|
|
}
|
|
}
|
|
|
|
func (e *dummy) Name() string {
|
|
return "dummy"
|
|
}
|
|
|
|
func (e *dummy) IsAvailable(_ context.Context) bool {
|
|
return true
|
|
}
|
|
|
|
func (e *dummy) Flags() []cli.Flag {
|
|
return nil
|
|
}
|
|
|
|
// Load new client for Docker Backend using environment variables.
|
|
func (e *dummy) Load(_ context.Context) (*backend.BackendInfo, error) {
|
|
return &backend.BackendInfo{
|
|
Platform: "dummy",
|
|
}, nil
|
|
}
|
|
|
|
func (e *dummy) SetupWorkflow(_ context.Context, _ *backend.Config, taskUUID string) error {
|
|
if taskUUID == WorkflowSetupFailUUID {
|
|
return fmt.Errorf("expected fail to setup workflow")
|
|
}
|
|
log.Trace().Str("taskUUID", taskUUID).Msg("create workflow environment")
|
|
e.kv.Store("task_"+taskUUID, "setup")
|
|
return nil
|
|
}
|
|
|
|
func (e *dummy) StartStep(_ context.Context, step *backend.Step, taskUUID string) error {
|
|
log.Trace().Str("taskUUID", taskUUID).Msgf("start step %s", step.Name)
|
|
|
|
// internal state checks
|
|
_, exist := e.kv.Load("task_" + taskUUID)
|
|
if !exist {
|
|
return fmt.Errorf("expect env of workflow %s to exist but found none to destroy", taskUUID)
|
|
}
|
|
stepState, stepExist := e.kv.Load(fmt.Sprintf("task_%s_step_%s", taskUUID, step.UUID))
|
|
if stepExist {
|
|
// Detect issues like https://github.com/woodpecker-ci/woodpecker/issues/3494
|
|
return fmt.Errorf("StartStep detected already started step '%s' (%s) in state: %s", step.Name, step.UUID, stepState)
|
|
}
|
|
|
|
if stepStartFail, _ := strconv.ParseBool(step.Environment[EnvKeyStepStartFail]); stepStartFail {
|
|
return fmt.Errorf("expected fail to start step")
|
|
}
|
|
|
|
expectStepType, testStepType := step.Environment[EnvKeyStepType]
|
|
if testStepType && string(step.Type) != expectStepType {
|
|
return fmt.Errorf("expected step type '%s' but got '%s'", expectStepType, step.Type)
|
|
}
|
|
|
|
e.kv.Store(fmt.Sprintf("task_%s_step_%s", taskUUID, step.UUID), stepStateStarted)
|
|
return nil
|
|
}
|
|
|
|
func (e *dummy) WaitStep(ctx context.Context, step *backend.Step, taskUUID string) (*backend.State, error) {
|
|
log.Trace().Str("taskUUID", taskUUID).Msgf("wait for step %s", step.Name)
|
|
|
|
_, exist := e.kv.Load("task_" + taskUUID)
|
|
if !exist {
|
|
err := fmt.Errorf("expect env of workflow %s to exist but found none to destroy", taskUUID)
|
|
return &backend.State{Error: err}, err
|
|
}
|
|
|
|
// check state
|
|
stepState, stepExist := e.kv.Load(fmt.Sprintf("task_%s_step_%s", taskUUID, step.UUID))
|
|
if !stepExist {
|
|
err := fmt.Errorf("WaitStep expect step '%s' (%s) to be created but found none", step.Name, step.UUID)
|
|
return &backend.State{Error: err}, err
|
|
}
|
|
if stepState != stepStateStarted {
|
|
err := fmt.Errorf("WaitStep expect step '%s' (%s) to be '%s' but it is: %s", step.Name, step.UUID, stepStateStarted, stepState)
|
|
return &backend.State{Error: err}, err
|
|
}
|
|
|
|
// extend wait time logic
|
|
if sleep, sleepExist := step.Environment[EnvKeyStepSleep]; sleepExist {
|
|
toSleep, err := time.ParseDuration(sleep)
|
|
if err != nil {
|
|
err = fmt.Errorf("WaitStep fail to parse sleep duration: %w", err)
|
|
return &backend.State{Error: err}, err
|
|
}
|
|
time.Sleep(toSleep)
|
|
} else {
|
|
if step.Type == backend.StepTypeService {
|
|
select {
|
|
case <-time.NewTimer(testServiceTimeout).C:
|
|
err := fmt.Errorf("WaitStep fail due to timeout of service after 1 second")
|
|
return &backend.State{Error: err}, err
|
|
case <-ctx.Done():
|
|
// context for service closed ... we can move forward
|
|
}
|
|
} else {
|
|
time.Sleep(time.Nanosecond)
|
|
}
|
|
}
|
|
|
|
e.kv.Store(fmt.Sprintf("task_%s_step_%s", taskUUID, step.UUID), stepStateDone)
|
|
|
|
oomKilled, _ := strconv.ParseBool(step.Environment[EnvKeyStepOOMKilled])
|
|
exitCode := 0
|
|
|
|
if code, exist := step.Environment[EnvKeyStepExitCode]; exist {
|
|
exitCode, _ = strconv.Atoi(strings.TrimSpace(code))
|
|
}
|
|
|
|
return &backend.State{
|
|
ExitCode: exitCode,
|
|
Exited: true,
|
|
OOMKilled: oomKilled,
|
|
}, nil
|
|
}
|
|
|
|
func (e *dummy) TailStep(_ context.Context, step *backend.Step, taskUUID string) (io.ReadCloser, error) {
|
|
log.Trace().Str("taskUUID", taskUUID).Msgf("tail logs of step %s", step.Name)
|
|
|
|
_, exist := e.kv.Load("task_" + taskUUID)
|
|
if !exist {
|
|
return nil, fmt.Errorf("expect env of workflow %s to exist but found none to destroy", taskUUID)
|
|
}
|
|
|
|
// check state
|
|
stepState, stepExist := e.kv.Load(fmt.Sprintf("task_%s_step_%s", taskUUID, step.UUID))
|
|
if !stepExist {
|
|
return nil, fmt.Errorf("WaitStep expect step '%s' (%s) to be created but found none", step.Name, step.UUID)
|
|
}
|
|
if stepState != stepStateStarted {
|
|
return nil, fmt.Errorf("WaitStep expect step '%s' (%s) to be '%s' but it is: %s", step.Name, step.UUID, stepStateStarted, stepState)
|
|
}
|
|
|
|
if tailShouldFail, _ := strconv.ParseBool(step.Environment[EnvKeyStepTailFail]); tailShouldFail {
|
|
return nil, fmt.Errorf("expected fail to read stdout of step")
|
|
}
|
|
|
|
return io.NopCloser(strings.NewReader(dummyExecStepOutput(step))), nil
|
|
}
|
|
|
|
func (e *dummy) DestroyStep(_ context.Context, step *backend.Step, taskUUID string) error {
|
|
log.Trace().Str("taskUUID", taskUUID).Msgf("stop step %s", step.Name)
|
|
|
|
_, exist := e.kv.Load("task_" + taskUUID)
|
|
if !exist {
|
|
return fmt.Errorf("expect env of workflow %s to exist but found none to destroy", taskUUID)
|
|
}
|
|
|
|
// check state
|
|
stepState, stepExist := e.kv.Load(fmt.Sprintf("task_%s_step_%s", taskUUID, step.UUID))
|
|
if !stepExist {
|
|
return fmt.Errorf("WaitStep expect step '%s' (%s) to be created but found none", step.Name, step.UUID)
|
|
}
|
|
if stepState != stepStateDone {
|
|
return fmt.Errorf("WaitStep expect step '%s' (%s) to be '%s' but it is: %s", step.Name, step.UUID, stepStateDone, stepState)
|
|
}
|
|
|
|
e.kv.Delete(fmt.Sprintf("task_%s_step_%s", taskUUID, step.UUID))
|
|
return nil
|
|
}
|
|
|
|
func (e *dummy) DestroyWorkflow(_ context.Context, _ *backend.Config, taskUUID string) error {
|
|
log.Trace().Str("taskUUID", taskUUID).Msgf("delete workflow environment")
|
|
|
|
_, exist := e.kv.Load("task_" + taskUUID)
|
|
if !exist {
|
|
return fmt.Errorf("expect env of workflow %s to exist but found none to destroy", taskUUID)
|
|
}
|
|
e.kv.Delete("task_" + taskUUID)
|
|
return nil
|
|
}
|
|
|
|
func dummyExecStepOutput(step *backend.Step) string {
|
|
return fmt.Sprintf(`StepName: %s
|
|
StepType: %s
|
|
StepUUID: %s
|
|
StepCommands:
|
|
------------------
|
|
%s
|
|
------------------
|
|
`, step.Name, step.Type, step.UUID, strings.Join(step.Commands, "\n"))
|
|
}
|