Skip to content

Commit 576dfcb

Browse files
authored
Merge branch 'scaleway:master' into master
2 parents bd93e35 + cd6f77f commit 576dfcb

19 files changed

+6351
-4
lines changed

cmd/scw/testdata/test-all-usage-inference-deployment-create-usage.golden

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@ ARGS:
1414
[tags.{index}] List of tags to apply to the deployment
1515
[min-size] Defines the minimum size of the pool
1616
[max-size] Defines the maximum size of the pool
17+
[endpoints.{index}.is-public=true] Will configure your public endpoint if true
1718
[endpoints.{index}.private-network.private-network-id]
1819
[endpoints.{index}.disable-auth=false] Disable the authentication on the endpoint.
1920
[quantization.bits] The number of bits each model parameter should be quantized to. The quantization method is chosen based on this value.
2021
[region=fr-par] Region to target. If none is passed will use default region from the config (fr-par)
2122

2223
FLAGS:
2324
-h, --help help for create
25+
-w, --wait wait until the deployment is ready
2426

2527
GLOBAL FLAGS:
2628
-c, --config string The path to the config file

cmd/scw/testdata/test-all-usage-inference-deployment-delete-usage.golden

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ ARGS:
1111

1212
FLAGS:
1313
-h, --help help for delete
14+
-w, --wait wait until the deployment is ready
1415

1516
GLOBAL FLAGS:
1617
-c, --config string The path to the config file

docs/commands/inference.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ scw inference deployment create [arg=value ...]
5050
| tags.{index} | | List of tags to apply to the deployment |
5151
| min-size | | Defines the minimum size of the pool |
5252
| max-size | | Defines the maximum size of the pool |
53+
| endpoints.{index}.is-public | Default: `true` | Will configure your public endpoint if true |
5354
| endpoints.{index}.private-network.private-network-id | | |
5455
| endpoints.{index}.disable-auth | Default: `false` | Disable the authentication on the endpoint. |
5556
| quantization.bits | | The number of bits each model parameter should be quantized to. The quantization method is chosen based on this value. |

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ require (
2121
github.com/karrick/tparse/v2 v2.8.2
2222
github.com/mattn/go-colorable v0.1.14
2323
github.com/mattn/go-isatty v0.0.20
24-
github.com/moby/buildkit v0.26.1
24+
github.com/moby/buildkit v0.26.2
2525
github.com/opencontainers/go-digest v1.0.0
2626
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.35.0.20251120105008-da92c1d23d16
2727
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -366,8 +366,8 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG
366366
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
367367
github.com/mitchellh/ioprogress v0.0.0-20180201004757-6a23b12fa88e h1:Qa6dnn8DlasdXRnacluu8HzPts0S1I9zvvUPDbBnXFI=
368368
github.com/mitchellh/ioprogress v0.0.0-20180201004757-6a23b12fa88e/go.mod h1:waEya8ee1Ro/lgxpVhkJI4BVASzkm3UZqkx/cFJiYHM=
369-
github.com/moby/buildkit v0.26.1 h1:Cf/AB/8/5N+GBQnVPBW+hR2tDWoImuZ28ciqaF+mzgs=
370-
github.com/moby/buildkit v0.26.1/go.mod h1:ylDa7IqzVJgLdi/wO7H1qLREFQpmhFbw2fbn4yoTw40=
369+
github.com/moby/buildkit v0.26.2 h1:EIh5j0gzRsCZmQzvgNNWzSDbuKqwUIiBH7ssqLv8RU8=
370+
github.com/moby/buildkit v0.26.2/go.mod h1:ylDa7IqzVJgLdi/wO7H1qLREFQpmhFbw2fbn4yoTw40=
371371
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
372372
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
373373
github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ=
Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,25 @@
11
package inference
22

3-
import "github.com/scaleway/scaleway-cli/v2/core"
3+
import (
4+
"github.com/scaleway/scaleway-cli/v2/core"
5+
"github.com/scaleway/scaleway-cli/v2/core/human"
6+
"github.com/scaleway/scaleway-sdk-go/api/inference/v1"
7+
)
48

59
func GetCommands() *core.Commands {
610
cmds := GetGeneratedCommands()
711

812
cmds.MustFind("inference").Groups = []string{"ai"}
913

14+
human.RegisterMarshalerFunc(
15+
inference.DeploymentStatus(""),
16+
human.EnumMarshalFunc(deploymentStateMarshalSpecs),
17+
)
18+
19+
human.RegisterMarshalerFunc(inference.Deployment{}, DeploymentMarshalerFunc)
20+
21+
cmds.MustFind("inference", "deployment", "create").Override(deploymentCreateBuilder)
22+
cmds.MustFind("inference", "deployment", "delete").Override(deploymentDeleteBuilder)
23+
1024
return cmds
1125
}
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
package inference
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
"net/http"
8+
"reflect"
9+
"time"
10+
11+
"github.com/fatih/color"
12+
"github.com/scaleway/scaleway-cli/v2/core"
13+
"github.com/scaleway/scaleway-cli/v2/core/human"
14+
"github.com/scaleway/scaleway-sdk-go/api/inference/v1"
15+
"github.com/scaleway/scaleway-sdk-go/scw"
16+
)
17+
18+
const (
19+
deploymentActionTimeout = 60 * time.Minute
20+
deploymentActionCreate = 1
21+
deploymentActionDelete = 2
22+
)
23+
24+
var deploymentStateMarshalSpecs = human.EnumMarshalSpecs{
25+
inference.DeploymentStatusCreating: &human.EnumMarshalSpec{Attribute: color.FgBlue},
26+
inference.DeploymentStatusDeploying: &human.EnumMarshalSpec{Attribute: color.FgBlue},
27+
inference.DeploymentStatusDeleting: &human.EnumMarshalSpec{Attribute: color.FgBlue},
28+
inference.DeploymentStatusError: &human.EnumMarshalSpec{Attribute: color.FgRed},
29+
inference.DeploymentStatusReady: &human.EnumMarshalSpec{Attribute: color.FgGreen},
30+
inference.DeploymentStatusLocked: &human.EnumMarshalSpec{Attribute: color.FgRed},
31+
}
32+
33+
func DeploymentMarshalerFunc(i any, opt *human.MarshalOpt) (string, error) {
34+
type tmp inference.Deployment
35+
deployment := tmp(i.(inference.Deployment))
36+
opt.Sections = []*human.MarshalSection{
37+
{
38+
FieldName: "Endpoints",
39+
Title: "Endpoints",
40+
},
41+
}
42+
str, err := human.Marshal(deployment, opt)
43+
if err != nil {
44+
return "", err
45+
}
46+
47+
return str, nil
48+
}
49+
50+
func deploymentDeleteBuilder(c *core.Command) *core.Command {
51+
c.WaitFunc = waitForDeploymentFunc(deploymentActionDelete)
52+
53+
return c
54+
}
55+
56+
func deploymentCreateBuilder(c *core.Command) *core.Command {
57+
type llmInferenceEndpointSpecCustom struct {
58+
*inference.EndpointSpec
59+
IsPublic bool `json:"is-public"`
60+
}
61+
62+
type llmInferenceCreateDeploymentRequestCustom struct {
63+
*inference.CreateDeploymentRequest
64+
Endpoints []*llmInferenceEndpointSpecCustom `json:"endpoints"`
65+
}
66+
67+
c.ArgSpecs.AddBefore("endpoints.{index}.private-network.private-network-id", &core.ArgSpec{
68+
Name: "endpoints.{index}.is-public",
69+
Short: "Will configure your public endpoint if true",
70+
Required: false,
71+
Default: core.DefaultValueSetter("true"),
72+
})
73+
74+
c.ArgsType = reflect.TypeOf(llmInferenceCreateDeploymentRequestCustom{})
75+
76+
c.WaitFunc = waitForDeploymentFunc(deploymentActionCreate)
77+
78+
c.Interceptor = func(ctx context.Context, argsI any, runner core.CommandRunner) (any, error) {
79+
deploymentCreateCustomRequest := argsI.(*llmInferenceCreateDeploymentRequestCustom)
80+
deploymentRequest := deploymentCreateCustomRequest.CreateDeploymentRequest
81+
if deploymentCreateCustomRequest.Endpoints == nil {
82+
publicEndpoint := &inference.EndpointPublicNetworkDetails{}
83+
endpoint := inference.EndpointSpec{
84+
PublicNetwork: publicEndpoint,
85+
PrivateNetwork: nil,
86+
DisableAuth: false,
87+
}
88+
deploymentRequest.Endpoints = append(deploymentRequest.Endpoints, &endpoint)
89+
90+
return runner(ctx, deploymentRequest)
91+
}
92+
for _, ep := range deploymentCreateCustomRequest.Endpoints {
93+
if ep.IsPublic {
94+
deploymentRequest.Endpoints = append(
95+
deploymentRequest.Endpoints,
96+
&inference.EndpointSpec{
97+
PublicNetwork: &inference.EndpointPublicNetworkDetails{},
98+
DisableAuth: ep.DisableAuth,
99+
},
100+
)
101+
}
102+
103+
if ep.PrivateNetwork != nil {
104+
deploymentRequest.Endpoints = append(
105+
deploymentRequest.Endpoints,
106+
&inference.EndpointSpec{
107+
PrivateNetwork: &inference.EndpointPrivateNetworkDetails{
108+
PrivateNetworkID: ep.PrivateNetwork.PrivateNetworkID,
109+
},
110+
DisableAuth: ep.DisableAuth,
111+
},
112+
)
113+
}
114+
}
115+
116+
return runner(ctx, deploymentRequest)
117+
}
118+
119+
return c
120+
}
121+
122+
func waitForDeploymentFunc(action int) core.WaitFunc {
123+
return func(ctx context.Context, _, respI any) (any, error) {
124+
deployment, err := inference.NewAPI(core.ExtractClient(ctx)).
125+
WaitForDeployment(&inference.WaitForDeploymentRequest{
126+
DeploymentID: respI.(*inference.Deployment).ID,
127+
Region: respI.(*inference.Deployment).Region,
128+
Timeout: scw.TimeDurationPtr(deploymentActionTimeout),
129+
RetryInterval: core.DefaultRetryInterval,
130+
})
131+
132+
switch action {
133+
case deploymentActionCreate:
134+
return deployment, err
135+
case deploymentActionDelete:
136+
if err != nil {
137+
// if we get a 404 here, it means the resource was successfully deleted
138+
notFoundError := &scw.ResourceNotFoundError{}
139+
responseError := &scw.ResponseError{}
140+
if errors.As(err, &responseError) &&
141+
responseError.StatusCode == http.StatusNotFound ||
142+
errors.As(err, &notFoundError) {
143+
return fmt.Sprintf(
144+
"Server %s successfully deleted.",
145+
respI.(*inference.Deployment).ID,
146+
), nil
147+
}
148+
}
149+
}
150+
151+
return nil, err
152+
}
153+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package inference_test
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/scaleway/scaleway-cli/v2/core"
8+
inference "github.com/scaleway/scaleway-cli/v2/internal/namespaces/inference/v1"
9+
"github.com/scaleway/scaleway-cli/v2/internal/namespaces/vpc/v2"
10+
)
11+
12+
const (
13+
ModelID = "739d51ae-4f1e-4193-a4bf-f7380c090d46"
14+
NodeTypeName = "H100-2"
15+
)
16+
17+
func Test_DeploymentCreate(t *testing.T) {
18+
cmds := inference.GetCommands()
19+
20+
t.Run("Simple deployment", core.Test(&core.TestConfig{
21+
Commands: cmds,
22+
Cmd: fmt.Sprintf(
23+
"scw inference deployment create node-type-name=%s model-id=%s",
24+
NodeTypeName,
25+
ModelID,
26+
),
27+
Check: core.TestCheckGolden(),
28+
AfterFunc: core.ExecAfterCmd(
29+
"scw inference deployment delete {{ .CmdResult.ID }}",
30+
),
31+
}))
32+
33+
t.Run("Deployment with wait flag", core.Test(&core.TestConfig{
34+
Commands: cmds,
35+
Cmd: fmt.Sprintf(
36+
"scw inference deployment create node-type-name=%s model-id=%s accept-eula=true --wait",
37+
NodeTypeName, ModelID,
38+
),
39+
Check: core.TestCheckGolden(),
40+
AfterFunc: core.ExecAfterCmd(
41+
"scw inference deployment delete {{ .CmdResult.ID }}",
42+
),
43+
}))
44+
45+
t.Run("Deployment with no endpoints must fail", core.Test(&core.TestConfig{
46+
Commands: cmds,
47+
Cmd: fmt.Sprintf(
48+
"scw inference deployment create node-type-name=%s model-id=%s endpoints.0.is-public=false",
49+
NodeTypeName,
50+
ModelID,
51+
),
52+
Check: core.TestCheckGolden(),
53+
}))
54+
}
55+
56+
func Test_CreateDeploymentPrivateEndpoint(t *testing.T) {
57+
cmds := inference.GetCommands()
58+
cmds.Merge(vpc.GetCommands())
59+
60+
t.Run("Create Deployment Private Endpoint", core.Test(&core.TestConfig{
61+
Commands: cmds,
62+
BeforeFunc: CreatePN(),
63+
Cmd: fmt.Sprintf(
64+
"scw inference deployment create model-id=%s node-type-name=H100-SXM-2 accept-eula=true endpoints.0.private-network.private-network-id={{ .PN.ID }}",
65+
ModelID,
66+
),
67+
Check: core.TestCheckCombine(
68+
core.TestCheckGolden(),
69+
),
70+
AfterFunc: core.AfterFuncCombine(
71+
core.ExecAfterCmd("scw inference deployment delete {{ .CmdResult.ID }} --wait"),
72+
DeletePrivateNetwork(),
73+
),
74+
}))
75+
}
76+
77+
func Test_DeploymentDelete(t *testing.T) {
78+
cmds := inference.GetCommands()
79+
80+
t.Run("Delete deployment with wait flag", core.Test(&core.TestConfig{
81+
Commands: cmds,
82+
BeforeFunc: CreateDeploymentPublicEndpoint(),
83+
Cmd: "scw inference deployment delete {{ .DEPLOYMENT.ID }} --wait",
84+
Check: core.TestCheckGolden(),
85+
}))
86+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package inference_test
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/scaleway/scaleway-cli/v2/core"
7+
)
8+
9+
func CreateDeploymentPublicEndpoint() core.BeforeFunc {
10+
return core.ExecStoreBeforeCmd(
11+
"DEPLOYMENT",
12+
fmt.Sprintf(
13+
"scw inference deployment create node-type-name=%s model-id=%s -w",
14+
NodeTypeName,
15+
ModelID,
16+
),
17+
)
18+
}
19+
20+
func CreatePN() core.BeforeFunc {
21+
return core.ExecStoreBeforeCmd(
22+
"PN",
23+
"scw vpc private-network create",
24+
)
25+
}
26+
27+
func DeletePrivateNetwork() core.AfterFunc {
28+
return core.ExecAfterCmd("scw vpc private-network delete {{ .PN.ID }}")
29+
}

0 commit comments

Comments
 (0)