435c0d9fc8
This PR switches the Nomad repository from using govendor to Go modules for managing dependencies. Aspects of the Nomad workflow remain pretty much the same. The usual Makefile targets should continue to work as they always did. The API submodule simply defers to the parent Nomad version on the repository, keeping the semantics of API versioning that currently exists.
269 lines
7.1 KiB
Go
269 lines
7.1 KiB
Go
//
|
|
// Copyright (c) 2018, Joyent, Inc. All rights reserved.
|
|
//
|
|
// 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 compute
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/url"
|
|
"path"
|
|
"time"
|
|
|
|
"github.com/joyent/triton-go/client"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
type ImagesClient struct {
|
|
client *client.Client
|
|
}
|
|
|
|
type ImageFile struct {
|
|
Compression string `json:"compression"`
|
|
SHA1 string `json:"sha1"`
|
|
Size int64 `json:"size"`
|
|
}
|
|
|
|
type Image struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
OS string `json:"os"`
|
|
Description string `json:"description"`
|
|
Version string `json:"version"`
|
|
Type string `json:"type"`
|
|
Requirements map[string]interface{} `json:"requirements"`
|
|
Homepage string `json:"homepage"`
|
|
Files []*ImageFile `json:"files"`
|
|
PublishedAt time.Time `json:"published_at"`
|
|
Owner string `json:"owner"`
|
|
Public bool `json:"public"`
|
|
State string `json:"state"`
|
|
Tags map[string]string `json:"tags"`
|
|
EULA string `json:"eula"`
|
|
ACL []string `json:"acl"`
|
|
}
|
|
|
|
type ListImagesInput struct {
|
|
Name string
|
|
OS string
|
|
Version string
|
|
Public bool
|
|
State string
|
|
Owner string
|
|
Type string
|
|
}
|
|
|
|
func (c *ImagesClient) List(ctx context.Context, input *ListImagesInput) ([]*Image, error) {
|
|
fullPath := path.Join("/", c.client.AccountName, "images")
|
|
|
|
query := &url.Values{}
|
|
if input.Name != "" {
|
|
query.Set("name", input.Name)
|
|
}
|
|
if input.OS != "" {
|
|
query.Set("os", input.OS)
|
|
}
|
|
if input.Version != "" {
|
|
query.Set("version", input.Version)
|
|
}
|
|
if input.Public {
|
|
query.Set("public", "true")
|
|
}
|
|
if input.State != "" {
|
|
query.Set("state", input.State)
|
|
}
|
|
if input.Owner != "" {
|
|
query.Set("owner", input.Owner)
|
|
}
|
|
if input.Type != "" {
|
|
query.Set("type", input.Type)
|
|
}
|
|
|
|
reqInputs := client.RequestInput{
|
|
Method: http.MethodGet,
|
|
Path: fullPath,
|
|
Query: query,
|
|
}
|
|
respReader, err := c.client.ExecuteRequestURIParams(ctx, reqInputs)
|
|
if respReader != nil {
|
|
defer respReader.Close()
|
|
}
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "unable to list images")
|
|
}
|
|
|
|
var result []*Image
|
|
decoder := json.NewDecoder(respReader)
|
|
if err = decoder.Decode(&result); err != nil {
|
|
return nil, errors.Wrap(err, "unable to decode list images response")
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
type GetImageInput struct {
|
|
ImageID string
|
|
}
|
|
|
|
func (c *ImagesClient) Get(ctx context.Context, input *GetImageInput) (*Image, error) {
|
|
fullPath := path.Join("/", c.client.AccountName, "images", input.ImageID)
|
|
reqInputs := client.RequestInput{
|
|
Method: http.MethodGet,
|
|
Path: fullPath,
|
|
}
|
|
respReader, err := c.client.ExecuteRequest(ctx, reqInputs)
|
|
if respReader != nil {
|
|
defer respReader.Close()
|
|
}
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "unable to get image")
|
|
}
|
|
|
|
var result *Image
|
|
decoder := json.NewDecoder(respReader)
|
|
if err = decoder.Decode(&result); err != nil {
|
|
return nil, errors.Wrap(err, "unable to decode get image response")
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
type DeleteImageInput struct {
|
|
ImageID string
|
|
}
|
|
|
|
func (c *ImagesClient) Delete(ctx context.Context, input *DeleteImageInput) error {
|
|
fullPath := path.Join("/", c.client.AccountName, "images", input.ImageID)
|
|
reqInputs := client.RequestInput{
|
|
Method: http.MethodDelete,
|
|
Path: fullPath,
|
|
}
|
|
respReader, err := c.client.ExecuteRequest(ctx, reqInputs)
|
|
if respReader != nil {
|
|
defer respReader.Close()
|
|
}
|
|
if err != nil {
|
|
return errors.Wrap(err, "unable to delete image")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type ExportImageInput struct {
|
|
ImageID string
|
|
MantaPath string
|
|
}
|
|
|
|
type MantaLocation struct {
|
|
MantaURL string `json:"manta_url"`
|
|
ImagePath string `json:"image_path"`
|
|
ManifestPath string `json:"manifest_path"`
|
|
}
|
|
|
|
func (c *ImagesClient) Export(ctx context.Context, input *ExportImageInput) (*MantaLocation, error) {
|
|
fullPath := path.Join("/", c.client.AccountName, "images", input.ImageID)
|
|
query := &url.Values{}
|
|
query.Set("action", "export")
|
|
|
|
reqInputs := client.RequestInput{
|
|
Method: http.MethodPost,
|
|
Path: fullPath,
|
|
Query: query,
|
|
}
|
|
respReader, err := c.client.ExecuteRequestURIParams(ctx, reqInputs)
|
|
if respReader != nil {
|
|
defer respReader.Close()
|
|
}
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "unable to export image")
|
|
}
|
|
|
|
var result *MantaLocation
|
|
decoder := json.NewDecoder(respReader)
|
|
if err = decoder.Decode(&result); err != nil {
|
|
return nil, errors.Wrap(err, "unable to decode export image response")
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
type CreateImageFromMachineInput struct {
|
|
MachineID string `json:"machine"`
|
|
Name string `json:"name"`
|
|
Version string `json:"version,omitempty"`
|
|
Description string `json:"description,omitempty"`
|
|
HomePage string `json:"homepage,omitempty"`
|
|
EULA string `json:"eula,omitempty"`
|
|
ACL []string `json:"acl,omitempty"`
|
|
Tags map[string]string `json:"tags,omitempty"`
|
|
}
|
|
|
|
func (c *ImagesClient) CreateFromMachine(ctx context.Context, input *CreateImageFromMachineInput) (*Image, error) {
|
|
fullPath := path.Join("/", c.client.AccountName, "images")
|
|
reqInputs := client.RequestInput{
|
|
Method: http.MethodPost,
|
|
Path: fullPath,
|
|
Body: input,
|
|
}
|
|
respReader, err := c.client.ExecuteRequest(ctx, reqInputs)
|
|
if respReader != nil {
|
|
defer respReader.Close()
|
|
}
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "unable to create machine from image")
|
|
}
|
|
|
|
var result *Image
|
|
decoder := json.NewDecoder(respReader)
|
|
if err = decoder.Decode(&result); err != nil {
|
|
return nil, errors.Wrap(err, "unable to decode create machine from image response")
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
type UpdateImageInput struct {
|
|
ImageID string `json:"-"`
|
|
Name string `json:"name,omitempty"`
|
|
Version string `json:"version,omitempty"`
|
|
Description string `json:"description,omitempty"`
|
|
HomePage string `json:"homepage,omitempty"`
|
|
EULA string `json:"eula,omitempty"`
|
|
ACL []string `json:"acl,omitempty"`
|
|
Tags map[string]string `json:"tags,omitempty"`
|
|
}
|
|
|
|
func (c *ImagesClient) Update(ctx context.Context, input *UpdateImageInput) (*Image, error) {
|
|
fullPath := path.Join("/", c.client.AccountName, "images", input.ImageID)
|
|
query := &url.Values{}
|
|
query.Set("action", "update")
|
|
|
|
reqInputs := client.RequestInput{
|
|
Method: http.MethodPost,
|
|
Path: fullPath,
|
|
Query: query,
|
|
Body: input,
|
|
}
|
|
respReader, err := c.client.ExecuteRequestURIParams(ctx, reqInputs)
|
|
if respReader != nil {
|
|
defer respReader.Close()
|
|
}
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "unable to update image")
|
|
}
|
|
|
|
var result *Image
|
|
decoder := json.NewDecoder(respReader)
|
|
if err = decoder.Decode(&result); err != nil {
|
|
return nil, errors.Wrap(err, "unable to decode update image response")
|
|
}
|
|
|
|
return result, nil
|
|
}
|