package v2 import ( "context" "errors" "sort" "time" apiv2 "github.com/exoscale/egoscale/v2/api" "github.com/exoscale/egoscale/v2/oapi" ) // Template represents a Compute instance template. type Template struct { BootMode *string Build *string Checksum *string `req-for:"create"` CreatedAt *time.Time DefaultUser *string Description *string Family *string ID *string `req-for:"update,delete"` Maintainer *string Name *string `req-for:"create"` PasswordEnabled *bool `req-for:"create"` SSHKeyEnabled *bool `req-for:"create"` Size *int64 URL *string `req-for:"create"` Version *string Visibility *string Zone *string } // Implement sort.Interface for []*Template by CreatedAt or by Nane type Templates []*Template func (s Templates) Len() int { return len(s) } func (s Templates) Swap(i, j int) { s[i], s[j] = s[j], s[i] } type ByName struct{ Templates } func (s ByName) Less(i, j int) bool { return *s.Templates[i].Name < *s.Templates[j].Name } type ByCreatedAt struct{ Templates } func (s ByCreatedAt) Less(i, j int) bool { return s.Templates[i].CreatedAt.Before(*s.Templates[j].CreatedAt) } // ListTemplatesOpt represents an ListTemplates operation option. type ListTemplatesOpt func(params *oapi.ListTemplatesParams) // ListTemplatesWithFamily sets a family filter to list Templates with. func ListTemplatesWithFamily(v string) ListTemplatesOpt { return func(p *oapi.ListTemplatesParams) { if v != "" { p.Family = &v } } } // ListTemplatesWithVisibility sets a visibility filter to list Templates with (default: "public"). func ListTemplatesWithVisibility(v string) ListTemplatesOpt { return func(p *oapi.ListTemplatesParams) { if v != "" { p.Visibility = (*oapi.ListTemplatesParamsVisibility)(&v) } } } func templateFromAPI(t *oapi.Template, zone string) *Template { return &Template{ BootMode: (*string)(t.BootMode), Build: t.Build, Checksum: t.Checksum, CreatedAt: t.CreatedAt, DefaultUser: t.DefaultUser, Description: t.Description, Family: t.Family, ID: t.Id, Maintainer: t.Maintainer, Name: t.Name, PasswordEnabled: t.PasswordEnabled, SSHKeyEnabled: t.SshKeyEnabled, Size: t.Size, URL: t.Url, Version: t.Version, Visibility: (*string)(t.Visibility), Zone: &zone, } } // CopyTemplate copies a Template to a different Exoscale zone. func (c *Client) CopyTemplate(ctx context.Context, zone string, template *Template, dstZone string) (*Template, error) { if err := validateOperationParams(template, "update"); err != nil { return nil, err } resp, err := c.CopyTemplateWithResponse( apiv2.WithZone(ctx, zone), *template.ID, oapi.CopyTemplateJSONRequestBody{TargetZone: oapi.Zone{Name: (*oapi.ZoneName)(&dstZone)}}, ) if err != nil { return nil, err } res, err := oapi.NewPoller(). WithTimeout(c.timeout). WithInterval(c.pollInterval). Poll(ctx, oapi.OperationPoller(c, zone, *resp.JSON200.Id)) if err != nil { return nil, err } return c.GetTemplate(ctx, dstZone, *res.(*struct { Command *string `json:"command,omitempty"` Id *string `json:"id,omitempty"` // revive:disable-line Link *string `json:"link,omitempty"` }).Id) } // DeleteTemplate deletes a Template. func (c *Client) DeleteTemplate(ctx context.Context, zone string, template *Template) error { if err := validateOperationParams(template, "delete"); err != nil { return err } resp, err := c.DeleteTemplateWithResponse(apiv2.WithZone(ctx, zone), *template.ID) if err != nil { return err } _, err = oapi.NewPoller(). WithTimeout(c.timeout). WithInterval(c.pollInterval). Poll(ctx, oapi.OperationPoller(c, zone, *resp.JSON200.Id)) if err != nil { return err } return nil } // GetTemplate returns the Template corresponding to the specified ID. func (c *Client) GetTemplate(ctx context.Context, zone, id string) (*Template, error) { resp, err := c.GetTemplateWithResponse(apiv2.WithZone(ctx, zone), id) if err != nil { return nil, err } return templateFromAPI(resp.JSON200, zone), nil } // GetTemplateByName returns the newest Template corresponding to the specified Name. func (c *Client) GetTemplateByName(ctx context.Context, zone string, templateName string, visibility string) (*Template, error) { templates, err := c.ListTemplates(ctx, zone, ListTemplatesWithVisibility(visibility)) if err != nil { return nil, err } // Newest first (multiple private templates can have the same name) sort.Sort(sort.Reverse(ByCreatedAt{templates})) for _, template := range templates { if *template.Name == templateName { return template, nil } } return nil, apiv2.ErrNotFound } // FindTemplate attempts to find a template by name or ID. // In case the identifier is a name and multiple resources match, the newest template is returned. func (c *Client) FindTemplate(ctx context.Context, zone, x string, visibilty string) (*Template, error) { // Check if template is referenced by ID. template, err := c.GetTemplate(ctx, zone, x) if err != nil { // ErrInvalidRequest when we pass an invalid id (should be a UUID) // We can check if template is referenced by Name. if errors.Is(err, apiv2.ErrInvalidRequest) || errors.Is(err, apiv2.ErrNotFound) { template, err = c.GetTemplateByName(ctx, zone, x, visibilty) if err == nil { return template, nil } } return nil, err } return template, nil } // ListTemplates returns the list of existing Templates. func (c *Client) ListTemplates(ctx context.Context, zone string, opts ...ListTemplatesOpt) ([]*Template, error) { list := make([]*Template, 0) defaultVisibility := oapi.TemplateVisibilityPublic params := oapi.ListTemplatesParams{ Visibility: (*oapi.ListTemplatesParamsVisibility)(&defaultVisibility), } for _, opt := range opts { opt(¶ms) } resp, err := c.ListTemplatesWithResponse(apiv2.WithZone(ctx, zone), ¶ms) if err != nil { return nil, err } if resp.JSON200.Templates != nil { for i := range *resp.JSON200.Templates { list = append(list, templateFromAPI(&(*resp.JSON200.Templates)[i], zone)) } } return list, nil } // RegisterTemplate registers a new Template. func (c *Client) RegisterTemplate(ctx context.Context, zone string, template *Template) (*Template, error) { if err := validateOperationParams(template, "create"); err != nil { return nil, err } resp, err := c.RegisterTemplateWithResponse( apiv2.WithZone(ctx, zone), oapi.RegisterTemplateJSONRequestBody{ BootMode: (*oapi.RegisterTemplateJSONBodyBootMode)(template.BootMode), Build: template.Build, Checksum: *template.Checksum, DefaultUser: template.DefaultUser, Description: template.Description, Maintainer: template.Maintainer, Name: *template.Name, PasswordEnabled: *template.PasswordEnabled, SshKeyEnabled: *template.SSHKeyEnabled, Url: *template.URL, Version: template.Version, }) if err != nil { return nil, err } res, err := oapi.NewPoller(). WithTimeout(c.timeout). WithInterval(c.pollInterval). Poll(ctx, oapi.OperationPoller(c, zone, *resp.JSON200.Id)) if err != nil { return nil, err } return c.GetTemplate(ctx, zone, *res.(*struct { Command *string `json:"command,omitempty"` Id *string `json:"id,omitempty"` // revive:disable-line Link *string `json:"link,omitempty"` }).Id) } // UpdateTemplate updates a Template. func (c *Client) UpdateTemplate(ctx context.Context, zone string, template *Template) error { if err := validateOperationParams(template, "update"); err != nil { return err } resp, err := c.UpdateTemplateWithResponse( apiv2.WithZone(ctx, zone), *template.ID, oapi.UpdateTemplateJSONRequestBody{ Description: oapi.NilableString(template.Description), Name: template.Name, }) if err != nil { return err } _, err = oapi.NewPoller(). WithTimeout(c.timeout). WithInterval(c.pollInterval). Poll(ctx, oapi.OperationPoller(c, zone, *resp.JSON200.Id)) if err != nil { return err } return nil }