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
51 changes: 51 additions & 0 deletions commands/dedicated_inference.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,22 @@ For more information, see https://docs.digitalocean.com/reference/api/digitaloce
AddBoolFlag(cmdDelete, doctl.ArgForce, doctl.ArgShortForce, false, "Delete the dedicated inference endpoint without a confirmation prompt")
cmdDelete.Example = `The following example deletes a dedicated inference endpoint: doctl dedicated-inference delete 12345678-1234-1234-1234-123456789012`

cmdUpdate := CmdBuilder(
cmd,
RunDedicatedInferenceUpdate,
"update <dedicated-inference-id>",
"Update a dedicated inference endpoint",
`Updates a dedicated inference endpoint using a spec file in JSON or YAML format.
Use the `+"`"+`--spec`+"`"+` flag to provide the path to the updated spec file.
Optionally provide a Hugging Face access token using `+"`"+`--hugging-face-token`+"`"+`.`,
Writer,
aliasOpt("u"),
displayerType(&displayers.DedicatedInference{}),
)
AddStringFlag(cmdUpdate, doctl.ArgDedicatedInferenceSpec, "", "", `Path to a dedicated inference spec in JSON or YAML format. Set to "-" to read from stdin.`, requiredOpt())
AddStringFlag(cmdUpdate, doctl.ArgDedicatedInferenceHuggingFaceToken, "", "", "Hugging Face token for accessing gated models (optional)")
cmdUpdate.Example = `The following example updates a dedicated inference endpoint using a spec file: doctl dedicated-inference update 12345678-1234-1234-1234-123456789012 --spec spec.yaml`

cmdListAccelerators := CmdBuilder(
cmd,
RunDedicatedInferenceListAccelerators,
Expand Down Expand Up @@ -183,6 +199,41 @@ func RunDedicatedInferenceGet(c *CmdConfig) error {
return c.Display(&displayers.DedicatedInference{DedicatedInferences: do.DedicatedInferences{*endpoint}})
}

// RunDedicatedInferenceUpdate updates a dedicated inference endpoint.
func RunDedicatedInferenceUpdate(c *CmdConfig) error {
if len(c.Args) < 1 {
return doctl.NewMissingArgsErr(c.NS)
}
id := c.Args[0]

specPath, err := c.Doit.GetString(c.NS, doctl.ArgDedicatedInferenceSpec)
if err != nil {
return err
}

spec, err := readDedicatedInferenceSpec(os.Stdin, specPath)
if err != nil {
return err
}

req := &godo.DedicatedInferenceUpdateRequest{
Spec: spec,
}

hfToken, _ := c.Doit.GetString(c.NS, doctl.ArgDedicatedInferenceHuggingFaceToken)
if hfToken != "" {
req.Secrets = &godo.DedicatedInferenceSecrets{
HuggingFaceToken: hfToken,
}
}

endpoint, err := c.DedicatedInferences().Update(id, req)
if err != nil {
return err
}
return c.Display(&displayers.DedicatedInference{DedicatedInferences: do.DedicatedInferences{*endpoint}})
}

// RunDedicatedInferenceListAccelerators lists accelerators for a dedicated inference endpoint.
func RunDedicatedInferenceListAccelerators(c *CmdConfig) error {
if len(c.Args) < 1 {
Expand Down
88 changes: 88 additions & 0 deletions commands/dedicated_inference_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ func TestDedicatedInferenceCommand(t *testing.T) {
}
assert.True(t, subcommands["create"], "Expected create subcommand")
assert.True(t, subcommands["get"], "Expected get subcommand")
assert.True(t, subcommands["update"], "Expected update subcommand")
assert.True(t, subcommands["delete"], "Expected delete subcommand")
assert.True(t, subcommands["list-accelerators"], "Expected list-accelerators subcommand")
}
Expand Down Expand Up @@ -190,6 +191,93 @@ func TestRunDedicatedInferenceDelete_MissingID(t *testing.T) {
})
}

func TestRunDedicatedInferenceUpdate(t *testing.T) {
withTestClient(t, func(config *CmdConfig, tm *tcMocks) {
specJSON := `{
"version": 0,
"name": "test-dedicated-inference",
"region": "nyc2",
"vpc": {"uuid": "00000000-0000-4000-8000-000000000001"},
"enable_public_endpoint": true,
"model_deployments": [
{
"model_slug": "mistral/mistral-7b-instruct-v3",
"model_provider": "hugging_face",
"accelerators": [
{"scale": 2, "type": "prefill", "accelerator_slug": "gpu-mi300x1-192gb"},
{"scale": 4, "type": "decode", "accelerator_slug": "gpu-mi300x1-192gb"}
]
}
]
}`
tmpFile := t.TempDir() + "/spec.json"
err := os.WriteFile(tmpFile, []byte(specJSON), 0644)
assert.NoError(t, err)

config.Args = append(config.Args, "00000000-0000-4000-8000-000000000000")
config.Doit.Set(config.NS, doctl.ArgDedicatedInferenceSpec, tmpFile)

expectedReq := &godo.DedicatedInferenceUpdateRequest{
Spec: testDedicatedInferenceSpecRequest,
}

updatedDI := testDedicatedInference
tm.dedicatedInferences.EXPECT().Update("00000000-0000-4000-8000-000000000000", expectedReq).Return(&updatedDI, nil)

err = RunDedicatedInferenceUpdate(config)
assert.NoError(t, err)
})
}

func TestRunDedicatedInferenceUpdate_WithHuggingFaceToken(t *testing.T) {
withTestClient(t, func(config *CmdConfig, tm *tcMocks) {
specJSON := `{
"version": 0,
"name": "test-dedicated-inference",
"region": "nyc2",
"vpc": {"uuid": "00000000-0000-4000-8000-000000000001"},
"enable_public_endpoint": true,
"model_deployments": [
{
"model_slug": "mistral/mistral-7b-instruct-v3",
"model_provider": "hugging_face",
"accelerators": [
{"scale": 2, "type": "prefill", "accelerator_slug": "gpu-mi300x1-192gb"},
{"scale": 4, "type": "decode", "accelerator_slug": "gpu-mi300x1-192gb"}
]
}
]
}`
tmpFile := t.TempDir() + "/spec.json"
err := os.WriteFile(tmpFile, []byte(specJSON), 0644)
assert.NoError(t, err)

config.Args = append(config.Args, "00000000-0000-4000-8000-000000000000")
config.Doit.Set(config.NS, doctl.ArgDedicatedInferenceSpec, tmpFile)
config.Doit.Set(config.NS, doctl.ArgDedicatedInferenceHuggingFaceToken, "hf_test_token")

expectedReq := &godo.DedicatedInferenceUpdateRequest{
Spec: testDedicatedInferenceSpecRequest,
Secrets: &godo.DedicatedInferenceSecrets{
HuggingFaceToken: "hf_test_token",
},
}

updatedDI := testDedicatedInference
tm.dedicatedInferences.EXPECT().Update("00000000-0000-4000-8000-000000000000", expectedReq).Return(&updatedDI, nil)

err = RunDedicatedInferenceUpdate(config)
assert.NoError(t, err)
})
}

func TestRunDedicatedInferenceUpdate_MissingID(t *testing.T) {
withTestClient(t, func(config *CmdConfig, tm *tcMocks) {
err := RunDedicatedInferenceUpdate(config)
assert.Error(t, err)
})
}

func TestRunDedicatedInferenceListAccelerators(t *testing.T) {
withTestClient(t, func(config *CmdConfig, tm *tcMocks) {
testAccelerators := do.DedicatedInferenceAcceleratorInfos{
Expand Down
10 changes: 10 additions & 0 deletions do/dedicated_inference.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ type DedicatedInferenceAcceleratorInfos []DedicatedInferenceAcceleratorInfo
type DedicatedInferenceService interface {
Create(req *godo.DedicatedInferenceCreateRequest) (*DedicatedInference, *DedicatedInferenceToken, error)
Get(id string) (*DedicatedInference, error)
Update(id string, req *godo.DedicatedInferenceUpdateRequest) (*DedicatedInference, error)
Delete(id string) error
ListAccelerators(diID string, slug string) (DedicatedInferenceAcceleratorInfos, error)
}
Expand Down Expand Up @@ -83,6 +84,15 @@ func (s *dedicatedInferenceService) Get(id string) (*DedicatedInference, error)
return &DedicatedInference{DedicatedInference: d}, nil
}

// Update updates a dedicated inference endpoint by ID.
func (s *dedicatedInferenceService) Update(id string, req *godo.DedicatedInferenceUpdateRequest) (*DedicatedInference, error) {
d, _, err := s.client.DedicatedInference.Update(context.TODO(), id, req)
if err != nil {
return nil, err
}
return &DedicatedInference{DedicatedInference: d}, nil
}

// Delete deletes a dedicated inference endpoint by ID.
func (s *dedicatedInferenceService) Delete(id string) error {
_, err := s.client.DedicatedInference.Delete(context.TODO(), id)
Expand Down
15 changes: 15 additions & 0 deletions do/mocks/DedicatedInferenceService.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading