Andrey Smirnov cde9b3954c
fix: update Talos version listing
Now Image Factory filters out pre-release versions for all releases but
the last one.

In the UI, now pre-release versions are shown.

Return proper 404 not found when someone requests something for
an unsupported version.

Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
2023-12-21 15:02:36 +04:00

153 lines
3.2 KiB
Go

// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Package client implements image factory HTTP API client.
package client
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"github.com/siderolabs/image-factory/pkg/schematic"
)
// ExtensionInfo defines extensions versions list response item.
type ExtensionInfo struct {
Name string `json:"name"`
Ref string `json:"ref"`
Digest string `json:"digest"`
}
// Client is the Image Factory HTTP API client.
type Client struct {
baseURL *url.URL
client http.Client
}
// New creates a new Image Factory API client.
func New(baseURL string, options ...Option) (*Client, error) {
opts := withDefaults(options)
bURL, err := url.Parse(baseURL)
if err != nil {
return nil, err
}
c := &Client{
baseURL: bURL,
client: opts.Client,
}
return c, nil
}
// SchematicCreate generates new schematic from the configuration.
func (c *Client) SchematicCreate(ctx context.Context, schematic schematic.Schematic) (string, error) {
data, err := schematic.Marshal()
if err != nil {
return "", err
}
var response struct {
ID string `json:"id"`
}
if err = c.do(ctx, http.MethodPost, "/schematics", data, &response, map[string]string{
"Content-Type": "application/yaml",
}); err != nil {
return "", err
}
return response.ID, nil
}
// Versions gets the list of Talos versions available.
func (c *Client) Versions(ctx context.Context) ([]string, error) {
var versions []string
if err := c.do(ctx, http.MethodGet, "/versions", nil, &versions, nil); err != nil {
return nil, err
}
return versions, nil
}
// ExtensionsVersions gets the version of the extension for a Talos version.
func (c *Client) ExtensionsVersions(ctx context.Context, talosVersion string) ([]ExtensionInfo, error) {
var versions []ExtensionInfo
if err := c.do(ctx, http.MethodGet, fmt.Sprintf("/version/%s/extensions/official", talosVersion), nil, &versions, nil); err != nil {
return nil, err
}
return versions, nil
}
func (c *Client) do(ctx context.Context, method, uri string, requestData []byte, responseData any, headers map[string]string) error {
var reader io.Reader
if requestData != nil {
reader = bytes.NewReader(requestData)
}
req, err := http.NewRequestWithContext(ctx, method, c.baseURL.JoinPath(uri).String(), reader)
if err != nil {
return err
}
for k, v := range headers {
req.Header.Add(k, v)
}
resp, err := c.client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close() //nolint:errcheck
if err = c.checkError(resp); err != nil {
return err
}
if responseData != nil {
decoder := json.NewDecoder(resp.Body)
return decoder.Decode(responseData)
}
return nil
}
func (c *Client) checkError(resp *http.Response) error {
const maxErrorBody = 8192
if resp.StatusCode < http.StatusBadRequest {
return nil
}
body, err := io.ReadAll(io.LimitReader(resp.Body, maxErrorBody))
if err != nil {
return err
}
err = &HTTPError{
Code: resp.StatusCode,
Message: string(body),
}
if resp.StatusCode == http.StatusBadRequest {
return &InvalidSchematicError{
e: err,
}
}
return err
}