1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
|
package common
import (
"context"
"errors"
"fmt"
"github.com/sirupsen/logrus"
)
// ExecutorData is an empty interface representing free-form data
// executor will use. Meant to be casted, e.g. virtual machine details.
type ExecutorData interface{}
// ExecutorCommand stores the script executor will run on a given stage.
// If Predefined it will try to use already allocated resources.
type ExecutorCommand struct {
Script string
Stage BuildStage
Predefined bool
Context context.Context
}
// ExecutorStage represents a stage of build execution in the executor scope.
type ExecutorStage string
const (
// ExecutorStageCreated means the executor is being initialized, i.e. created.
ExecutorStageCreated ExecutorStage = "created"
// ExecutorStagePrepare means the executor is preparing its environment, initializing dependencies.
ExecutorStagePrepare ExecutorStage = "prepare"
// ExecutorStageFinish means the executor has finished build execution.
ExecutorStageFinish ExecutorStage = "finish"
// ExecutorStageCleanup means the executor is cleaning up resources.
ExecutorStageCleanup ExecutorStage = "cleanup"
)
// ExecutorPrepareOptions stores any data necessary for the executor to prepare
// the environment for running a build. This includes runner configuration, build data, etc.
type ExecutorPrepareOptions struct {
Config *RunnerConfig
Build *Build
Trace JobTrace
User string
Context context.Context
}
type NoFreeExecutorError struct {
Message string
}
func (e *NoFreeExecutorError) Error() string {
return e.Message
}
// Executor represents entities responsible for build execution.
// It prepares the environment, runs the build and cleans up resources.
// See more in https://docs.gitlab.com/runner/executors/
type Executor interface {
// Shell returns data about the shell and scripts this executor is bound to.
Shell() *ShellScriptInfo
// Prepare prepares the environment for build execution. e.g. connects to SSH, creates containers.
Prepare(options ExecutorPrepareOptions) error
// Run executes a command on the prepared environment.
Run(cmd ExecutorCommand) error
// Finish marks the build execution as finished.
Finish(err error)
// Cleanup cleans any resources left by build execution.
Cleanup()
// GetCurrentStage returns current stage of build execution.
GetCurrentStage() ExecutorStage
// SetCurrentStage sets the current stage of build execution.
SetCurrentStage(stage ExecutorStage)
}
// ExecutorProvider is responsible for managing the lifetime of executors, acquiring resources,
// retrieving executor metadata, etc.
type ExecutorProvider interface {
// CanCreate returns whether the executor provider has the necessary data to create an executor.
CanCreate() bool
// Create creates a new executor. No resource allocation happens.
Create() Executor
// Acquire acquires the necessary resources for the executor to run, e.g. finds a virtual machine.
Acquire(config *RunnerConfig) (ExecutorData, error)
// Release releases any resources locked by Acquire.
Release(config *RunnerConfig, data ExecutorData)
// GetFeatures returns metadata about the features the executor supports, e.g. variables, services, shell.
GetFeatures(features *FeaturesInfo) error
// GetConfigInfo extracts metadata about the config the executor is using, e.g. GPUs.
GetConfigInfo(input *RunnerConfig, output *ConfigInfo)
// GetDefaultShell returns the name of the default shell for the executor.
GetDefaultShell() string
}
// BuildError represents an error during build execution, not related to
// the job script, e.g. failed to create container, establish ssh connection.
type BuildError struct {
Inner error
FailureReason JobFailureReason
ExitCode int
}
// Error implements the error interface.
func (b *BuildError) Error() string {
if b.Inner == nil {
return "error"
}
return b.Inner.Error()
}
func (b *BuildError) Is(err error) bool {
buildErr, ok := err.(*BuildError)
if !ok {
return false
}
return buildErr.FailureReason == b.FailureReason
}
func (b *BuildError) Unwrap() error {
return b.Inner
}
// MakeBuildError returns an new instance of BuildError.
func MakeBuildError(format string, args ...interface{}) error {
return &BuildError{
Inner: fmt.Errorf(format, args...),
}
}
var executorProviders map[string]ExecutorProvider
func validateExecutorProvider(provider ExecutorProvider) error {
if provider.GetDefaultShell() == "" {
return errors.New("default shell not implemented")
}
if !provider.CanCreate() {
return errors.New("cannot create executor")
}
if err := provider.GetFeatures(&FeaturesInfo{}); err != nil {
return fmt.Errorf("cannot get features: %w", err)
}
return nil
}
// RegisterExecutorProvider maps an ExecutorProvider to an executor name, i.e. registers it.
func RegisterExecutorProvider(executor string, provider ExecutorProvider) {
logrus.Debugln("Registering", executor, "executor...")
if err := validateExecutorProvider(provider); err != nil {
panic("Executor cannot be registered: " + err.Error())
}
if executorProviders == nil {
executorProviders = make(map[string]ExecutorProvider)
}
if _, ok := executorProviders[executor]; ok {
panic("Executor already exist: " + executor)
}
executorProviders[executor] = provider
}
// GetExecutorProvider returns an ExecutorProvider by name from the registered ones.
func GetExecutorProvider(executor string) ExecutorProvider {
if executorProviders == nil {
return nil
}
provider := executorProviders[executor]
return provider
}
// GetExecutorNames returns a list of all registered executor names.
func GetExecutorNames() []string {
var names []string
for name := range executorProviders {
names = append(names, name)
}
return names
}
// GetExecutorProviders returns a list of all registered executor providers.
func GetExecutorProviders() []ExecutorProvider {
var providers []ExecutorProvider
for _, executorProvider := range executorProviders {
providers = append(providers, executorProvider)
}
return providers
}
func NewExecutor(executor string) Executor {
provider := GetExecutorProvider(executor)
if provider != nil {
return provider.Create()
}
return nil
}
|