Skip to content

Commit 109a19b

Browse files
andreaanezclaude
andauthored
feat: add SFC V2 cloud provider integration (#116)
* feat: add SFC V2 cloud provider integration Introduces the sfcomputev2 provider package that talks to the V2 SFC API via github.com/sfcompute/sfc-go. Uses capacity-based slot tracking to report availability and native tags for instance metadata instead of name encoding. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(sfcomputev2): consolidate Brev-specific constants into brev_constants.go Moves tag keys, SSH defaults, and adds production capacity/image IDs to a dedicated file to separate Brev-specific config from generated SDK usage. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(sfcomputev2): clean up provider for correctness and consistency - Store user tags from attrs.Tags as real SFC V2 instance tags; populate v1.Instance.Tags on read, filtering internal Brev metadata keys - Remove CapacityID/ImageID from SFCCredentialV2 — pull from constants instead, with a TODO to source from env vars - Replace procurementTarget (Procurements.List) with currentCapacityAllocation (Capacities.Fetch + AllocationSchedule.Total) for available slot counting - Count all non-terminated instances (including failed) against capacity - Fix sfcInstanceToBrevInstance to use sfcLocation constant instead of c.location - Add CapabilityTags to declared capabilities - Remove legacy sfcNameToBrevData fallback logic (V1 and V2 are mutually exclusive) - Remove speculative stageTesting/stageProduction normalization; pass stage through raw - Add validation_test.go following the same pattern as the V1 provider Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore: swap sfc-go local replace for tagged release v0.1.0-preview Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent ffcb421 commit 109a19b

8 files changed

Lines changed: 663 additions & 0 deletions

File tree

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ require (
2222
github.com/nebius/gosdk v0.0.0-20250826102719-940ad1dfb5de
2323
github.com/pkg/errors v0.9.1
2424
github.com/sfcompute/nodes-go v0.1.0-alpha.4
25+
github.com/sfcompute/sfc-go v0.1.0-preview
2526
github.com/stretchr/testify v1.11.1
2627
golang.org/x/crypto v0.50.0
2728
golang.org/x/text v0.36.0
@@ -85,6 +86,7 @@ require (
8586
github.com/sirupsen/logrus v1.9.3 // indirect
8687
github.com/spf13/afero v1.15.0 // indirect
8788
github.com/spf13/pflag v1.0.10 // indirect
89+
github.com/spyzhov/ajson v0.8.0 // indirect
8890
github.com/tidwall/gjson v1.18.0 // indirect
8991
github.com/tidwall/match v1.1.1 // indirect
9092
github.com/tidwall/pretty v1.2.1 // indirect

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,12 +162,16 @@ github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0t
162162
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
163163
github.com/sfcompute/nodes-go v0.1.0-alpha.4 h1:oFBWcMPSpqLYm/NDs5I1jTvzgx9rsXDL9Ghsm30Hc0Q=
164164
github.com/sfcompute/nodes-go v0.1.0-alpha.4/go.mod h1:nUviHgK+Fgt2hDFcRL3M8VoyiypC8fc0dsY8C30QU8M=
165+
github.com/sfcompute/sfc-go v0.1.0-preview h1:yJ6ICglA/JZal2kauzb2aZlV9XdLPejsvFpsKwwThkQ=
166+
github.com/sfcompute/sfc-go v0.1.0-preview/go.mod h1:vhUpRpAHKitZzzWPg87RjreC+pzK57PGe4ZuSIQSk94=
165167
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
166168
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
167169
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
168170
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
169171
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
170172
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
173+
github.com/spyzhov/ajson v0.8.0 h1:sFXyMbi4Y/BKjrsfkUZHSjA2JM1184enheSjjoT/zCc=
174+
github.com/spyzhov/ajson v0.8.0/go.mod h1:63V+CGM6f1Bu/p4nLIN8885ojBdt88TbLoSFzyqMuVA=
171175
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
172176
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
173177
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package v2
2+
3+
// Package-internal constants — SSH defaults and internal tag keys.
4+
const (
5+
defaultPort = 22
6+
defaultSSHUsername = "ubuntu"
7+
8+
// Internal tag keys written to every SFCompute V2 instance. These are stripped from
9+
// v1.Instance.Tags on read so they don't surface as user-facing tags.
10+
tagKeyCloudCredRefID = "brev-cloud-cred-ref-id"
11+
tagKeyRefID = "brev-ref-id"
12+
)
13+
14+
// Brev environment config for SFCompute V2.
15+
// TODO: source these from environment variables rather than hardcoding them here.
16+
const (
17+
// BrevProductionCapacityID is the SFCompute V2 capacity ID for Brev production instances.
18+
BrevProductionCapacityID = "brev-production-capacity"
19+
20+
// BrevProductionImageID is the SFCompute image for Brev production instances
21+
// (ubuntu-24.04.4-cuda-12.8, vm_images.vm_image_id).
22+
BrevProductionImageID = "vmi_4GwEvmclFURy7ztFQjOdr"
23+
)
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package v2
2+
3+
import (
4+
"context"
5+
6+
v1 "github.com/brevdev/cloud/v1"
7+
)
8+
9+
func getSFCCapabilitiesV2() v1.Capabilities {
10+
return v1.Capabilities{
11+
v1.CapabilityCreateInstance,
12+
v1.CapabilityTerminateInstance,
13+
v1.CapabilityCreateTerminateInstance,
14+
v1.CapabilityTags,
15+
}
16+
}
17+
18+
func (c *SFCClientV2) GetCapabilities(_ context.Context) (v1.Capabilities, error) {
19+
return getSFCCapabilitiesV2(), nil
20+
}
21+
22+
func (c *SFCCredentialV2) GetCapabilities(_ context.Context) (v1.Capabilities, error) {
23+
return getSFCCapabilitiesV2(), nil
24+
}

v1/providers/sfcomputev2/client.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package v2
2+
3+
import (
4+
"context"
5+
6+
v1 "github.com/brevdev/cloud/v1"
7+
sfc "github.com/sfcompute/sfc-go"
8+
)
9+
10+
const CloudProviderID = "sfcompute"
11+
12+
// SFCCredentialV2 holds authentication details for a Brev-managed SFCompute V2 account.
13+
type SFCCredentialV2 struct {
14+
RefID string
15+
APIKey string `json:"api_key"`
16+
}
17+
18+
var _ v1.CloudCredential = &SFCCredentialV2{}
19+
20+
func NewSFCCredentialV2(refID, apiKey string) *SFCCredentialV2 {
21+
return &SFCCredentialV2{
22+
RefID: refID,
23+
APIKey: apiKey,
24+
}
25+
}
26+
27+
func (c *SFCCredentialV2) GetReferenceID() string {
28+
return c.RefID
29+
}
30+
31+
func (c *SFCCredentialV2) GetAPIType() v1.APIType {
32+
return v1.APITypeGlobal
33+
}
34+
35+
func (c *SFCCredentialV2) GetCloudProviderID() v1.CloudProviderID {
36+
return CloudProviderID
37+
}
38+
39+
func (c *SFCCredentialV2) GetTenantID() (string, error) {
40+
return "", nil
41+
}
42+
43+
type SFCClientV2 struct {
44+
v1.NotImplCloudClient
45+
refID string
46+
location string
47+
client *sfc.SDK
48+
logger v1.Logger
49+
}
50+
51+
var _ v1.CloudClient = &SFCClientV2{}
52+
53+
type SFCClientV2Option func(c *SFCClientV2)
54+
55+
func WithLogger(logger v1.Logger) SFCClientV2Option {
56+
return func(c *SFCClientV2) {
57+
c.logger = logger
58+
}
59+
}
60+
61+
func (c *SFCCredentialV2) MakeClientWithOptions(_ context.Context, location string, opts ...SFCClientV2Option) (v1.CloudClient, error) {
62+
sfcClient := &SFCClientV2{
63+
refID: c.RefID,
64+
location: location,
65+
client: sfc.New(sfc.WithSecurity(c.APIKey)),
66+
logger: &v1.NoopLogger{},
67+
}
68+
69+
for _, opt := range opts {
70+
opt(sfcClient)
71+
}
72+
73+
return sfcClient, nil
74+
}
75+
76+
func (c *SFCCredentialV2) MakeClient(ctx context.Context, location string) (v1.CloudClient, error) {
77+
return c.MakeClientWithOptions(ctx, location)
78+
}
79+
80+
func (c *SFCClientV2) GetAPIType() v1.APIType {
81+
return v1.APITypeGlobal
82+
}
83+
84+
func (c *SFCClientV2) GetCloudProviderID() v1.CloudProviderID {
85+
return CloudProviderID
86+
}
87+
88+
func (c *SFCClientV2) GetReferenceID() string {
89+
return c.refID
90+
}
91+
92+
func (c *SFCClientV2) GetTenantID() (string, error) {
93+
return "", nil
94+
}
95+
96+
func (c *SFCClientV2) MakeClient(_ context.Context, location string) (v1.CloudClient, error) {
97+
c.location = location
98+
return c, nil
99+
}

0 commit comments

Comments
 (0)