Skip to content
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### Bug fixes

- compute: allow instance and instance pool operations to use template IDs omitted from template lists (#888)
- compute: honor the selected template's minimum disk size when creating instances and instance pools (#882)
- instance create: apply delete protection in the instance's zone, fixing a wrong-zone "Not Found" error when creating protected instances outside the default zone (#879)
- sks cluster update: fix oidc config drop (#881)
Expand Down
13 changes: 2 additions & 11 deletions cmd/compute/instance/instance_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,18 +89,9 @@ func (c *instanceCreateCmd) CmdRun(cmd *cobra.Command, _ []string) error { //nol
return err
}

templates, err := client.ListTemplates(ctx, v3.ListTemplatesWithVisibility(v3.ListTemplatesVisibility(c.TemplateVisibility)))
template, err := compute.ResolveTemplate(ctx, client, c.Template, c.TemplateVisibility, c.Zone)
if err != nil {
return fmt.Errorf("error listing template with visibility %q: %w", c.TemplateVisibility, err)
}
template, err := templates.FindTemplate(c.Template)
if err != nil {
return fmt.Errorf(
"no template %q found with visibility %s in zone %s",
c.Template,
c.TemplateVisibility,
c.Zone,
)
return err
}
diskSize, err := compute.ResolveTemplateDiskSize(
c.DiskSize,
Expand Down
24 changes: 9 additions & 15 deletions cmd/compute/instance/instance_reset.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/spf13/cobra"

exocmd "github.com/exoscale/cli/cmd"
"github.com/exoscale/cli/cmd/compute"
"github.com/exoscale/cli/pkg/globalstate"
"github.com/exoscale/cli/pkg/output"
"github.com/exoscale/cli/utils"
Expand Down Expand Up @@ -77,24 +78,17 @@ func (c *instanceResetCmd) CmdRun(_ *cobra.Command, _ []string) error {
}

if c.Template != "" {

templates, err := client.ListTemplates(ctx, v3.ListTemplatesWithVisibility(v3.ListTemplatesVisibility(c.TemplateVisibility)))

templateID, err := compute.ResolveTemplateID(
ctx,
client,
c.Template,
c.TemplateVisibility,
v3.ZoneName(c.Zone),
)
if err != nil {
return err
}

template, err := templates.FindTemplate(c.Template)
if err != nil {
return fmt.Errorf(
"no template %q found with visibility %s in zone %s",
c.Template,
c.TemplateVisibility,
c.Zone,
)
}

request.Template = &template
request.Template = &v3.Template{ID: templateID}
}

op, err := client.ResetInstance(ctx, instance.ID, request)
Expand Down
13 changes: 2 additions & 11 deletions cmd/compute/instance_pool/instance_pool_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,18 +71,9 @@ func (c *instancePoolCreateCmd) CmdRun(cmd *cobra.Command, _ []string) error {
return err
}

templates, err := client.ListTemplates(ctx, v3.ListTemplatesWithVisibility(v3.ListTemplatesVisibility(c.TemplateVisibility)))
template, err := compute.ResolveTemplate(ctx, client, c.Template, c.TemplateVisibility, c.Zone)
if err != nil {
return fmt.Errorf("error listing template with visibility %q: %w", c.TemplateVisibility, err)
}
template, err := templates.FindTemplate(c.Template)
if err != nil {
return fmt.Errorf(
"no template %q found with visibility %s in zone %s",
c.Template,
c.TemplateVisibility,
c.Zone,
)
return err
}
diskSize, err := compute.ResolveTemplateDiskSize(
c.DiskSize,
Expand Down
16 changes: 4 additions & 12 deletions cmd/compute/instance_pool/instance_pool_update.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/spf13/cobra"

exocmd "github.com/exoscale/cli/cmd"
"github.com/exoscale/cli/cmd/compute"
"github.com/exoscale/cli/pkg/globalstate"
"github.com/exoscale/cli/pkg/output"
"github.com/exoscale/cli/pkg/userdata"
Expand Down Expand Up @@ -259,20 +260,11 @@ func (c *instancePoolUpdateCmd) CmdRun(cmd *cobra.Command, _ []string) error { /
}

if cmd.Flags().Changed(exocmd.MustCLICommandFlagName(c, &c.Template)) {
templates, err := client.ListTemplates(ctx, v3.ListTemplatesWithVisibility(v3.ListTemplatesVisibility(c.TemplateVisibility)))
templateID, err := compute.ResolveTemplateID(ctx, client, c.Template, c.TemplateVisibility, c.Zone)
if err != nil {
return fmt.Errorf("error listing template with visibility %q: %w", c.TemplateVisibility, err)
}
template, err := templates.FindTemplate(c.Template)
if err != nil {
return fmt.Errorf(
"no template %q found with visibility %s in zone %s",
c.Template,
c.TemplateVisibility,
c.Zone,
)
return err
}
updateReq.Template = &template
updateReq.Template = &v3.Template{ID: templateID}
updated = true
}

Expand Down
62 changes: 62 additions & 0 deletions cmd/compute/template.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package compute

import (
"context"
"fmt"

v3 "github.com/exoscale/egoscale/v3"
)

// ResolveTemplate resolves names and retrieves UUID metadata when available.
func ResolveTemplate(
ctx context.Context,
client *v3.Client,
nameOrID string,
visibility string,
zone v3.ZoneName,
) (*v3.Template, error) {
if id, err := v3.ParseUUID(nameOrID); err == nil {
template, err := client.GetTemplate(ctx, id)
Comment thread
kobajagi marked this conversation as resolved.
if err != nil {
// Metadata is optional for UUIDs; the mutation API remains authoritative.
return &v3.Template{ID: id}, nil
}
return template, nil
}

templates, err := client.ListTemplates(ctx, v3.ListTemplatesWithVisibility(v3.ListTemplatesVisibility(visibility)))
if err != nil {
return nil, fmt.Errorf("error listing template with visibility %q: %w", visibility, err)
}
template, err := templates.FindTemplate(nameOrID)
if err != nil {
return nil, fmt.Errorf(
"no template %q found with visibility %s in zone %s",
nameOrID,
visibility,
zone,
)
}

return &template, nil
}

// ResolveTemplateID passes UUIDs through and resolves template names from the list endpoint.
func ResolveTemplateID(
ctx context.Context,
client *v3.Client,
nameOrID string,
visibility string,
zone v3.ZoneName,
) (v3.UUID, error) {
if id, err := v3.ParseUUID(nameOrID); err == nil {
return id, nil
}

template, err := ResolveTemplate(ctx, client, nameOrID, visibility, zone)
if err != nil {
return "", err
}

return template.ID, nil
}
76 changes: 76 additions & 0 deletions cmd/compute/template_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package compute

import (
"context"
"net/http"
"net/http/httptest"
"testing"

v3 "github.com/exoscale/egoscale/v3"
"github.com/exoscale/egoscale/v3/credentials"
)

func TestResolveTemplateIDPassesUUIDThrough(t *testing.T) {
const id = "5b473841-fd33-4b88-bcfe-a776abf15034"

got, err := ResolveTemplateID(context.Background(), nil, id, "public", "ch-gva-2")
if err != nil {
t.Fatalf("ResolveTemplateID() error = %v", err)
}
if got != v3.UUID(id) {
t.Errorf("ResolveTemplateID() = %q, want %q", got, id)
}
}

func TestResolveTemplateGetsOptionalUUIDMetadata(t *testing.T) {
const id = "5b473841-fd33-4b88-bcfe-a776abf15034"

tests := []struct {
name string
status int
response string
wantSize int64
}{
{
name: "metadata available",
status: http.StatusOK,
response: `{"id":"5b473841-fd33-4b88-bcfe-a776abf15034","size":85899345920}`,
wantSize: 80 << 30,
},
{
name: "metadata unavailable",
status: http.StatusNotFound,
response: `{"message":"not found"}`,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet || r.URL.Path != "/template/"+id {
t.Errorf("request = %s %s, want GET /template/%s", r.Method, r.URL.Path, id)
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(tt.status)
_, _ = w.Write([]byte(tt.response))
}))
defer server.Close()

client, err := v3.NewClient(
credentials.NewStaticCredentials("key", "secret"),
v3.ClientOptWithEndpoint(v3.Endpoint(server.URL)),
)
if err != nil {
t.Fatalf("NewClient() error = %v", err)
}

got, err := ResolveTemplate(context.Background(), client, id, "public", "ch-gva-2")
if err != nil {
t.Fatalf("ResolveTemplate() error = %v", err)
}
if got.ID != v3.UUID(id) || got.Size != tt.wantSize {
t.Errorf("ResolveTemplate() = {ID: %q, Size: %d}, want {ID: %q, Size: %d}", got.ID, got.Size, id, tt.wantSize)
}
})
}
}