Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/validation-sfcompute.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,11 @@ jobs:
SFCOMPUTE_API_KEY: ${{ secrets.SFCOMPUTE_API_KEY }}
TEST_PRIVATE_KEY_BASE64: ${{ secrets.TEST_PRIVATE_KEY_BASE64 }}
TEST_PUBLIC_KEY_BASE64: ${{ secrets.TEST_PUBLIC_KEY_BASE64 }}
SFCOMPUTE_ORGANIZATION: ${{ secrets.SFCOMPUTE_ORGANIZATION }}
SFCOMPUTE_WORKSPACE: ${{ secrets.SFCOMPUTE_WORKSPACE }}
VALIDATION_TEST: true
run: |
cd v1/providers/sfcompute
cd v1/providers/sfcomputev2
go test -v -short=false -timeout=30m ./...

- name: Upload test results
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ require (
github.com/nebius/gosdk v0.0.0-20250826102719-940ad1dfb5de
github.com/pkg/errors v0.9.1
github.com/sfcompute/nodes-go v0.1.0-alpha.4
github.com/sfcompute/sfc-go v0.1.0-preview
github.com/stretchr/testify v1.11.1
golang.org/x/crypto v0.50.0
golang.org/x/text v0.36.0
Expand Down Expand Up @@ -85,6 +86,7 @@ require (
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/spyzhov/ajson v0.8.0 // indirect
github.com/tidwall/gjson v1.18.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -162,12 +162,16 @@ github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0t
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/sfcompute/nodes-go v0.1.0-alpha.4 h1:oFBWcMPSpqLYm/NDs5I1jTvzgx9rsXDL9Ghsm30Hc0Q=
github.com/sfcompute/nodes-go v0.1.0-alpha.4/go.mod h1:nUviHgK+Fgt2hDFcRL3M8VoyiypC8fc0dsY8C30QU8M=
github.com/sfcompute/sfc-go v0.1.0-preview h1:yJ6ICglA/JZal2kauzb2aZlV9XdLPejsvFpsKwwThkQ=
github.com/sfcompute/sfc-go v0.1.0-preview/go.mod h1:vhUpRpAHKitZzzWPg87RjreC+pzK57PGe4ZuSIQSk94=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spyzhov/ajson v0.8.0 h1:sFXyMbi4Y/BKjrsfkUZHSjA2JM1184enheSjjoT/zCc=
github.com/spyzhov/ajson v0.8.0/go.mod h1:63V+CGM6f1Bu/p4nLIN8885ojBdt88TbLoSFzyqMuVA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
Expand Down
29 changes: 29 additions & 0 deletions v1/providers/sfcomputev2/brev_constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package v2

import "fmt"

// Package-internal constants — SSH defaults and internal tag keys.
const (
defaultPort = 22
defaultSSHUsername = "ubuntu"

// Internal tag keys written to every SFCompute V2 instance. These are stripped from
// v1.Instance.Tags on read so they don't surface as user-facing tags.
tagKeyCloudCredRefID = "brev-cloud-cred-ref-id" //nolint:gosec // not a secret
tagKeyRefID = "brev-ref-id"

// Brev environment config for SFCompute V2.
brevDefaultImageResourcePath = "sfc:image:sfcompute:public:ubuntu-24.04.4-cuda-12.8"
)

func (c *SFCClientV2) GetDefaultCapacityResourcePath() string {
return fmt.Sprintf("sfc:capacity:%s:%s:brev-default-capacity", c.organization, c.workspace)
}

func (c *SFCClientV2) GetWorkspaceResourcePath() string {
return fmt.Sprintf("sfc:workspace:%s:%s", c.organization, c.workspace)
}

func (c *SFCClientV2) GetDefaultImageResourcePath() string {
return brevDefaultImageResourcePath
}
24 changes: 24 additions & 0 deletions v1/providers/sfcomputev2/capabilities.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package v2

import (
"context"

v1 "github.com/brevdev/cloud/v1"
)

func getSFCCapabilitiesV2() v1.Capabilities {
return v1.Capabilities{
v1.CapabilityCreateInstance,
v1.CapabilityTerminateInstance,
v1.CapabilityCreateTerminateInstance,
v1.CapabilityTags,
}
}

func (c *SFCClientV2) GetCapabilities(_ context.Context) (v1.Capabilities, error) {
return getSFCCapabilitiesV2(), nil
}

func (c *SFCCredentialV2) GetCapabilities(_ context.Context) (v1.Capabilities, error) {
return getSFCCapabilitiesV2(), nil
}
115 changes: 115 additions & 0 deletions v1/providers/sfcomputev2/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package v2

import (
"context"

v1 "github.com/brevdev/cloud/v1"
sfc "github.com/sfcompute/sfc-go"
)

const CloudProviderID = "sfcompute"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok if this collides with the existing cloudcred?


// SFCCredentialV2 holds authentication details for a Brev-managed SFCompute V2 account.
type SFCCredentialV2 struct {
RefID string
APIKey string `json:"api_key"`
Organization string `json:"organization"`
Workspace string `json:"workspace"`
}

var _ v1.CloudCredential = &SFCCredentialV2{}

func NewSFCCredentialV2(refID string, apiKey string, organization string, workspace string) *SFCCredentialV2 {
return &SFCCredentialV2{
RefID: refID,
APIKey: apiKey,
Organization: organization,
Workspace: workspace,
}
}

func (c *SFCCredentialV2) GetReferenceID() string {
return c.RefID
}

func (c *SFCCredentialV2) GetAPIType() v1.APIType {
return v1.APITypeGlobal
}

func (c *SFCCredentialV2) GetCloudProviderID() v1.CloudProviderID {
return CloudProviderID
}

func (c *SFCCredentialV2) GetTenantID() (string, error) {
return "", nil
}

type SFCClientV2 struct {
v1.NotImplCloudClient
refID string
organization string
workspace string
location string
client *sfc.SDK
logger v1.Logger
}

var _ v1.CloudClient = &SFCClientV2{}

type SFCClientV2Option func(c *SFCClientV2)

func WithLogger(logger v1.Logger) SFCClientV2Option {
return func(c *SFCClientV2) {
c.logger = logger
}
}

func (c *SFCCredentialV2) MakeClientWithOptions(_ context.Context, location string, opts ...SFCClientV2Option) (v1.CloudClient, error) {
sfcClient := &SFCClientV2{
refID: c.RefID,
organization: c.Organization,
workspace: c.Workspace,
location: location,
client: sfc.New(sfc.WithSecurity(c.APIKey)),
logger: &v1.NoopLogger{},
}

for _, opt := range opts {
opt(sfcClient)
}

return sfcClient, nil
}

func (c *SFCCredentialV2) MakeClient(ctx context.Context, location string) (v1.CloudClient, error) {
return c.MakeClientWithOptions(ctx, location)
}

func (c *SFCClientV2) GetAPIType() v1.APIType {
return v1.APITypeGlobal
}

func (c *SFCClientV2) GetCloudProviderID() v1.CloudProviderID {
return CloudProviderID
}

func (c *SFCClientV2) GetOrganization() string {
return c.organization
}

func (c *SFCClientV2) GetWorkspace() string {
return c.workspace
}

func (c *SFCClientV2) GetReferenceID() string {
return c.refID
}

func (c *SFCClientV2) GetTenantID() (string, error) {
return "", nil
}

func (c *SFCClientV2) MakeClient(_ context.Context, location string) (v1.CloudClient, error) {
c.location = location
return c, nil
}
Loading
Loading