mirror of
https://github.com/edgelesssys/constellation.git
synced 2024-12-29 09:26:17 -05:00
111 lines
2.9 KiB
Go
111 lines
2.9 KiB
Go
|
/*
|
||
|
Copyright (c) Edgeless Systems GmbH
|
||
|
|
||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||
|
*/
|
||
|
|
||
|
package fetcher
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"encoding/json"
|
||
|
"fmt"
|
||
|
"net/http"
|
||
|
|
||
|
"github.com/edgelesssys/constellation/v2/internal/versionsapi"
|
||
|
)
|
||
|
|
||
|
// Fetcher fetches versions API resources.
|
||
|
//
|
||
|
// The fetcher is used to get information from the versions API without having to
|
||
|
// authenticate with AWS. It is the interface that should be used in user-facing
|
||
|
// application code most of the time.
|
||
|
type Fetcher struct {
|
||
|
httpc httpc
|
||
|
}
|
||
|
|
||
|
// NewFetcher returns a new Fetcher.
|
||
|
func NewFetcher() *Fetcher {
|
||
|
return &Fetcher{
|
||
|
httpc: http.DefaultClient,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// FetchVersionList fetches the given version list from the versions API.
|
||
|
func (f *Fetcher) FetchVersionList(ctx context.Context, list versionsapi.List) (versionsapi.List, error) {
|
||
|
return fetch(ctx, f.httpc, list)
|
||
|
}
|
||
|
|
||
|
// FetchVersionLatest fetches the latest version from the versions API.
|
||
|
func (f *Fetcher) FetchVersionLatest(ctx context.Context, latest versionsapi.Latest) (versionsapi.Latest, error) {
|
||
|
return fetch(ctx, f.httpc, latest)
|
||
|
}
|
||
|
|
||
|
// FetchImageInfo fetches the given image info from the versions API.
|
||
|
func (f *Fetcher) FetchImageInfo(ctx context.Context, imageInfo versionsapi.ImageInfo) (versionsapi.ImageInfo, error) {
|
||
|
return fetch(ctx, f.httpc, imageInfo)
|
||
|
}
|
||
|
|
||
|
type apiObject interface {
|
||
|
ValidateRequest() error
|
||
|
Validate() error
|
||
|
URL() (string, error)
|
||
|
}
|
||
|
|
||
|
func fetch[T apiObject](ctx context.Context, c httpc, obj T) (T, error) {
|
||
|
if err := obj.ValidateRequest(); err != nil {
|
||
|
return *new(T), fmt.Errorf("validating request for %T: %w", obj, err)
|
||
|
}
|
||
|
|
||
|
url, err := obj.URL()
|
||
|
if err != nil {
|
||
|
return *new(T), fmt.Errorf("getting URL for %T: %w", obj, err)
|
||
|
}
|
||
|
|
||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, http.NoBody)
|
||
|
if err != nil {
|
||
|
return *new(T), fmt.Errorf("creating request for %T: %w", obj, err)
|
||
|
}
|
||
|
|
||
|
resp, err := c.Do(req)
|
||
|
if err != nil {
|
||
|
return *new(T), fmt.Errorf("sending request for %T: %w", obj, err)
|
||
|
}
|
||
|
defer resp.Body.Close()
|
||
|
switch resp.StatusCode {
|
||
|
case http.StatusOK:
|
||
|
case http.StatusNotFound:
|
||
|
return *new(T), &NotFoundError{fmt.Errorf("requesting resource at %s returned status code 404", url)}
|
||
|
default:
|
||
|
return *new(T), fmt.Errorf("unexpected status code %d while requesting resource", resp.StatusCode)
|
||
|
}
|
||
|
|
||
|
var newObj T
|
||
|
if err := json.NewDecoder(resp.Body).Decode(&newObj); err != nil {
|
||
|
return *new(T), fmt.Errorf("decoding %T: %w", obj, err)
|
||
|
}
|
||
|
|
||
|
if newObj.Validate() != nil {
|
||
|
return *new(T), fmt.Errorf("received invalid %T: %w", newObj, newObj.Validate())
|
||
|
}
|
||
|
|
||
|
return newObj, nil
|
||
|
}
|
||
|
|
||
|
// NotFoundError is an error that is returned when a resource is not found.
|
||
|
type NotFoundError struct {
|
||
|
err error
|
||
|
}
|
||
|
|
||
|
func (e *NotFoundError) Error() string {
|
||
|
return fmt.Sprintf("the requested resource was not found: %s", e.err.Error())
|
||
|
}
|
||
|
|
||
|
func (e *NotFoundError) Unwrap() error {
|
||
|
return e.err
|
||
|
}
|
||
|
|
||
|
type httpc interface {
|
||
|
Do(req *http.Request) (*http.Response, error)
|
||
|
}
|