diff --git a/src/cmd/linuxkit/run.go b/src/cmd/linuxkit/run.go index 7e2e9c465..d8caf3091 100644 --- a/src/cmd/linuxkit/run.go +++ b/src/cmd/linuxkit/run.go @@ -22,6 +22,7 @@ func runUsage() { fmt.Printf(" gcp\n") fmt.Printf(" hyperkit [macOS]\n") fmt.Printf(" hyperv [Windows]\n") + fmt.Printf(" openstack\n") fmt.Printf(" packet\n") fmt.Printf(" qemu [linux]\n") fmt.Printf(" vcenter\n") @@ -54,6 +55,8 @@ func run(args []string) { runHyperKit(args[1:]) case "hyperv": runHyperV(args[1:]) + case "openstack": + runOpenStack(args[1:]) case "packet": runPacket(args[1:]) case "qemu": diff --git a/src/cmd/linuxkit/run_openstack.go b/src/cmd/linuxkit/run_openstack.go new file mode 100644 index 000000000..b78078ca8 --- /dev/null +++ b/src/cmd/linuxkit/run_openstack.go @@ -0,0 +1,88 @@ +package main + +import ( + "flag" + "fmt" + "os" + "path/filepath" + + log "github.com/Sirupsen/logrus" + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack" + "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" +) + +const ( + defaultOSFlavor = "m1.tiny" + authurlVar = "OS_AUTH_URL" + usernameVar = "OS_USERNAME" + passwordVar = "OS_PASSWORD" + projectNameVar = "OS_PROJECT_NAME" + userDomainVar = "OS_USER_DOMAIN_NAME" +) + +func runOpenStack(args []string) { + flags := flag.NewFlagSet("openstack", flag.ExitOnError) + invoked := filepath.Base(os.Args[0]) + flags.Usage = func() { + fmt.Printf("USAGE: %s run openstack [options]\n\n", invoked) + flags.PrintDefaults() + } + authurlFlag := flags.String("authurl", "", "The URL of the OpenStack identity service, i.e https://keystone.example.com:5000/v3") + usernameFlag := flags.String("username", "", "Username with permissions to create an instance") + passwordFlag := flags.String("password", "", "Password for the specified username") + projectNameFlag := flags.String("project", "", "Name of the Project (aka Tenant) to be used") + userDomainFlag := flags.String("domain", "Default", "Domain name") + imageID := flags.String("img-ID", "", "The ID of the image to boot the instance from") + networkID := flags.String("network", "", "The ID of the network to attach the instance to") + flavorName := flags.String("flavor", defaultOSFlavor, "Instance size (flavor)") + name := flags.String("name", "", "Name of the instance") + + if err := flags.Parse(args); err != nil { + log.Fatal("Unable to parse args") + } + + authurl := getStringValue(authurlVar, *authurlFlag, "") + username := getStringValue(usernameVar, *usernameFlag, "") + password := getStringValue(passwordVar, *passwordFlag, "") + projectName := getStringValue(projectNameVar, *projectNameFlag, "") + userDomain := getStringValue(userDomainVar, *userDomainFlag, "") + + authOpts := gophercloud.AuthOptions{ + IdentityEndpoint: authurl, + Username: username, + Password: password, + DomainName: userDomain, + TenantName: projectName, + } + provider, err := openstack.AuthenticatedClient(authOpts) + if err != nil { + log.Fatalf("Failed to authenticate") + } + + client, err := openstack.NewComputeV2(provider, gophercloud.EndpointOpts{}) + if err != nil { + log.Fatalf("Unable to create Compute V2 client, %s", err) + } + + network := servers.Network{ + UUID: *networkID, + } + + serverOpts := &servers.CreateOpts{ + FlavorName: *flavorName, + ImageRef: *imageID, + Name: *name, + Networks: []servers.Network{network}, + ServiceClient: client, + } + + server, err := servers.Create(client, serverOpts).Extract() + if err != nil { + log.Fatalf("Unable to create server: %s", err) + } + + servers.WaitForStatus(client, server.ID, "ACTIVE", 600) + fmt.Printf("Server created, UUID is %s", server.ID) + +} diff --git a/src/cmd/linuxkit/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/doc.go b/src/cmd/linuxkit/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/doc.go new file mode 100644 index 000000000..5822e1bcf --- /dev/null +++ b/src/cmd/linuxkit/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/doc.go @@ -0,0 +1,7 @@ +// Package flavors provides information and interaction with the flavor API +// resource in the OpenStack Compute service. +// +// A flavor is an available hardware configuration for a server. Each flavor +// has a unique combination of disk space, memory capacity and priority for CPU +// time. +package flavors diff --git a/src/cmd/linuxkit/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/requests.go b/src/cmd/linuxkit/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/requests.go new file mode 100644 index 000000000..03d7e8724 --- /dev/null +++ b/src/cmd/linuxkit/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/requests.go @@ -0,0 +1,163 @@ +package flavors + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToFlavorListQuery() (string, error) +} + +// AccessType maps to OpenStack's Flavor.is_public field. Although the is_public field is boolean, the +// request options are ternary, which is why AccessType is a string. The following values are +// allowed: +// +// PublicAccess (the default): Returns public flavors and private flavors associated with that project. +// PrivateAccess (admin only): Returns private flavors, across all projects. +// AllAccess (admin only): Returns public and private flavors across all projects. +// +// The AccessType arguement is optional, and if it is not supplied, OpenStack returns the PublicAccess +// flavors. +type AccessType string + +const ( + PublicAccess AccessType = "true" + PrivateAccess AccessType = "false" + AllAccess AccessType = "None" +) + +// ListOpts helps control the results returned by the List() function. +// For example, a flavor with a minDisk field of 10 will not be returned if you specify MinDisk set to 20. +// Typically, software will use the last ID of the previous call to List to set the Marker for the current call. +type ListOpts struct { + + // ChangesSince, if provided, instructs List to return only those things which have changed since the timestamp provided. + ChangesSince string `q:"changes-since"` + + // MinDisk and MinRAM, if provided, elides flavors which do not meet your criteria. + MinDisk int `q:"minDisk"` + MinRAM int `q:"minRam"` + + // Marker and Limit control paging. + // Marker instructs List where to start listing from. + Marker string `q:"marker"` + + // Limit instructs List to refrain from sending excessively large lists of flavors. + Limit int `q:"limit"` + + // AccessType, if provided, instructs List which set of flavors to return. If IsPublic not provided, + // flavors for the current project are returned. + AccessType AccessType `q:"is_public"` +} + +// ToFlavorListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToFlavorListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// ListDetail instructs OpenStack to provide a list of flavors. +// You may provide criteria by which List curtails its results for easier processing. +// See ListOpts for more details. +func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(client) + if opts != nil { + query, err := opts.ToFlavorListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return FlavorPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +type CreateOptsBuilder interface { + ToFlavorCreateMap() (map[string]interface{}, error) +} + +// CreateOpts is passed to Create to create a flavor +// Source: +// https://github.com/openstack/nova/blob/stable/newton/nova/api/openstack/compute/schemas/flavor_manage.py#L20 +type CreateOpts struct { + Name string `json:"name" required:"true"` + // memory size, in MBs + RAM int `json:"ram" required:"true"` + VCPUs int `json:"vcpus" required:"true"` + // disk size, in GBs + Disk *int `json:"disk" required:"true"` + ID string `json:"id,omitempty"` + // non-zero, positive + Swap *int `json:"swap,omitempty"` + RxTxFactor float64 `json:"rxtx_factor,omitempty"` + IsPublic *bool `json:"os-flavor-access:is_public,omitempty"` + // ephemeral disk size, in GBs, non-zero, positive + Ephemeral *int `json:"OS-FLV-EXT-DATA:ephemeral,omitempty"` +} + +// ToFlavorCreateMap satisfies the CreateOptsBuilder interface +func (opts *CreateOpts) ToFlavorCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "flavor") +} + +// Create a flavor +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToFlavorCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 201}, + }) + return +} + +// Get instructs OpenStack to provide details on a single flavor, identified by its ID. +// Use ExtractFlavor to convert its result into a Flavor. +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = client.Get(getURL(client, id), &r.Body, nil) + return +} + +// IDFromName is a convienience function that returns a flavor's ID given its name. +func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { + count := 0 + id := "" + allPages, err := ListDetail(client, nil).AllPages() + if err != nil { + return "", err + } + + all, err := ExtractFlavors(allPages) + if err != nil { + return "", err + } + + for _, f := range all { + if f.Name == name { + count++ + id = f.ID + } + } + + switch count { + case 0: + err := &gophercloud.ErrResourceNotFound{} + err.ResourceType = "flavor" + err.Name = name + return "", err + case 1: + return id, nil + default: + err := &gophercloud.ErrMultipleResourcesFound{} + err.ResourceType = "flavor" + err.Name = name + err.Count = count + return "", err + } +} diff --git a/src/cmd/linuxkit/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/results.go b/src/cmd/linuxkit/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/results.go new file mode 100644 index 000000000..121abbb8d --- /dev/null +++ b/src/cmd/linuxkit/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/results.go @@ -0,0 +1,115 @@ +package flavors + +import ( + "encoding/json" + "strconv" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +type commonResult struct { + gophercloud.Result +} + +type CreateResult struct { + commonResult +} + +// GetResult temporarily holds the response from a Get call. +type GetResult struct { + commonResult +} + +// Extract provides access to the individual Flavor returned by the Get and Create functions. +func (r commonResult) Extract() (*Flavor, error) { + var s struct { + Flavor *Flavor `json:"flavor"` + } + err := r.ExtractInto(&s) + return s.Flavor, err +} + +// Flavor records represent (virtual) hardware configurations for server resources in a region. +type Flavor struct { + // The Id field contains the flavor's unique identifier. + // For example, this identifier will be useful when specifying which hardware configuration to use for a new server instance. + ID string `json:"id"` + // The Disk and RA< fields provide a measure of storage space offered by the flavor, in GB and MB, respectively. + Disk int `json:"disk"` + RAM int `json:"ram"` + // The Name field provides a human-readable moniker for the flavor. + Name string `json:"name"` + RxTxFactor float64 `json:"rxtx_factor"` + // Swap indicates how much space is reserved for swap. + // If not provided, this field will be set to 0. + Swap int `json:"swap"` + // VCPUs indicates how many (virtual) CPUs are available for this flavor. + VCPUs int `json:"vcpus"` + // IsPublic indicates whether the flavor is public. + IsPublic bool `json:"is_public"` +} + +func (r *Flavor) UnmarshalJSON(b []byte) error { + type tmp Flavor + var s struct { + tmp + Swap interface{} `json:"swap"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + + *r = Flavor(s.tmp) + + switch t := s.Swap.(type) { + case float64: + r.Swap = int(t) + case string: + switch t { + case "": + r.Swap = 0 + default: + swap, err := strconv.ParseFloat(t, 64) + if err != nil { + return err + } + r.Swap = int(swap) + } + } + + return nil +} + +// FlavorPage contains a single page of the response from a List call. +type FlavorPage struct { + pagination.LinkedPageBase +} + +// IsEmpty determines if a page contains any results. +func (page FlavorPage) IsEmpty() (bool, error) { + flavors, err := ExtractFlavors(page) + return len(flavors) == 0, err +} + +// NextPageURL uses the response's embedded link reference to navigate to the next page of results. +func (page FlavorPage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"flavors_links"` + } + err := page.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// ExtractFlavors provides access to the list of flavors in a page acquired from the List operation. +func ExtractFlavors(r pagination.Page) ([]Flavor, error) { + var s struct { + Flavors []Flavor `json:"flavors"` + } + err := (r.(FlavorPage)).ExtractInto(&s) + return s.Flavors, err +} diff --git a/src/cmd/linuxkit/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/urls.go b/src/cmd/linuxkit/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/urls.go new file mode 100644 index 000000000..2fc21796f --- /dev/null +++ b/src/cmd/linuxkit/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/urls.go @@ -0,0 +1,17 @@ +package flavors + +import ( + "github.com/gophercloud/gophercloud" +) + +func getURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("flavors", id) +} + +func listURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("flavors", "detail") +} + +func createURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("flavors") +} diff --git a/src/cmd/linuxkit/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/doc.go b/src/cmd/linuxkit/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/doc.go new file mode 100644 index 000000000..0edaa3f02 --- /dev/null +++ b/src/cmd/linuxkit/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/doc.go @@ -0,0 +1,7 @@ +// Package images provides information and interaction with the image API +// resource in the OpenStack Compute service. +// +// An image is a collection of files used to create or rebuild a server. +// Operators provide a number of pre-built OS images by default. You may also +// create custom images from cloud servers you have launched. +package images diff --git a/src/cmd/linuxkit/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/requests.go b/src/cmd/linuxkit/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/requests.go new file mode 100644 index 000000000..df9f1da8f --- /dev/null +++ b/src/cmd/linuxkit/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/requests.go @@ -0,0 +1,102 @@ +package images + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToImageListQuery() (string, error) +} + +// ListOpts contain options for limiting the number of Images returned from a call to ListDetail. +type ListOpts struct { + // When the image last changed status (in date-time format). + ChangesSince string `q:"changes-since"` + // The number of Images to return. + Limit int `q:"limit"` + // UUID of the Image at which to set a marker. + Marker string `q:"marker"` + // The name of the Image. + Name string `q:"name"` + // The name of the Server (in URL format). + Server string `q:"server"` + // The current status of the Image. + Status string `q:"status"` + // The value of the type of image (e.g. BASE, SERVER, ALL) + Type string `q:"type"` +} + +// ToImageListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToImageListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// ListDetail enumerates the available images. +func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listDetailURL(client) + if opts != nil { + query, err := opts.ToImageListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return ImagePage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Get acquires additional detail about a specific image by ID. +// Use ExtractImage() to interpret the result as an openstack Image. +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = client.Get(getURL(client, id), &r.Body, nil) + return +} + +// Delete deletes the specified image ID. +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = client.Delete(deleteURL(client, id), nil) + return +} + +// IDFromName is a convienience function that returns an image's ID given its name. +func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { + count := 0 + id := "" + allPages, err := ListDetail(client, nil).AllPages() + if err != nil { + return "", err + } + + all, err := ExtractImages(allPages) + if err != nil { + return "", err + } + + for _, f := range all { + if f.Name == name { + count++ + id = f.ID + } + } + + switch count { + case 0: + err := &gophercloud.ErrResourceNotFound{} + err.ResourceType = "image" + err.Name = name + return "", err + case 1: + return id, nil + default: + err := &gophercloud.ErrMultipleResourcesFound{} + err.ResourceType = "image" + err.Name = name + err.Count = count + return "", err + } +} diff --git a/src/cmd/linuxkit/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/results.go b/src/cmd/linuxkit/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/results.go new file mode 100644 index 000000000..f9ebc69e9 --- /dev/null +++ b/src/cmd/linuxkit/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/results.go @@ -0,0 +1,83 @@ +package images + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// GetResult temporarily stores a Get response. +type GetResult struct { + gophercloud.Result +} + +// DeleteResult represents the result of an image.Delete operation. +type DeleteResult struct { + gophercloud.ErrResult +} + +// Extract interprets a GetResult as an Image. +func (r GetResult) Extract() (*Image, error) { + var s struct { + Image *Image `json:"image"` + } + err := r.ExtractInto(&s) + return s.Image, err +} + +// Image is used for JSON (un)marshalling. +// It provides a description of an OS image. +type Image struct { + // ID contains the image's unique identifier. + ID string + + Created string + + // MinDisk and MinRAM specify the minimum resources a server must provide to be able to install the image. + MinDisk int + MinRAM int + + // Name provides a human-readable moniker for the OS image. + Name string + + // The Progress and Status fields indicate image-creation status. + // Any usable image will have 100% progress. + Progress int + Status string + + Updated string + + Metadata map[string]interface{} +} + +// ImagePage contains a single page of results from a List operation. +// Use ExtractImages to convert it into a slice of usable structs. +type ImagePage struct { + pagination.LinkedPageBase +} + +// IsEmpty returns true if a page contains no Image results. +func (page ImagePage) IsEmpty() (bool, error) { + images, err := ExtractImages(page) + return len(images) == 0, err +} + +// NextPageURL uses the response's embedded link reference to navigate to the next page of results. +func (page ImagePage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"images_links"` + } + err := page.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// ExtractImages converts a page of List results into a slice of usable Image structs. +func ExtractImages(r pagination.Page) ([]Image, error) { + var s struct { + Images []Image `json:"images"` + } + err := (r.(ImagePage)).ExtractInto(&s) + return s.Images, err +} diff --git a/src/cmd/linuxkit/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/urls.go b/src/cmd/linuxkit/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/urls.go new file mode 100644 index 000000000..57787fb72 --- /dev/null +++ b/src/cmd/linuxkit/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/urls.go @@ -0,0 +1,15 @@ +package images + +import "github.com/gophercloud/gophercloud" + +func listDetailURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("images", "detail") +} + +func getURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("images", id) +} + +func deleteURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("images", id) +} diff --git a/src/cmd/linuxkit/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/doc.go b/src/cmd/linuxkit/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/doc.go new file mode 100644 index 000000000..fe4567120 --- /dev/null +++ b/src/cmd/linuxkit/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/doc.go @@ -0,0 +1,6 @@ +// Package servers provides information and interaction with the server API +// resource in the OpenStack Compute service. +// +// A server is a virtual machine instance in the compute system. In order for +// one to be provisioned, a valid flavor and image are required. +package servers diff --git a/src/cmd/linuxkit/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/errors.go b/src/cmd/linuxkit/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/errors.go new file mode 100644 index 000000000..c9f0e3c20 --- /dev/null +++ b/src/cmd/linuxkit/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/errors.go @@ -0,0 +1,71 @@ +package servers + +import ( + "fmt" + + "github.com/gophercloud/gophercloud" +) + +// ErrNeitherImageIDNorImageNameProvided is the error when neither the image +// ID nor the image name is provided for a server operation +type ErrNeitherImageIDNorImageNameProvided struct{ gophercloud.ErrMissingInput } + +func (e ErrNeitherImageIDNorImageNameProvided) Error() string { + return "One and only one of the image ID and the image name must be provided." +} + +// ErrNeitherFlavorIDNorFlavorNameProvided is the error when neither the flavor +// ID nor the flavor name is provided for a server operation +type ErrNeitherFlavorIDNorFlavorNameProvided struct{ gophercloud.ErrMissingInput } + +func (e ErrNeitherFlavorIDNorFlavorNameProvided) Error() string { + return "One and only one of the flavor ID and the flavor name must be provided." +} + +type ErrNoClientProvidedForIDByName struct{ gophercloud.ErrMissingInput } + +func (e ErrNoClientProvidedForIDByName) Error() string { + return "A service client must be provided to find a resource ID by name." +} + +// ErrInvalidHowParameterProvided is the error when an unknown value is given +// for the `how` argument +type ErrInvalidHowParameterProvided struct{ gophercloud.ErrInvalidInput } + +// ErrNoAdminPassProvided is the error when an administrative password isn't +// provided for a server operation +type ErrNoAdminPassProvided struct{ gophercloud.ErrMissingInput } + +// ErrNoImageIDProvided is the error when an image ID isn't provided for a server +// operation +type ErrNoImageIDProvided struct{ gophercloud.ErrMissingInput } + +// ErrNoIDProvided is the error when a server ID isn't provided for a server +// operation +type ErrNoIDProvided struct{ gophercloud.ErrMissingInput } + +// ErrServer is a generic error type for servers HTTP operations. +type ErrServer struct { + gophercloud.ErrUnexpectedResponseCode + ID string +} + +func (se ErrServer) Error() string { + return fmt.Sprintf("Error while executing HTTP request for server [%s]", se.ID) +} + +// Error404 overrides the generic 404 error message. +func (se ErrServer) Error404(e gophercloud.ErrUnexpectedResponseCode) error { + se.ErrUnexpectedResponseCode = e + return &ErrServerNotFound{se} +} + +// ErrServerNotFound is the error when a 404 is received during server HTTP +// operations. +type ErrServerNotFound struct { + ErrServer +} + +func (e ErrServerNotFound) Error() string { + return fmt.Sprintf("I couldn't find server [%s]", e.ID) +} diff --git a/src/cmd/linuxkit/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/requests.go b/src/cmd/linuxkit/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/requests.go new file mode 100644 index 000000000..1a7b3ec77 --- /dev/null +++ b/src/cmd/linuxkit/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/requests.go @@ -0,0 +1,744 @@ +package servers + +import ( + "encoding/base64" + "encoding/json" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/compute/v2/flavors" + "github.com/gophercloud/gophercloud/openstack/compute/v2/images" + "github.com/gophercloud/gophercloud/pagination" +) + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToServerListQuery() (string, error) +} + +// ListOpts allows the filtering and sorting of paginated collections through +// the API. Filtering is achieved by passing in struct field values that map to +// the server attributes you want to see returned. Marker and Limit are used +// for pagination. +type ListOpts struct { + // A time/date stamp for when the server last changed status. + ChangesSince string `q:"changes-since"` + + // Name of the image in URL format. + Image string `q:"image"` + + // Name of the flavor in URL format. + Flavor string `q:"flavor"` + + // Name of the server as a string; can be queried with regular expressions. + // Realize that ?name=bob returns both bob and bobb. If you need to match bob + // only, you can use a regular expression matching the syntax of the + // underlying database server implemented for Compute. + Name string `q:"name"` + + // Value of the status of the server so that you can filter on "ACTIVE" for example. + Status string `q:"status"` + + // Name of the host as a string. + Host string `q:"host"` + + // UUID of the server at which you want to set a marker. + Marker string `q:"marker"` + + // Integer value for the limit of values to return. + Limit int `q:"limit"` + + // Bool to show all tenants + AllTenants bool `q:"all_tenants"` + + // List servers for a particular tenant. Setting "AllTenants = true" is required. + TenantID string `q:"tenant_id"` +} + +// ToServerListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToServerListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List makes a request against the API to list servers accessible to you. +func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listDetailURL(client) + if opts != nil { + query, err := opts.ToServerListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return ServerPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// CreateOptsBuilder describes struct types that can be accepted by the Create call. +// The CreateOpts struct in this package does. +type CreateOptsBuilder interface { + ToServerCreateMap() (map[string]interface{}, error) +} + +// Network is used within CreateOpts to control a new server's network attachments. +type Network struct { + // UUID of a nova-network to attach to the newly provisioned server. + // Required unless Port is provided. + UUID string + + // Port of a neutron network to attach to the newly provisioned server. + // Required unless UUID is provided. + Port string + + // FixedIP [optional] specifies a fixed IPv4 address to be used on this network. + FixedIP string +} + +// Personality is an array of files that are injected into the server at launch. +type Personality []*File + +// File is used within CreateOpts and RebuildOpts to inject a file into the server at launch. +// File implements the json.Marshaler interface, so when a Create or Rebuild operation is requested, +// json.Marshal will call File's MarshalJSON method. +type File struct { + // Path of the file + Path string + // Contents of the file. Maximum content size is 255 bytes. + Contents []byte +} + +// MarshalJSON marshals the escaped file, base64 encoding the contents. +func (f *File) MarshalJSON() ([]byte, error) { + file := struct { + Path string `json:"path"` + Contents string `json:"contents"` + }{ + Path: f.Path, + Contents: base64.StdEncoding.EncodeToString(f.Contents), + } + return json.Marshal(file) +} + +// CreateOpts specifies server creation parameters. +type CreateOpts struct { + // Name is the name to assign to the newly launched server. + Name string `json:"name" required:"true"` + + // ImageRef [optional; required if ImageName is not provided] is the ID or full + // URL to the image that contains the server's OS and initial state. + // Also optional if using the boot-from-volume extension. + ImageRef string `json:"imageRef"` + + // ImageName [optional; required if ImageRef is not provided] is the name of the + // image that contains the server's OS and initial state. + // Also optional if using the boot-from-volume extension. + ImageName string `json:"-"` + + // FlavorRef [optional; required if FlavorName is not provided] is the ID or + // full URL to the flavor that describes the server's specs. + FlavorRef string `json:"flavorRef"` + + // FlavorName [optional; required if FlavorRef is not provided] is the name of + // the flavor that describes the server's specs. + FlavorName string `json:"-"` + + // SecurityGroups lists the names of the security groups to which this server should belong. + SecurityGroups []string `json:"-"` + + // UserData contains configuration information or scripts to use upon launch. + // Create will base64-encode it for you, if it isn't already. + UserData []byte `json:"-"` + + // AvailabilityZone in which to launch the server. + AvailabilityZone string `json:"availability_zone,omitempty"` + + // Networks dictates how this server will be attached to available networks. + // By default, the server will be attached to all isolated networks for the tenant. + Networks []Network `json:"-"` + + // Metadata contains key-value pairs (up to 255 bytes each) to attach to the server. + Metadata map[string]string `json:"metadata,omitempty"` + + // Personality includes files to inject into the server at launch. + // Create will base64-encode file contents for you. + Personality Personality `json:"personality,omitempty"` + + // ConfigDrive enables metadata injection through a configuration drive. + ConfigDrive *bool `json:"config_drive,omitempty"` + + // AdminPass sets the root user password. If not set, a randomly-generated + // password will be created and returned in the rponse. + AdminPass string `json:"adminPass,omitempty"` + + // AccessIPv4 specifies an IPv4 address for the instance. + AccessIPv4 string `json:"accessIPv4,omitempty"` + + // AccessIPv6 pecifies an IPv6 address for the instance. + AccessIPv6 string `json:"accessIPv6,omitempty"` + + // ServiceClient will allow calls to be made to retrieve an image or + // flavor ID by name. + ServiceClient *gophercloud.ServiceClient `json:"-"` +} + +// ToServerCreateMap assembles a request body based on the contents of a CreateOpts. +func (opts CreateOpts) ToServerCreateMap() (map[string]interface{}, error) { + sc := opts.ServiceClient + opts.ServiceClient = nil + b, err := gophercloud.BuildRequestBody(opts, "") + if err != nil { + return nil, err + } + + if opts.UserData != nil { + var userData string + if _, err := base64.StdEncoding.DecodeString(string(opts.UserData)); err != nil { + userData = base64.StdEncoding.EncodeToString(opts.UserData) + } else { + userData = string(opts.UserData) + } + b["user_data"] = &userData + } + + if len(opts.SecurityGroups) > 0 { + securityGroups := make([]map[string]interface{}, len(opts.SecurityGroups)) + for i, groupName := range opts.SecurityGroups { + securityGroups[i] = map[string]interface{}{"name": groupName} + } + b["security_groups"] = securityGroups + } + + if len(opts.Networks) > 0 { + networks := make([]map[string]interface{}, len(opts.Networks)) + for i, net := range opts.Networks { + networks[i] = make(map[string]interface{}) + if net.UUID != "" { + networks[i]["uuid"] = net.UUID + } + if net.Port != "" { + networks[i]["port"] = net.Port + } + if net.FixedIP != "" { + networks[i]["fixed_ip"] = net.FixedIP + } + } + b["networks"] = networks + } + + // If ImageRef isn't provided, check if ImageName was provided to ascertain + // the image ID. + if opts.ImageRef == "" { + if opts.ImageName != "" { + if sc == nil { + err := ErrNoClientProvidedForIDByName{} + err.Argument = "ServiceClient" + return nil, err + } + imageID, err := images.IDFromName(sc, opts.ImageName) + if err != nil { + return nil, err + } + b["imageRef"] = imageID + } + } + + // If FlavorRef isn't provided, use FlavorName to ascertain the flavor ID. + if opts.FlavorRef == "" { + if opts.FlavorName == "" { + err := ErrNeitherFlavorIDNorFlavorNameProvided{} + err.Argument = "FlavorRef/FlavorName" + return nil, err + } + if sc == nil { + err := ErrNoClientProvidedForIDByName{} + err.Argument = "ServiceClient" + return nil, err + } + flavorID, err := flavors.IDFromName(sc, opts.FlavorName) + if err != nil { + return nil, err + } + b["flavorRef"] = flavorID + } + + return map[string]interface{}{"server": b}, nil +} + +// Create requests a server to be provisioned to the user in the current tenant. +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + reqBody, err := opts.ToServerCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(listURL(client), reqBody, &r.Body, nil) + return +} + +// Delete requests that a server previously provisioned be removed from your account. +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = client.Delete(deleteURL(client, id), nil) + return +} + +// ForceDelete forces the deletion of a server +func ForceDelete(client *gophercloud.ServiceClient, id string) (r ActionResult) { + _, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"forceDelete": ""}, nil, nil) + return +} + +// Get requests details on a single server, by ID. +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = client.Get(getURL(client, id), &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 203}, + }) + return +} + +// UpdateOptsBuilder allows extensions to add additional attributes to the Update request. +type UpdateOptsBuilder interface { + ToServerUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts specifies the base attributes that may be updated on an existing server. +type UpdateOpts struct { + // Name changes the displayed name of the server. + // The server host name will *not* change. + // Server names are not constrained to be unique, even within the same tenant. + Name string `json:"name,omitempty"` + + // AccessIPv4 provides a new IPv4 address for the instance. + AccessIPv4 string `json:"accessIPv4,omitempty"` + + // AccessIPv6 provides a new IPv6 address for the instance. + AccessIPv6 string `json:"accessIPv6,omitempty"` +} + +// ToServerUpdateMap formats an UpdateOpts structure into a request body. +func (opts UpdateOpts) ToServerUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "server") +} + +// Update requests that various attributes of the indicated server be changed. +func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToServerUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Put(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// ChangeAdminPassword alters the administrator or root password for a specified server. +func ChangeAdminPassword(client *gophercloud.ServiceClient, id, newPassword string) (r ActionResult) { + b := map[string]interface{}{ + "changePassword": map[string]string{ + "adminPass": newPassword, + }, + } + _, r.Err = client.Post(actionURL(client, id), b, nil, nil) + return +} + +// RebootMethod describes the mechanisms by which a server reboot can be requested. +type RebootMethod string + +// These constants determine how a server should be rebooted. +// See the Reboot() function for further details. +const ( + SoftReboot RebootMethod = "SOFT" + HardReboot RebootMethod = "HARD" + OSReboot = SoftReboot + PowerCycle = HardReboot +) + +// RebootOptsBuilder is an interface that options must satisfy in order to be +// used when rebooting a server instance +type RebootOptsBuilder interface { + ToServerRebootMap() (map[string]interface{}, error) +} + +// RebootOpts satisfies the RebootOptsBuilder interface +type RebootOpts struct { + Type RebootMethod `json:"type" required:"true"` +} + +// ToServerRebootMap allows RebootOpts to satisfiy the RebootOptsBuilder +// interface +func (opts *RebootOpts) ToServerRebootMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "reboot") +} + +// Reboot requests that a given server reboot. +// Two methods exist for rebooting a server: +// +// HardReboot (aka PowerCycle) starts the server instance by physically cutting power to the machine, or if a VM, +// terminating it at the hypervisor level. +// It's done. Caput. Full stop. +// Then, after a brief while, power is rtored or the VM instance rtarted. +// +// SoftReboot (aka OSReboot) simply tells the OS to rtart under its own procedur. +// E.g., in Linux, asking it to enter runlevel 6, or executing "sudo shutdown -r now", or by asking Windows to rtart the machine. +func Reboot(client *gophercloud.ServiceClient, id string, opts RebootOptsBuilder) (r ActionResult) { + b, err := opts.ToServerRebootMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(actionURL(client, id), b, nil, nil) + return +} + +// RebuildOptsBuilder is an interface that allows extensions to override the +// default behaviour of rebuild options +type RebuildOptsBuilder interface { + ToServerRebuildMap() (map[string]interface{}, error) +} + +// RebuildOpts represents the configuration options used in a server rebuild +// operation +type RebuildOpts struct { + // The server's admin password + AdminPass string `json:"adminPass,omitempty"` + // The ID of the image you want your server to be provisioned on + ImageID string `json:"imageRef"` + ImageName string `json:"-"` + // Name to set the server to + Name string `json:"name,omitempty"` + // AccessIPv4 [optional] provides a new IPv4 address for the instance. + AccessIPv4 string `json:"accessIPv4,omitempty"` + // AccessIPv6 [optional] provides a new IPv6 address for the instance. + AccessIPv6 string `json:"accessIPv6,omitempty"` + // Metadata [optional] contains key-value pairs (up to 255 bytes each) to attach to the server. + Metadata map[string]string `json:"metadata,omitempty"` + // Personality [optional] includes files to inject into the server at launch. + // Rebuild will base64-encode file contents for you. + Personality Personality `json:"personality,omitempty"` + ServiceClient *gophercloud.ServiceClient `json:"-"` +} + +// ToServerRebuildMap formats a RebuildOpts struct into a map for use in JSON +func (opts RebuildOpts) ToServerRebuildMap() (map[string]interface{}, error) { + b, err := gophercloud.BuildRequestBody(opts, "") + if err != nil { + return nil, err + } + + // If ImageRef isn't provided, check if ImageName was provided to ascertain + // the image ID. + if opts.ImageID == "" { + if opts.ImageName != "" { + if opts.ServiceClient == nil { + err := ErrNoClientProvidedForIDByName{} + err.Argument = "ServiceClient" + return nil, err + } + imageID, err := images.IDFromName(opts.ServiceClient, opts.ImageName) + if err != nil { + return nil, err + } + b["imageRef"] = imageID + } + } + + return map[string]interface{}{"rebuild": b}, nil +} + +// Rebuild will reprovision the server according to the configuration options +// provided in the RebuildOpts struct. +func Rebuild(client *gophercloud.ServiceClient, id string, opts RebuildOptsBuilder) (r RebuildResult) { + b, err := opts.ToServerRebuildMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(actionURL(client, id), b, &r.Body, nil) + return +} + +// ResizeOptsBuilder is an interface that allows extensions to override the default structure of +// a Resize request. +type ResizeOptsBuilder interface { + ToServerResizeMap() (map[string]interface{}, error) +} + +// ResizeOpts represents the configuration options used to control a Resize operation. +type ResizeOpts struct { + // FlavorRef is the ID of the flavor you wish your server to become. + FlavorRef string `json:"flavorRef" required:"true"` +} + +// ToServerResizeMap formats a ResizeOpts as a map that can be used as a JSON request body for the +// Resize request. +func (opts ResizeOpts) ToServerResizeMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "resize") +} + +// Resize instructs the provider to change the flavor of the server. +// Note that this implies rebuilding it. +// Unfortunately, one cannot pass rebuild parameters to the resize function. +// When the resize completes, the server will be in RESIZE_VERIFY state. +// While in this state, you can explore the use of the new server's configuration. +// If you like it, call ConfirmResize() to commit the resize permanently. +// Otherwise, call RevertResize() to restore the old configuration. +func Resize(client *gophercloud.ServiceClient, id string, opts ResizeOptsBuilder) (r ActionResult) { + b, err := opts.ToServerResizeMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(actionURL(client, id), b, nil, nil) + return +} + +// ConfirmResize confirms a previous resize operation on a server. +// See Resize() for more details. +func ConfirmResize(client *gophercloud.ServiceClient, id string) (r ActionResult) { + _, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"confirmResize": nil}, nil, &gophercloud.RequestOpts{ + OkCodes: []int{201, 202, 204}, + }) + return +} + +// RevertResize cancels a previous resize operation on a server. +// See Resize() for more details. +func RevertResize(client *gophercloud.ServiceClient, id string) (r ActionResult) { + _, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"revertResize": nil}, nil, nil) + return +} + +// RescueOptsBuilder is an interface that allows extensions to override the +// default structure of a Rescue request. +type RescueOptsBuilder interface { + ToServerRescueMap() (map[string]interface{}, error) +} + +// RescueOpts represents the configuration options used to control a Rescue +// option. +type RescueOpts struct { + // AdminPass is the desired administrative password for the instance in + // RESCUE mode. If it's left blank, the server will generate a password. + AdminPass string `json:"adminPass,omitempty"` +} + +// ToServerRescueMap formats a RescueOpts as a map that can be used as a JSON +// request body for the Rescue request. +func (opts RescueOpts) ToServerRescueMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "rescue") +} + +// Rescue instructs the provider to place the server into RESCUE mode. +func Rescue(client *gophercloud.ServiceClient, id string, opts RescueOptsBuilder) (r RescueResult) { + b, err := opts.ToServerRescueMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// ResetMetadataOptsBuilder allows extensions to add additional parameters to the +// Reset request. +type ResetMetadataOptsBuilder interface { + ToMetadataResetMap() (map[string]interface{}, error) +} + +// MetadataOpts is a map that contains key-value pairs. +type MetadataOpts map[string]string + +// ToMetadataResetMap assembles a body for a Reset request based on the contents of a MetadataOpts. +func (opts MetadataOpts) ToMetadataResetMap() (map[string]interface{}, error) { + return map[string]interface{}{"metadata": opts}, nil +} + +// ToMetadataUpdateMap assembles a body for an Update request based on the contents of a MetadataOpts. +func (opts MetadataOpts) ToMetadataUpdateMap() (map[string]interface{}, error) { + return map[string]interface{}{"metadata": opts}, nil +} + +// ResetMetadata will create multiple new key-value pairs for the given server ID. +// Note: Using this operation will erase any already-existing metadata and create +// the new metadata provided. To keep any already-existing metadata, use the +// UpdateMetadatas or UpdateMetadata function. +func ResetMetadata(client *gophercloud.ServiceClient, id string, opts ResetMetadataOptsBuilder) (r ResetMetadataResult) { + b, err := opts.ToMetadataResetMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Put(metadataURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// Metadata requests all the metadata for the given server ID. +func Metadata(client *gophercloud.ServiceClient, id string) (r GetMetadataResult) { + _, r.Err = client.Get(metadataURL(client, id), &r.Body, nil) + return +} + +// UpdateMetadataOptsBuilder allows extensions to add additional parameters to the +// Create request. +type UpdateMetadataOptsBuilder interface { + ToMetadataUpdateMap() (map[string]interface{}, error) +} + +// UpdateMetadata updates (or creates) all the metadata specified by opts for the given server ID. +// This operation does not affect already-existing metadata that is not specified +// by opts. +func UpdateMetadata(client *gophercloud.ServiceClient, id string, opts UpdateMetadataOptsBuilder) (r UpdateMetadataResult) { + b, err := opts.ToMetadataUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(metadataURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// MetadatumOptsBuilder allows extensions to add additional parameters to the +// Create request. +type MetadatumOptsBuilder interface { + ToMetadatumCreateMap() (map[string]interface{}, string, error) +} + +// MetadatumOpts is a map of length one that contains a key-value pair. +type MetadatumOpts map[string]string + +// ToMetadatumCreateMap assembles a body for a Create request based on the contents of a MetadataumOpts. +func (opts MetadatumOpts) ToMetadatumCreateMap() (map[string]interface{}, string, error) { + if len(opts) != 1 { + err := gophercloud.ErrInvalidInput{} + err.Argument = "servers.MetadatumOpts" + err.Info = "Must have 1 and only 1 key-value pair" + return nil, "", err + } + metadatum := map[string]interface{}{"meta": opts} + var key string + for k := range metadatum["meta"].(MetadatumOpts) { + key = k + } + return metadatum, key, nil +} + +// CreateMetadatum will create or update the key-value pair with the given key for the given server ID. +func CreateMetadatum(client *gophercloud.ServiceClient, id string, opts MetadatumOptsBuilder) (r CreateMetadatumResult) { + b, key, err := opts.ToMetadatumCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Put(metadatumURL(client, id, key), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// Metadatum requests the key-value pair with the given key for the given server ID. +func Metadatum(client *gophercloud.ServiceClient, id, key string) (r GetMetadatumResult) { + _, r.Err = client.Get(metadatumURL(client, id, key), &r.Body, nil) + return +} + +// DeleteMetadatum will delete the key-value pair with the given key for the given server ID. +func DeleteMetadatum(client *gophercloud.ServiceClient, id, key string) (r DeleteMetadatumResult) { + _, r.Err = client.Delete(metadatumURL(client, id, key), nil) + return +} + +// ListAddresses makes a request against the API to list the servers IP addresses. +func ListAddresses(client *gophercloud.ServiceClient, id string) pagination.Pager { + return pagination.NewPager(client, listAddressesURL(client, id), func(r pagination.PageResult) pagination.Page { + return AddressPage{pagination.SinglePageBase(r)} + }) +} + +// ListAddressesByNetwork makes a request against the API to list the servers IP addresses +// for the given network. +func ListAddressesByNetwork(client *gophercloud.ServiceClient, id, network string) pagination.Pager { + return pagination.NewPager(client, listAddressesByNetworkURL(client, id, network), func(r pagination.PageResult) pagination.Page { + return NetworkAddressPage{pagination.SinglePageBase(r)} + }) +} + +// CreateImageOptsBuilder is the interface types must satisfy in order to be +// used as CreateImage options +type CreateImageOptsBuilder interface { + ToServerCreateImageMap() (map[string]interface{}, error) +} + +// CreateImageOpts satisfies the CreateImageOptsBuilder +type CreateImageOpts struct { + // Name of the image/snapshot + Name string `json:"name" required:"true"` + // Metadata contains key-value pairs (up to 255 bytes each) to attach to the created image. + Metadata map[string]string `json:"metadata,omitempty"` +} + +// ToServerCreateImageMap formats a CreateImageOpts structure into a request body. +func (opts CreateImageOpts) ToServerCreateImageMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "createImage") +} + +// CreateImage makes a request against the nova API to schedule an image to be created of the server +func CreateImage(client *gophercloud.ServiceClient, id string, opts CreateImageOptsBuilder) (r CreateImageResult) { + b, err := opts.ToServerCreateImageMap() + if err != nil { + r.Err = err + return + } + resp, err := client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{ + OkCodes: []int{202}, + }) + r.Err = err + r.Header = resp.Header + return +} + +// IDFromName is a convienience function that returns a server's ID given its name. +func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { + count := 0 + id := "" + allPages, err := List(client, nil).AllPages() + if err != nil { + return "", err + } + + all, err := ExtractServers(allPages) + if err != nil { + return "", err + } + + for _, f := range all { + if f.Name == name { + count++ + id = f.ID + } + } + + switch count { + case 0: + return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "server"} + case 1: + return id, nil + default: + return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "server"} + } +} + +// GetPassword makes a request against the nova API to get the encrypted administrative password. +func GetPassword(client *gophercloud.ServiceClient, serverId string) (r GetPasswordResult) { + _, r.Err = client.Get(passwordURL(client, serverId), &r.Body, nil) + return +} diff --git a/src/cmd/linuxkit/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/results.go b/src/cmd/linuxkit/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/results.go new file mode 100644 index 000000000..1ae1e91c7 --- /dev/null +++ b/src/cmd/linuxkit/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/results.go @@ -0,0 +1,350 @@ +package servers + +import ( + "crypto/rsa" + "encoding/base64" + "encoding/json" + "fmt" + "net/url" + "path" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +type serverResult struct { + gophercloud.Result +} + +// Extract interprets any serverResult as a Server, if possible. +func (r serverResult) Extract() (*Server, error) { + var s Server + err := r.ExtractInto(&s) + return &s, err +} + +func (r serverResult) ExtractInto(v interface{}) error { + return r.Result.ExtractIntoStructPtr(v, "server") +} + +func ExtractServersInto(r pagination.Page, v interface{}) error { + return r.(ServerPage).Result.ExtractIntoSlicePtr(v, "servers") +} + +// CreateResult temporarily contains the response from a Create call. +type CreateResult struct { + serverResult +} + +// GetResult temporarily contains the response from a Get call. +type GetResult struct { + serverResult +} + +// UpdateResult temporarily contains the response from an Update call. +type UpdateResult struct { + serverResult +} + +// DeleteResult temporarily contains the response from a Delete call. +type DeleteResult struct { + gophercloud.ErrResult +} + +// RebuildResult temporarily contains the response from a Rebuild call. +type RebuildResult struct { + serverResult +} + +// ActionResult represents the result of server action operations, like reboot +type ActionResult struct { + gophercloud.ErrResult +} + +// RescueResult represents the result of a server rescue operation +type RescueResult struct { + ActionResult +} + +// CreateImageResult represents the result of an image creation operation +type CreateImageResult struct { + gophercloud.Result +} + +// GetPasswordResult represent the result of a get os-server-password operation. +type GetPasswordResult struct { + gophercloud.Result +} + +// ExtractPassword gets the encrypted password. +// If privateKey != nil the password is decrypted with the private key. +// If privateKey == nil the encrypted password is returned and can be decrypted with: +// echo '' | base64 -D | openssl rsautl -decrypt -inkey +func (r GetPasswordResult) ExtractPassword(privateKey *rsa.PrivateKey) (string, error) { + var s struct { + Password string `json:"password"` + } + err := r.ExtractInto(&s) + if err == nil && privateKey != nil && s.Password != "" { + return decryptPassword(s.Password, privateKey) + } + return s.Password, err +} + +func decryptPassword(encryptedPassword string, privateKey *rsa.PrivateKey) (string, error) { + b64EncryptedPassword := make([]byte, base64.StdEncoding.DecodedLen(len(encryptedPassword))) + + n, err := base64.StdEncoding.Decode(b64EncryptedPassword, []byte(encryptedPassword)) + if err != nil { + return "", fmt.Errorf("Failed to base64 decode encrypted password: %s", err) + } + password, err := rsa.DecryptPKCS1v15(nil, privateKey, b64EncryptedPassword[0:n]) + if err != nil { + return "", fmt.Errorf("Failed to decrypt password: %s", err) + } + + return string(password), nil +} + +// ExtractImageID gets the ID of the newly created server image from the header +func (r CreateImageResult) ExtractImageID() (string, error) { + if r.Err != nil { + return "", r.Err + } + // Get the image id from the header + u, err := url.ParseRequestURI(r.Header.Get("Location")) + if err != nil { + return "", err + } + imageID := path.Base(u.Path) + if imageID == "." || imageID == "/" { + return "", fmt.Errorf("Failed to parse the ID of newly created image: %s", u) + } + return imageID, nil +} + +// Extract interprets any RescueResult as an AdminPass, if possible. +func (r RescueResult) Extract() (string, error) { + var s struct { + AdminPass string `json:"adminPass"` + } + err := r.ExtractInto(&s) + return s.AdminPass, err +} + +// Server exposes only the standard OpenStack fields corresponding to a given server on the user's account. +type Server struct { + // ID uniquely identifies this server amongst all other servers, including those not accessible to the current tenant. + ID string `json:"id"` + // TenantID identifies the tenant owning this server resource. + TenantID string `json:"tenant_id"` + // UserID uniquely identifies the user account owning the tenant. + UserID string `json:"user_id"` + // Name contains the human-readable name for the server. + Name string `json:"name"` + // Updated and Created contain ISO-8601 timestamps of when the state of the server last changed, and when it was created. + Updated time.Time `json:"updated"` + Created time.Time `json:"created"` + HostID string `json:"hostid"` + // Status contains the current operational status of the server, such as IN_PROGRESS or ACTIVE. + Status string `json:"status"` + // Progress ranges from 0..100. + // A request made against the server completes only once Progress reaches 100. + Progress int `json:"progress"` + // AccessIPv4 and AccessIPv6 contain the IP addresses of the server, suitable for remote access for administration. + AccessIPv4 string `json:"accessIPv4"` + AccessIPv6 string `json:"accessIPv6"` + // Image refers to a JSON object, which itself indicates the OS image used to deploy the server. + Image map[string]interface{} `json:"-"` + // Flavor refers to a JSON object, which itself indicates the hardware configuration of the deployed server. + Flavor map[string]interface{} `json:"flavor"` + // Addresses includes a list of all IP addresses assigned to the server, keyed by pool. + Addresses map[string]interface{} `json:"addresses"` + // Metadata includes a list of all user-specified key-value pairs attached to the server. + Metadata map[string]string `json:"metadata"` + // Links includes HTTP references to the itself, useful for passing along to other APIs that might want a server reference. + Links []interface{} `json:"links"` + // KeyName indicates which public key was injected into the server on launch. + KeyName string `json:"key_name"` + // AdminPass will generally be empty (""). However, it will contain the administrative password chosen when provisioning a new server without a set AdminPass setting in the first place. + // Note that this is the ONLY time this field will be valid. + AdminPass string `json:"adminPass"` + // SecurityGroups includes the security groups that this instance has applied to it + SecurityGroups []map[string]interface{} `json:"security_groups"` +} + +func (r *Server) UnmarshalJSON(b []byte) error { + type tmp Server + var s struct { + tmp + Image interface{} `json:"image"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + + *r = Server(s.tmp) + + switch t := s.Image.(type) { + case map[string]interface{}: + r.Image = t + case string: + switch t { + case "": + r.Image = nil + } + } + + return err +} + +// ServerPage abstracts the raw results of making a List() request against the API. +// As OpenStack extensions may freely alter the response bodies of structures returned to the client, you may only safely access the +// data provided through the ExtractServers call. +type ServerPage struct { + pagination.LinkedPageBase +} + +// IsEmpty returns true if a page contains no Server results. +func (r ServerPage) IsEmpty() (bool, error) { + s, err := ExtractServers(r) + return len(s) == 0, err +} + +// NextPageURL uses the response's embedded link reference to navigate to the next page of results. +func (r ServerPage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"servers_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// ExtractServers interprets the results of a single page from a List() call, producing a slice of Server entities. +func ExtractServers(r pagination.Page) ([]Server, error) { + var s []Server + err := ExtractServersInto(r, &s) + return s, err +} + +// MetadataResult contains the result of a call for (potentially) multiple key-value pairs. +type MetadataResult struct { + gophercloud.Result +} + +// GetMetadataResult temporarily contains the response from a metadata Get call. +type GetMetadataResult struct { + MetadataResult +} + +// ResetMetadataResult temporarily contains the response from a metadata Reset call. +type ResetMetadataResult struct { + MetadataResult +} + +// UpdateMetadataResult temporarily contains the response from a metadata Update call. +type UpdateMetadataResult struct { + MetadataResult +} + +// MetadatumResult contains the result of a call for individual a single key-value pair. +type MetadatumResult struct { + gophercloud.Result +} + +// GetMetadatumResult temporarily contains the response from a metadatum Get call. +type GetMetadatumResult struct { + MetadatumResult +} + +// CreateMetadatumResult temporarily contains the response from a metadatum Create call. +type CreateMetadatumResult struct { + MetadatumResult +} + +// DeleteMetadatumResult temporarily contains the response from a metadatum Delete call. +type DeleteMetadatumResult struct { + gophercloud.ErrResult +} + +// Extract interprets any MetadataResult as a Metadata, if possible. +func (r MetadataResult) Extract() (map[string]string, error) { + var s struct { + Metadata map[string]string `json:"metadata"` + } + err := r.ExtractInto(&s) + return s.Metadata, err +} + +// Extract interprets any MetadatumResult as a Metadatum, if possible. +func (r MetadatumResult) Extract() (map[string]string, error) { + var s struct { + Metadatum map[string]string `json:"meta"` + } + err := r.ExtractInto(&s) + return s.Metadatum, err +} + +// Address represents an IP address. +type Address struct { + Version int `json:"version"` + Address string `json:"addr"` +} + +// AddressPage abstracts the raw results of making a ListAddresses() request against the API. +// As OpenStack extensions may freely alter the response bodies of structures returned +// to the client, you may only safely access the data provided through the ExtractAddresses call. +type AddressPage struct { + pagination.SinglePageBase +} + +// IsEmpty returns true if an AddressPage contains no networks. +func (r AddressPage) IsEmpty() (bool, error) { + addresses, err := ExtractAddresses(r) + return len(addresses) == 0, err +} + +// ExtractAddresses interprets the results of a single page from a ListAddresses() call, +// producing a map of addresses. +func ExtractAddresses(r pagination.Page) (map[string][]Address, error) { + var s struct { + Addresses map[string][]Address `json:"addresses"` + } + err := (r.(AddressPage)).ExtractInto(&s) + return s.Addresses, err +} + +// NetworkAddressPage abstracts the raw results of making a ListAddressesByNetwork() request against the API. +// As OpenStack extensions may freely alter the response bodies of structures returned +// to the client, you may only safely access the data provided through the ExtractAddresses call. +type NetworkAddressPage struct { + pagination.SinglePageBase +} + +// IsEmpty returns true if a NetworkAddressPage contains no addresses. +func (r NetworkAddressPage) IsEmpty() (bool, error) { + addresses, err := ExtractNetworkAddresses(r) + return len(addresses) == 0, err +} + +// ExtractNetworkAddresses interprets the results of a single page from a ListAddressesByNetwork() call, +// producing a slice of addresses. +func ExtractNetworkAddresses(r pagination.Page) ([]Address, error) { + var s map[string][]Address + err := (r.(NetworkAddressPage)).ExtractInto(&s) + if err != nil { + return nil, err + } + + var key string + for k := range s { + key = k + } + + return s[key], err +} diff --git a/src/cmd/linuxkit/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/urls.go b/src/cmd/linuxkit/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/urls.go new file mode 100644 index 000000000..e892e8d92 --- /dev/null +++ b/src/cmd/linuxkit/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/urls.go @@ -0,0 +1,51 @@ +package servers + +import "github.com/gophercloud/gophercloud" + +func createURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("servers") +} + +func listURL(client *gophercloud.ServiceClient) string { + return createURL(client) +} + +func listDetailURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("servers", "detail") +} + +func deleteURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("servers", id) +} + +func getURL(client *gophercloud.ServiceClient, id string) string { + return deleteURL(client, id) +} + +func updateURL(client *gophercloud.ServiceClient, id string) string { + return deleteURL(client, id) +} + +func actionURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("servers", id, "action") +} + +func metadatumURL(client *gophercloud.ServiceClient, id, key string) string { + return client.ServiceURL("servers", id, "metadata", key) +} + +func metadataURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("servers", id, "metadata") +} + +func listAddressesURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("servers", id, "ips") +} + +func listAddressesByNetworkURL(client *gophercloud.ServiceClient, id, network string) string { + return client.ServiceURL("servers", id, "ips", network) +} + +func passwordURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("servers", id, "os-server-password") +} diff --git a/src/cmd/linuxkit/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/util.go b/src/cmd/linuxkit/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/util.go new file mode 100644 index 000000000..494a0e4dc --- /dev/null +++ b/src/cmd/linuxkit/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/util.go @@ -0,0 +1,20 @@ +package servers + +import "github.com/gophercloud/gophercloud" + +// WaitForStatus will continually poll a server until it successfully transitions to a specified +// status. It will do this for at most the number of seconds specified. +func WaitForStatus(c *gophercloud.ServiceClient, id, status string, secs int) error { + return gophercloud.WaitFor(secs, func() (bool, error) { + current, err := Get(c, id).Extract() + if err != nil { + return false, err + } + + if current.Status == status { + return true, nil + } + + return false, nil + }) +}