diff --git a/v1/providers/sfcompute/client.go b/v1/providers/sfcompute/client.go index 7dc2031..1efa6e6 100644 --- a/v1/providers/sfcompute/client.go +++ b/v1/providers/sfcompute/client.go @@ -9,8 +9,10 @@ import ( sfcnodes "github.com/sfcompute/nodes-go" ) +// CloudProviderID deprecated: Use sfcomputev2.CloudProviderID instead. const CloudProviderID = "sfcompute" +// Deprecated: Use sfcomputev2.SFCCredentialV2 instead. type SFCCredential struct { RefID string APIKey string `json:"api_key"` @@ -18,6 +20,7 @@ type SFCCredential struct { var _ v1.CloudCredential = &SFCCredential{} +// Deprecated: Use sfcomputev2.NewSFCCredentialV2 instead. func NewSFCCredential(refID string, apiKey string) *SFCCredential { return &SFCCredential{ RefID: refID, @@ -42,6 +45,7 @@ func (c *SFCCredential) GetTenantID() (string, error) { return "", nil } +// Deprecated: Use sfcomputev2.SFCClientV2 instead. type SFCClient struct { v1.NotImplCloudClient refID string @@ -53,8 +57,10 @@ type SFCClient struct { var _ v1.CloudClient = &SFCClient{} +// Deprecated: Use sfcomputev2.SFCClientV2Option instead. type SFCClientOption func(c *SFCClient) +// Deprecated: Use sfcomputev2.WithLogger instead. func WithLogger(logger v1.Logger) SFCClientOption { return func(c *SFCClient) { c.logger = logger diff --git a/v1/providers/sfcompute/doc.go b/v1/providers/sfcompute/doc.go new file mode 100644 index 0000000..9d21dfa --- /dev/null +++ b/v1/providers/sfcompute/doc.go @@ -0,0 +1,5 @@ +// Package v1 implements the legacy SFCompute nodes API provider. +// +// Deprecated: Use github.com/brevdev/cloud/v1/providers/sfcomputev2 instead. +// This provider is retained for backward compatibility and is planned for removal. +package v1 diff --git a/v1/providers/sfcompute/instance.go b/v1/providers/sfcompute/instance.go index dfaf244..1e57a17 100644 --- a/v1/providers/sfcompute/instance.go +++ b/v1/providers/sfcompute/instance.go @@ -13,6 +13,7 @@ import ( v1 "github.com/brevdev/cloud/v1" sfcnodes "github.com/sfcompute/nodes-go" "github.com/sfcompute/nodes-go/packages/param" + "golang.org/x/crypto/ssh" ) const ( @@ -23,6 +24,11 @@ const ( ) func (c *SFCClient) CreateInstance(ctx context.Context, attrs v1.CreateInstanceAttrs) (*v1.Instance, error) { + cloudInit, err := sshKeyCloudInit(attrs.PublicKey) + if err != nil { + return nil, errors.WrapAndTrace(err) + } + // Get the zone for the location (do not include unavailable zones) zone, err := c.getZone(ctx, attrs.Location, false) if err != nil { @@ -41,7 +47,7 @@ func (c *SFCClient) CreateInstance(ctx context.Context, attrs v1.CreateInstanceA MaxPricePerNodeHour: maxPricePerNodeHour, Zone: zone.Name, Names: []string{name}, - CloudInitUserData: param.Opt[string]{Value: sshKeyCloudInit(attrs.PublicKey)}, + CloudInitUserData: param.Opt[string]{Value: cloudInit}, }, }) if err != nil { @@ -61,9 +67,14 @@ func (c *SFCClient) CreateInstance(ctx context.Context, attrs v1.CreateInstanceA return instance, nil } -func sshKeyCloudInit(sshKey string) string { +func sshKeyCloudInit(sshKey string) (string, error) { + key, _, _, _, err := ssh.ParseAuthorizedKey([]byte(sshKey)) + if err != nil { + return "", fmt.Errorf("invalid SSH public key: %w", err) + } + sshKey = strings.TrimSpace(string(ssh.MarshalAuthorizedKey(key))) script := fmt.Sprintf("#cloud-config\nssh_authorized_keys:\n - %s", sshKey) - return base64.StdEncoding.EncodeToString([]byte(script)) + return base64.StdEncoding.EncodeToString([]byte(script)), nil } func (c *SFCClient) GetInstance(ctx context.Context, id v1.CloudProviderInstanceID) (*v1.Instance, error) { diff --git a/v1/providers/sfcomputev2/instance.go b/v1/providers/sfcomputev2/instance.go index 34cf70a..a9953ea 100644 --- a/v1/providers/sfcomputev2/instance.go +++ b/v1/providers/sfcomputev2/instance.go @@ -8,11 +8,13 @@ import ( "net/http" "regexp" "slices" + "strings" "time" "github.com/alecthomas/units" "github.com/brevdev/cloud/internal/errors" v1 "github.com/brevdev/cloud/v1" + "golang.org/x/crypto/ssh" ) // SFC instance names must match `[a-zA-Z0-9][a-zA-Z0-9._-]{0,254}`: start with an @@ -41,6 +43,11 @@ func makeSFCName(refID string, tags v1.Tags) string { } func (c *SFCClientV2) CreateInstance(ctx context.Context, attrs v1.CreateInstanceAttrs) (*v1.Instance, error) { + cloudInit, err := sshKeyCloudInit(attrs.PublicKey) + if err != nil { + return nil, errors.WrapAndTrace(err) + } + c.logger.Debug(ctx, "sfcv2: CreateInstance start", v1.LogField("name", attrs.Name), v1.LogField("location", attrs.Location), @@ -57,7 +64,6 @@ func (c *SFCClientV2) CreateInstance(ctx context.Context, attrs v1.CreateInstanc return nil, errors.WrapAndTrace(err) } - cloudInit := sshKeyCloudInit(attrs.PublicKey) req := createInstanceRequest{ Pool: c.GetDefaultPoolResourcePath(), Image: c.GetDefaultImageResourcePath(), @@ -89,9 +95,14 @@ func (c *SFCClientV2) CreateInstance(ctx context.Context, attrs v1.CreateInstanc return instance, nil } -func sshKeyCloudInit(sshKey string) string { +func sshKeyCloudInit(sshKey string) (string, error) { + key, _, _, _, err := ssh.ParseAuthorizedKey([]byte(sshKey)) + if err != nil { + return "", fmt.Errorf("invalid SSH public key: %w", err) + } + sshKey = strings.TrimSpace(string(ssh.MarshalAuthorizedKey(key))) script := fmt.Sprintf("#cloud-config\nssh_authorized_keys:\n - %s", sshKey) - return base64.StdEncoding.EncodeToString([]byte(script)) + return base64.StdEncoding.EncodeToString([]byte(script)), nil } func (c *SFCClientV2) GetInstance(ctx context.Context, id v1.CloudProviderInstanceID) (*v1.Instance, error) {