The params package should not depend on config. The params packages should be consumable by external applications that wish to interact with garm, and it makes no sense to pull in the config package just for some constants. As such, the following changes have been made: * Moved some types from config to params * Moved defaults in a new leaf package called appdefaults Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
206 lines
6.3 KiB
Go
206 lines
6.3 KiB
Go
package external
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
|
|
"github.com/cloudbase/garm/config"
|
|
garmErrors "github.com/cloudbase/garm/errors"
|
|
"github.com/cloudbase/garm/params"
|
|
"github.com/cloudbase/garm/runner/common"
|
|
providerCommon "github.com/cloudbase/garm/runner/providers/common"
|
|
"github.com/cloudbase/garm/util/exec"
|
|
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
var _ common.Provider = (*external)(nil)
|
|
|
|
func NewProvider(ctx context.Context, cfg *config.Provider, controllerID string) (common.Provider, error) {
|
|
if cfg.ProviderType != params.ExternalProvider {
|
|
return nil, garmErrors.NewBadRequestError("invalid provider config")
|
|
}
|
|
|
|
execPath, err := cfg.External.ExecutablePath()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "fetching executable path")
|
|
}
|
|
return &external{
|
|
ctx: ctx,
|
|
controllerID: controllerID,
|
|
cfg: cfg,
|
|
execPath: execPath,
|
|
}, nil
|
|
}
|
|
|
|
type external struct {
|
|
ctx context.Context
|
|
controllerID string
|
|
cfg *config.Provider
|
|
execPath string
|
|
}
|
|
|
|
func (e *external) configEnvVar() string {
|
|
return fmt.Sprintf("GARM_PROVIDER_CONFIG_FILE=%s", e.cfg.External.ConfigFile)
|
|
}
|
|
|
|
func (e *external) validateCreateResult(inst params.Instance, bootstrapParams params.BootstrapInstance) error {
|
|
if inst.ProviderID == "" {
|
|
return garmErrors.NewProviderError("missing provider ID after create call")
|
|
}
|
|
|
|
if inst.Name == "" {
|
|
return garmErrors.NewProviderError("missing instance name after create call")
|
|
}
|
|
|
|
if inst.OSName == "" || inst.OSArch == "" || inst.OSType == "" {
|
|
// we can still function without this info (I think)
|
|
log.Printf("WARNING: missing OS information after create call")
|
|
}
|
|
if !providerCommon.IsValidStatus(inst.Status) {
|
|
return garmErrors.NewProviderError("invalid status returned (%s) after create call", inst.Status)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// CreateInstance creates a new compute instance in the provider.
|
|
func (e *external) CreateInstance(ctx context.Context, bootstrapParams params.BootstrapInstance) (params.Instance, error) {
|
|
asEnv := bootstrapParamsToEnv(bootstrapParams)
|
|
asEnv = append(asEnv, createInstanceCommand)
|
|
asEnv = append(asEnv, fmt.Sprintf("GARM_CONTROLLER_ID=%s", e.controllerID))
|
|
asEnv = append(asEnv, fmt.Sprintf("GARM_POOL_ID=%s", bootstrapParams.PoolID))
|
|
asEnv = append(asEnv, e.configEnvVar())
|
|
|
|
asJs, err := json.Marshal(bootstrapParams)
|
|
if err != nil {
|
|
return params.Instance{}, errors.Wrap(err, "serializing bootstrap params")
|
|
}
|
|
|
|
out, err := exec.Exec(ctx, e.execPath, asJs, asEnv)
|
|
if err != nil {
|
|
return params.Instance{}, garmErrors.NewProviderError("provider binary %s returned error: %s", e.execPath, err)
|
|
}
|
|
|
|
var param params.Instance
|
|
if err := json.Unmarshal(out, ¶m); err != nil {
|
|
return params.Instance{}, garmErrors.NewProviderError("failed to decode response from binary: %s", err)
|
|
}
|
|
|
|
if err := e.validateCreateResult(param, bootstrapParams); err != nil {
|
|
return params.Instance{}, garmErrors.NewProviderError("failed to validate result: %s", err)
|
|
}
|
|
|
|
retAsJs, _ := json.MarshalIndent(param, "", " ")
|
|
log.Printf("provider returned: %s", string(retAsJs))
|
|
return param, nil
|
|
}
|
|
|
|
// Delete instance will delete the instance in a provider.
|
|
func (e *external) DeleteInstance(ctx context.Context, instance string) error {
|
|
asEnv := []string{
|
|
deleteInstanceCommand,
|
|
e.configEnvVar(),
|
|
fmt.Sprintf("GARM_INSTANCE_ID=%s", instance),
|
|
}
|
|
|
|
_, err := exec.Exec(ctx, e.execPath, nil, asEnv)
|
|
if err != nil {
|
|
return garmErrors.NewProviderError("provider binary %s returned error: %s", e.execPath, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetInstance will return details about one instance.
|
|
func (e *external) GetInstance(ctx context.Context, instance string) (params.Instance, error) {
|
|
asEnv := []string{
|
|
getInstanceCommand,
|
|
e.configEnvVar(),
|
|
fmt.Sprintf("GARM_INSTANCE_ID=%s", instance),
|
|
}
|
|
|
|
// TODO(gabriel-samfira): handle error types. Of particular insterest is to
|
|
// know when the error is ErrNotFound.
|
|
out, err := exec.Exec(ctx, e.execPath, nil, asEnv)
|
|
if err != nil {
|
|
return params.Instance{}, garmErrors.NewProviderError("provider binary %s returned error: %s", e.execPath, err)
|
|
}
|
|
|
|
var param params.Instance
|
|
if err := json.Unmarshal(out, ¶m); err != nil {
|
|
return params.Instance{}, garmErrors.NewProviderError("failed to decode response from binary: %s", err)
|
|
}
|
|
return param, nil
|
|
}
|
|
|
|
// ListInstances will list all instances for a provider.
|
|
func (e *external) ListInstances(ctx context.Context, poolID string) ([]params.Instance, error) {
|
|
asEnv := []string{
|
|
listInstancesCommand,
|
|
e.configEnvVar(),
|
|
fmt.Sprintf("GARM_POOL_ID=%s", poolID),
|
|
}
|
|
|
|
out, err := exec.Exec(ctx, e.execPath, nil, asEnv)
|
|
if err != nil {
|
|
return []params.Instance{}, garmErrors.NewProviderError("provider binary %s returned error: %s", e.execPath, err)
|
|
}
|
|
|
|
var param []params.Instance
|
|
if err := json.Unmarshal(out, ¶m); err != nil {
|
|
return []params.Instance{}, garmErrors.NewProviderError("failed to decode response from binary: %s", err)
|
|
}
|
|
return param, nil
|
|
}
|
|
|
|
// RemoveAllInstances will remove all instances created by this provider.
|
|
func (e *external) RemoveAllInstances(ctx context.Context) error {
|
|
asEnv := []string{
|
|
removeAllInstancesCommand,
|
|
e.configEnvVar(),
|
|
fmt.Sprintf("GARM_CONTROLLER_ID=%s", e.controllerID),
|
|
}
|
|
_, err := exec.Exec(ctx, e.execPath, nil, asEnv)
|
|
if err != nil {
|
|
return garmErrors.NewProviderError("provider binary %s returned error: %s", e.execPath, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Stop shuts down the instance.
|
|
func (e *external) Stop(ctx context.Context, instance string, force bool) error {
|
|
asEnv := []string{
|
|
stopInstanceCommand,
|
|
e.configEnvVar(),
|
|
fmt.Sprintf("GARM_INSTANCE_ID=%s", instance),
|
|
}
|
|
_, err := exec.Exec(ctx, e.execPath, nil, asEnv)
|
|
if err != nil {
|
|
return garmErrors.NewProviderError("provider binary %s returned error: %s", e.execPath, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Start boots up an instance.
|
|
func (e *external) Start(ctx context.Context, instance string) error {
|
|
asEnv := []string{
|
|
startInstanceCommand,
|
|
e.configEnvVar(),
|
|
fmt.Sprintf("GARM_INSTANCE_ID=%s", instance),
|
|
}
|
|
_, err := exec.Exec(ctx, e.execPath, nil, asEnv)
|
|
if err != nil {
|
|
return garmErrors.NewProviderError("provider binary %s returned error: %s", e.execPath, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (e *external) AsParams() params.Provider {
|
|
return params.Provider{
|
|
Name: e.cfg.Name,
|
|
Description: e.cfg.Description,
|
|
ProviderType: e.cfg.ProviderType,
|
|
}
|
|
}
|