garm/runner/providers/lxd/util.go
Gabriel Adrian Samfira da13cec2de Move code to external package
Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
2023-07-21 15:34:18 +00:00

235 lines
5.7 KiB
Go

// Copyright 2022 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 lxd
import (
"context"
"database/sql"
"fmt"
"log"
"net"
"net/http"
"os"
"strings"
"time"
commonParams "github.com/cloudbase/garm-provider-common/params"
"github.com/cloudbase/garm/config"
"github.com/cloudbase/garm/params"
"github.com/cloudbase/garm/util"
"github.com/juju/clock"
"github.com/juju/retry"
lxd "github.com/lxc/lxd/client"
"github.com/lxc/lxd/shared/api"
"github.com/pkg/errors"
)
var (
//lint:ignore ST1005 imported error from lxd
errInstanceIsStopped error = fmt.Errorf("The instance is already stopped")
)
var httpResponseErrors = map[int][]error{
http.StatusNotFound: {os.ErrNotExist, sql.ErrNoRows},
}
// isNotFoundError returns true if the error is considered a Not Found error.
func isNotFoundError(err error) bool {
if api.StatusErrorCheck(err, http.StatusNotFound) {
return true
}
for _, checkErr := range httpResponseErrors[http.StatusNotFound] {
if errors.Is(err, checkErr) {
return true
}
}
return false
}
func lxdInstanceToAPIInstance(instance *api.InstanceFull) params.Instance {
lxdOS, ok := instance.ExpandedConfig["image.os"]
if !ok {
log.Printf("failed to find OS in instance config")
}
osType, err := util.OSToOSType(lxdOS)
if err != nil {
log.Printf("failed to find OS type for OS %s", lxdOS)
}
if osType == "" {
osTypeFromTag, ok := instance.ExpandedConfig[osTypeKeyName]
if !ok {
log.Printf("failed to find OS type in fallback location")
}
osType = commonParams.OSType(osTypeFromTag)
}
osRelease, ok := instance.ExpandedConfig["image.release"]
if !ok {
log.Printf("failed to find OS release instance config")
}
state := instance.State
addresses := []commonParams.Address{}
if state.Network != nil {
for _, details := range state.Network {
for _, addr := range details.Addresses {
if addr.Scope != "global" {
continue
}
addresses = append(addresses, commonParams.Address{
Address: addr.Address,
Type: commonParams.PublicAddress,
})
}
}
}
instanceArch, ok := lxdToConfigArch[instance.Architecture]
if !ok {
log.Printf("failed to find OS architecture")
}
return params.Instance{
OSArch: instanceArch,
ProviderID: instance.Name,
Name: instance.Name,
OSType: osType,
OSName: strings.ToLower(lxdOS),
OSVersion: osRelease,
Addresses: addresses,
Status: lxdStatusToProviderStatus(state.Status),
}
}
func lxdStatusToProviderStatus(status string) commonParams.InstanceStatus {
switch status {
case "Running":
return commonParams.InstanceRunning
case "Stopped":
return commonParams.InstanceStopped
default:
return commonParams.InstanceStatusUnknown
}
}
func getClientFromConfig(ctx context.Context, cfg *config.LXD) (cli lxd.InstanceServer, err error) {
if cfg.UnixSocket != "" {
return lxd.ConnectLXDUnixWithContext(ctx, cfg.UnixSocket, nil)
}
var srvCrtContents, tlsCAContents, clientCertContents, clientKeyContents []byte
if cfg.TLSServerCert != "" {
srvCrtContents, err = os.ReadFile(cfg.TLSServerCert)
if err != nil {
return nil, errors.Wrap(err, "reading TLSServerCert")
}
}
if cfg.TLSCA != "" {
tlsCAContents, err = os.ReadFile(cfg.TLSCA)
if err != nil {
return nil, errors.Wrap(err, "reading TLSCA")
}
}
if cfg.ClientCertificate != "" {
clientCertContents, err = os.ReadFile(cfg.ClientCertificate)
if err != nil {
return nil, errors.Wrap(err, "reading ClientCertificate")
}
}
if cfg.ClientKey != "" {
clientKeyContents, err = os.ReadFile(cfg.ClientKey)
if err != nil {
return nil, errors.Wrap(err, "reading ClientKey")
}
}
connectArgs := lxd.ConnectionArgs{
TLSServerCert: string(srvCrtContents),
TLSCA: string(tlsCAContents),
TLSClientCert: string(clientCertContents),
TLSClientKey: string(clientKeyContents),
}
lxdCLI, err := lxd.ConnectLXD(cfg.URL, &connectArgs)
if err != nil {
return nil, errors.Wrap(err, "connecting to LXD")
}
return lxdCLI, nil
}
func projectName(cfg config.LXD) string {
if cfg.ProjectName != "" {
return cfg.ProjectName
}
return DefaultProjectName
}
func resolveArchitecture(osArch commonParams.OSArch) (string, error) {
if string(osArch) == "" {
return configToLXDArchMap[commonParams.Amd64], nil
}
arch, ok := configToLXDArchMap[osArch]
if !ok {
return "", fmt.Errorf("architecture %s is not supported", osArch)
}
return arch, nil
}
// waitDeviceActive is a function capable of figuring out when a Equinix Metal
// device is active
func (l *LXD) waitInstanceHasIP(ctx context.Context, instanceName string) (params.Instance, error) {
var p params.Instance
var errIPNotFound error = fmt.Errorf("ip not found")
err := retry.Call(retry.CallArgs{
Func: func() error {
var err error
p, err = l.GetInstance(ctx, instanceName)
if err != nil {
return errors.Wrap(err, "fetching instance")
}
for _, addr := range p.Addresses {
ip := net.ParseIP(addr.Address)
if ip == nil {
continue
}
if ip.To4() == nil {
continue
}
return nil
}
return errIPNotFound
},
Attempts: 20,
Delay: 5 * time.Second,
Clock: clock.WallClock,
})
if err != nil && err != errIPNotFound {
return params.Instance{}, err
}
return p, nil
}