garm/vendor/github.com/cloudbase/garm-provider-common/execution/execution.go
Gabriel Adrian Samfira bc4285dc80 Update garm-provider-common
Use the websocket reader from within garm-provider-common.

Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
2024-08-03 16:07:21 +00:00

204 lines
5.9 KiB
Go

// Copyright 2023 Cloudbase Solutions SRL
//
// 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.
package execution
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"os"
gErrors "github.com/cloudbase/garm-provider-common/errors"
"github.com/cloudbase/garm-provider-common/params"
"github.com/mattn/go-isatty"
)
const (
// ExitCodeNotFound is an exit code that indicates a Not Found error
ExitCodeNotFound int = 30
// ExitCodeDuplicate is an exit code that indicates a duplicate error
ExitCodeDuplicate int = 31
)
func ResolveErrorToExitCode(err error) int {
if err != nil {
if errors.Is(err, gErrors.ErrNotFound) {
return ExitCodeNotFound
} else if errors.Is(err, gErrors.ErrDuplicateEntity) {
return ExitCodeDuplicate
}
return 1
}
return 0
}
func GetEnvironment() (Environment, error) {
env := Environment{
Command: ExecutionCommand(os.Getenv("GARM_COMMAND")),
ControllerID: os.Getenv("GARM_CONTROLLER_ID"),
PoolID: os.Getenv("GARM_POOL_ID"),
ProviderConfigFile: os.Getenv("GARM_PROVIDER_CONFIG_FILE"),
InstanceID: os.Getenv("GARM_INSTANCE_ID"),
}
// If this is a CreateInstance command, we need to get the bootstrap params
// from stdin
if env.Command == CreateInstanceCommand {
if isatty.IsTerminal(os.Stdin.Fd()) || isatty.IsCygwinTerminal(os.Stdin.Fd()) {
return Environment{}, fmt.Errorf("%s requires data passed into stdin", CreateInstanceCommand)
}
var data bytes.Buffer
if _, err := io.Copy(&data, os.Stdin); err != nil {
return Environment{}, fmt.Errorf("failed to copy bootstrap params")
}
if data.Len() == 0 {
return Environment{}, fmt.Errorf("%s requires data passed into stdin", CreateInstanceCommand)
}
var bootstrapParams params.BootstrapInstance
if err := json.Unmarshal(data.Bytes(), &bootstrapParams); err != nil {
return Environment{}, fmt.Errorf("failed to decode instance params: %w", err)
}
if bootstrapParams.ExtraSpecs == nil {
// Initialize ExtraSpecs as an empty JSON object
bootstrapParams.ExtraSpecs = json.RawMessage([]byte("{}"))
}
env.BootstrapParams = bootstrapParams
}
if err := env.Validate(); err != nil {
return Environment{}, fmt.Errorf("failed to validate execution environment: %w", err)
}
return env, nil
}
type Environment struct {
Command ExecutionCommand
ControllerID string
PoolID string
ProviderConfigFile string
InstanceID string
BootstrapParams params.BootstrapInstance
}
func (e Environment) Validate() error {
if e.Command == "" {
return fmt.Errorf("missing GARM_COMMAND")
}
if e.ProviderConfigFile == "" {
return fmt.Errorf("missing GARM_PROVIDER_CONFIG_FILE")
}
if _, err := os.Lstat(e.ProviderConfigFile); err != nil {
return fmt.Errorf("error accessing config file: %w", err)
}
if e.ControllerID == "" {
return fmt.Errorf("missing GARM_CONTROLLER_ID")
}
switch e.Command {
case CreateInstanceCommand:
if e.BootstrapParams.Name == "" {
return fmt.Errorf("missing bootstrap params")
}
if e.ControllerID == "" {
return fmt.Errorf("missing controller ID")
}
if e.PoolID == "" {
return fmt.Errorf("missing pool ID")
}
case DeleteInstanceCommand, GetInstanceCommand,
StartInstanceCommand, StopInstanceCommand:
if e.InstanceID == "" {
return fmt.Errorf("missing instance ID")
}
case ListInstancesCommand:
if e.PoolID == "" {
return fmt.Errorf("missing pool ID")
}
case RemoveAllInstancesCommand:
if e.ControllerID == "" {
return fmt.Errorf("missing controller ID")
}
default:
return fmt.Errorf("unknown GARM_COMMAND: %s", e.Command)
}
return nil
}
func Run(ctx context.Context, provider ExternalProvider, env Environment) (string, error) {
var ret string
switch env.Command {
case CreateInstanceCommand:
instance, err := provider.CreateInstance(ctx, env.BootstrapParams)
if err != nil {
return "", fmt.Errorf("failed to create instance in provider: %w", err)
}
asJs, err := json.Marshal(instance)
if err != nil {
return "", fmt.Errorf("failed to marshal response: %w", err)
}
ret = string(asJs)
case GetInstanceCommand:
instance, err := provider.GetInstance(ctx, env.InstanceID)
if err != nil {
return "", fmt.Errorf("failed to get instance from provider: %w", err)
}
asJs, err := json.Marshal(instance)
if err != nil {
return "", fmt.Errorf("failed to marshal response: %w", err)
}
ret = string(asJs)
case ListInstancesCommand:
instances, err := provider.ListInstances(ctx, env.PoolID)
if err != nil {
return "", fmt.Errorf("failed to list instances from provider: %w", err)
}
asJs, err := json.Marshal(instances)
if err != nil {
return "", fmt.Errorf("failed to marshal response: %w", err)
}
ret = string(asJs)
case DeleteInstanceCommand:
if err := provider.DeleteInstance(ctx, env.InstanceID); err != nil {
return "", fmt.Errorf("failed to delete instance from provider: %w", err)
}
case RemoveAllInstancesCommand:
if err := provider.RemoveAllInstances(ctx); err != nil {
return "", fmt.Errorf("failed to destroy environment: %w", err)
}
case StartInstanceCommand:
if err := provider.Start(ctx, env.InstanceID); err != nil {
return "", fmt.Errorf("failed to start instance: %w", err)
}
case StopInstanceCommand:
if err := provider.Stop(ctx, env.InstanceID, true); err != nil {
return "", fmt.Errorf("failed to stop instance: %w", err)
}
default:
return "", fmt.Errorf("invalid command: %s", env.Command)
}
return ret, nil
}