diff --git a/OWNERS_ALIASES b/OWNERS_ALIASES index 2a6fb6de0b5..c310a5090b6 100644 --- a/OWNERS_ALIASES +++ b/OWNERS_ALIASES @@ -31,3 +31,9 @@ aliases: - abhinavdahiya - dav1x - staebler + baremetal-reviewers: + - celebdor + - markmc + - russellb + - hardys + - stbenjam diff --git a/data/data/baremetal/OWNERS b/data/data/baremetal/OWNERS new file mode 100644 index 00000000000..11af0bba91f --- /dev/null +++ b/data/data/baremetal/OWNERS @@ -0,0 +1,5 @@ +# See the OWNERS docs: https://git.k8s.io/community/contributors/guide/owners.md +# This file just uses aliases defined in OWNERS_ALIASES. + +reviewers: + - baremetal-reviewers diff --git a/data/data/baremetal/bootstrap/README.md b/data/data/baremetal/bootstrap/README.md new file mode 100644 index 00000000000..5dbaaf0a5af --- /dev/null +++ b/data/data/baremetal/bootstrap/README.md @@ -0,0 +1,49 @@ +# Bootstrap Module + +This [Terraform][] [module][] manages [libvirt][] resources only needed during cluster bootstrapping. +It uses [implicit provider inheritance][implicit-provider-inheritance] to access the [libvirt provider][libvirt-provider]. + +## Example + +Set up a `main.tf` with: + +```hcl +provider "libvirt" { + uri = "qemu:///system" +} + +resource "libvirt_network" "example" { + name = "example" + mode = "none" + domain = "example.com" + addresses = ["192.168.0.0/24"] +} + +resource "libvirt_volume" "example" { + name = "example" + source = "file:///path/to/example.qcow2" +} + +module "bootstrap" { + source = "github.com/openshift/installer//data/data/libvirt/bootstrap" + + addresses = ["192.168.0.1"] + base_volume_id = "${libvirt_volume.example.id}" + cluster_id = "my-cluster" + ignition = "{\"ignition\": {\"version\": \"2.2.0\"}}", + network_id = "${libvirt_network.example.id}" +} +``` + +Then run: + +```console +$ terraform init +$ terraform plan +``` + +[libvirt]: https://libvirt.org/ +[libvirt-provider]: https://github.com/dmacvicar/terraform-provider-libvirt +[implicit-provider-inheritance]: https://www.terraform.io/docs/modules/usage.html#implicit-provider-inheritance +[module]: https://www.terraform.io/docs/modules/ +[Terraform]: https://www.terraform.io/ diff --git a/data/data/baremetal/bootstrap/main.tf b/data/data/baremetal/bootstrap/main.tf new file mode 100644 index 00000000000..f18f23b4672 --- /dev/null +++ b/data/data/baremetal/bootstrap/main.tf @@ -0,0 +1,41 @@ +resource "libvirt_volume" "bootstrap" { + name = "${var.cluster_id}-bootstrap" + source = var.image +} + +resource "libvirt_ignition" "bootstrap" { + name = "${var.cluster_id}-bootstrap.ign" + content = var.ignition +} + +resource "libvirt_domain" "bootstrap" { + name = "${var.cluster_id}-bootstrap" + + memory = "4096" + + vcpu = "4" + + coreos_ignition = libvirt_ignition.bootstrap.id + + disk { + volume_id = libvirt_volume.bootstrap.id + } + + console { + type = "pty" + target_port = 0 + } + + cpu = { + mode = "host-passthrough" + } + + network_interface { + bridge = var.external_bridge + } + + network_interface { + bridge = var.provisioning_bridge + } +} + diff --git a/data/data/baremetal/bootstrap/variables.tf b/data/data/baremetal/bootstrap/variables.tf new file mode 100644 index 00000000000..84641ea30aa --- /dev/null +++ b/data/data/baremetal/bootstrap/variables.tf @@ -0,0 +1,25 @@ +variable "cluster_id" { + type = string + description = "The identifier for the cluster." +} + +variable "image" { + description = "The URL of the OS disk image" + type = string +} + +variable "ignition" { + type = string + description = "The content of the bootstrap ignition file." +} + +variable "external_bridge" { + type = string + description = "The name of the bridge providing external access" +} + +variable "provisioning_bridge" { + type = string + description = "The name of the bridge used for provisioning" +} + diff --git a/data/data/baremetal/bootstrap/versions.tf b/data/data/baremetal/bootstrap/versions.tf new file mode 100644 index 00000000000..ac97c6ac8e7 --- /dev/null +++ b/data/data/baremetal/bootstrap/versions.tf @@ -0,0 +1,4 @@ + +terraform { + required_version = ">= 0.12" +} diff --git a/data/data/baremetal/main.tf b/data/data/baremetal/main.tf new file mode 100644 index 00000000000..a0ebc17660e --- /dev/null +++ b/data/data/baremetal/main.tf @@ -0,0 +1,31 @@ +provider "libvirt" { + uri = var.libvirt_uri +} + +provider "ironic" { + url = var.ironic_uri + microversion = "1.52" +} + +module "bootstrap" { + source = "./bootstrap" + + cluster_id = var.cluster_id + image = var.os_image + ignition = var.ignition_bootstrap + external_bridge = var.external_bridge + provisioning_bridge = var.provisioning_bridge +} + +module "masters" { + source = "./masters" + + ironic_uri = var.ironic_uri + master_count = var.master_count + ignition = var.ignition_master + hosts = var.hosts + properties = var.properties + root_devices = var.root_devices + driver_infos = var.driver_infos + instance_infos = var.instance_infos +} diff --git a/data/data/baremetal/masters/main.tf b/data/data/baremetal/masters/main.tf new file mode 100644 index 00000000000..b81b23b1c0b --- /dev/null +++ b/data/data/baremetal/masters/main.tf @@ -0,0 +1,42 @@ +resource "ironic_node_v1" "openshift-master-host" { + count = var.master_count + name = var.hosts[count.index]["name"] + resource_class = "baremetal" + + inspect = true + clean = true + available = true + + ports = [ + { + address = var.hosts[count.index]["port_address"] + pxe_enabled = "true" + }, + ] + + properties = var.properties[count.index] + root_device = var.root_devices[count.index] + + driver = var.hosts[count.index]["driver"] + driver_info = var.driver_infos[count.index] +} + +resource "ironic_allocation_v1" "openshift-master-allocation" { + name = "master-${count.index}" + count = var.master_count + resource_class = "baremetal" + + candidate_nodes = ironic_node_v1.openshift-master-host.*.id +} + +resource "ironic_deployment" "openshift-master-deployment" { + count = var.master_count + node_uuid = element( + ironic_allocation_v1.openshift-master-allocation.*.node_uuid, + count.index, + ) + + instance_info = var.instance_infos[count.index] + user_data = var.ignition +} + diff --git a/data/data/baremetal/masters/variables.tf b/data/data/baremetal/masters/variables.tf new file mode 100644 index 00000000000..9bd3bed8f2e --- /dev/null +++ b/data/data/baremetal/masters/variables.tf @@ -0,0 +1,39 @@ +variable "ironic_uri" { + type = string + description = "URI for Ironic that bootstrap should wait for to be available" +} + +variable "master_count" { + type = string + description = "Number of masters" + default = 3 +} +variable "ignition" { + type = string + description = "The content of the master ignition file" +} + +variable "hosts" { + type = list(map(string)) + description = "Hardware details for hosts" +} + +variable "properties" { + type = list(map(string)) + description = "Properties for hosts" +} + +variable "root_devices" { + type = list(map(string)) + description = "Root devices for hosts" +} + +variable "driver_infos" { + type = list(map(string)) + description = "BMC information for hosts" +} + +variable "instance_infos" { + type = list(map(string)) + description = "Instance information for hosts" +} diff --git a/data/data/baremetal/masters/versions.tf b/data/data/baremetal/masters/versions.tf new file mode 100644 index 00000000000..ac97c6ac8e7 --- /dev/null +++ b/data/data/baremetal/masters/versions.tf @@ -0,0 +1,4 @@ + +terraform { + required_version = ">= 0.12" +} diff --git a/data/data/baremetal/variables-baremetal.tf b/data/data/baremetal/variables-baremetal.tf new file mode 100644 index 00000000000..ee17874ec28 --- /dev/null +++ b/data/data/baremetal/variables-baremetal.tf @@ -0,0 +1,50 @@ +variable "ironic_uri" { + type = string + description = "ironic connection URI" +} + +variable "libvirt_uri" { + type = string + description = "libvirt connection URI" +} + +variable "os_image" { + type = string + description = "The URL of the OS disk image" +} + +variable "external_bridge" { + type = string + description = "The name of the external bridge" +} + +variable "provisioning_bridge" { + type = string + description = "The name of the provisioning bridge" +} + +variable "hosts" { + type = list(map(string)) + description = "Hardware details for hosts" +} + +variable "properties" { + type = list(map(string)) + description = "Properties for hosts" +} + +variable "root_devices" { + type = list(map(string)) + description = "Root devices for hosts" +} + +variable "driver_infos" { + type = list(map(string)) + description = "BMC information for hosts" +} + +variable "instance_infos" { + type = list(map(string)) + description = "Instance information for hosts" +} + diff --git a/data/data/baremetal/versions.tf b/data/data/baremetal/versions.tf new file mode 100644 index 00000000000..ac97c6ac8e7 --- /dev/null +++ b/data/data/baremetal/versions.tf @@ -0,0 +1,4 @@ + +terraform { + required_version = ">= 0.12" +} diff --git a/pkg/terraform/exec/plugins/Gopkg.lock b/pkg/terraform/exec/plugins/Gopkg.lock index 357eeeb17a3..236c5931435 100644 --- a/pkg/terraform/exec/plugins/Gopkg.lock +++ b/pkg/terraform/exec/plugins/Gopkg.lock @@ -481,12 +481,18 @@ version = "v2.0.5" [[projects]] - digest = "1:2be73edf7ec0748333e19ee06a303a54fd8926f215a063a253647dc49b22d191" + digest = "1:c1024fa089f6cf75ea25d2a6d10bb001643e8464d710fa4d8205f8edfa8454bb" name = "github.com/gophercloud/gophercloud" packages = [ ".", "internal", "openstack", + "openstack/baremetal/noauth", + "openstack/baremetal/v1/allocations", + "openstack/baremetal/v1/nodes", + "openstack/baremetal/v1/ports", + "openstack/baremetalintrospection/noauth", + "openstack/baremetalintrospection/v1/introspection", "openstack/blockstorage/extensions/volumeactions", "openstack/blockstorage/v1/volumes", "openstack/blockstorage/v2/snapshots", @@ -585,9 +591,12 @@ [[projects]] branch = "master" - digest = "1:4a14c92687d36a3875744de509bae52334a00ba487eb285b8a57645ec46acc7e" + digest = "1:19d4403f555e6d4e540c55f7050093f5e07e0c4174c4169826fb4d962d0e6240" name = "github.com/gophercloud/utils" - packages = ["openstack/clientconfig"] + packages = [ + "openstack/baremetal/v1/nodes", + "openstack/clientconfig", + ] pruneopts = "NUT" revision = "0bcc8e728cb54c9dce9ed46924688b817ab14f31" @@ -778,6 +787,14 @@ revision = "eecee6c969c02c8cc2ae48e1e269843ae8590796" version = "v1.0.0" +[[projects]] + digest = "1:a0886267c60262a245d6eeb0be12bf8e165026ad088bce106b9c55c5d613144f" + name = "github.com/openshift-metalkube/terraform-provider-ironic" + packages = ["ironic"] + pruneopts = "NUT" + revision = "9f5bba54dad78c10ed572eb2541f8178a5500656" + version = "v0.1.4" + [[projects]] branch = "master" digest = "1:3bf17a6e6eaa6ad24152148a631d18662f7212e21637c2699bff3369b7f00fa2" @@ -1167,6 +1184,7 @@ analyzer-version = 1 input-imports = [ "github.com/dmacvicar/terraform-provider-libvirt/libvirt", + "github.com/openshift-metalkube/terraform-provider-ironic/ironic", "github.com/terraform-providers/terraform-provider-aws/aws", "github.com/terraform-providers/terraform-provider-azurerm/azurerm", "github.com/terraform-providers/terraform-provider-google/google", diff --git a/pkg/terraform/exec/plugins/Gopkg.toml b/pkg/terraform/exec/plugins/Gopkg.toml index 12d1eed42a3..addedbd6cba 100644 --- a/pkg/terraform/exec/plugins/Gopkg.toml +++ b/pkg/terraform/exec/plugins/Gopkg.toml @@ -67,3 +67,7 @@ ignored = [ [[override]] name = "github.com/hashicorp/go-azure-helpers" version = "0.4.1" + +[[constraint]] + name = "github.com/openshift-metalkube/terraform-provider-ironic" + version = "v0.1.3" diff --git a/pkg/terraform/exec/plugins/ironic.go b/pkg/terraform/exec/plugins/ironic.go new file mode 100644 index 00000000000..6acc671f6ee --- /dev/null +++ b/pkg/terraform/exec/plugins/ironic.go @@ -0,0 +1,17 @@ +// +build baremetal + +package plugins + +import ( + "github.com/hashicorp/terraform/plugin" + "github.com/openshift-metalkube/terraform-provider-ironic/ironic" +) + +func init() { + exec := func() { + plugin.Serve(&plugin.ServeOpts{ + ProviderFunc: ironic.Provider, + }) + } + KnownPlugins["terraform-provider-ironic"] = exec +} diff --git a/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/noauth/doc.go b/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/noauth/doc.go new file mode 100644 index 00000000000..9f83357b2aa --- /dev/null +++ b/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/noauth/doc.go @@ -0,0 +1,17 @@ +/* +Package noauth provides support for noauth bare metal endpoints. + +Example of obtaining and using a client: + + client, err := noauth.NewBareMetalNoAuth(noauth.EndpointOpts{ + IronicEndpoint: "http://localhost:6385/v1/", + }) + if err != nil { + panic(err) + } + + client.Microversion = "1.50" + + nodes.ListDetail(client, nodes.ListOpts{}) +*/ +package noauth diff --git a/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/noauth/requests.go b/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/noauth/requests.go new file mode 100644 index 00000000000..b5a1b58f0d5 --- /dev/null +++ b/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/noauth/requests.go @@ -0,0 +1,36 @@ +package noauth + +import ( + "fmt" + + "github.com/gophercloud/gophercloud" +) + +// EndpointOpts specifies a "noauth" Ironic Endpoint. +type EndpointOpts struct { + // IronicEndpoint [required] is currently only used with "noauth" Ironic. + // An Ironic endpoint with "auth_strategy=noauth" is necessary, for example: + // http://ironic.example.com:6385/v1. + IronicEndpoint string +} + +func initClientOpts(client *gophercloud.ProviderClient, eo EndpointOpts) (*gophercloud.ServiceClient, error) { + sc := new(gophercloud.ServiceClient) + if eo.IronicEndpoint == "" { + return nil, fmt.Errorf("IronicEndpoint is required") + } + + sc.Endpoint = gophercloud.NormalizeURL(eo.IronicEndpoint) + sc.ProviderClient = client + return sc, nil +} + +// NewBareMetalNoAuth creates a ServiceClient that may be used to access a +// "noauth" bare metal service. +func NewBareMetalNoAuth(eo EndpointOpts) (*gophercloud.ServiceClient, error) { + sc, err := initClientOpts(&gophercloud.ProviderClient{}, eo) + + sc.Type = "baremetal" + + return sc, err +} diff --git a/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/allocations/requests.go b/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/allocations/requests.go new file mode 100644 index 00000000000..7acb0021a70 --- /dev/null +++ b/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/allocations/requests.go @@ -0,0 +1,131 @@ +package allocations + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToAllocationCreateMap() (map[string]interface{}, error) +} + +// CreateOpts specifies allocation creation parameters +type CreateOpts struct { + // The requested resource class for the allocation. + ResourceClass string `json:"resource_class" required:"true"` + + // The list of nodes (names or UUIDs) that should be considered for this allocation. If not provided, all available nodes will be considered. + CandidateNodes []string `json:"candidate_nodes,omitempty"` + + // The unique name of the Allocation. + Name string `json:"name,omitempty"` + + // The list of requested traits for the allocation. + Traits []string `json:"traits,omitempty"` + + // The UUID for the resource. + UUID string `json:"uuid,omitempty"` + + // A set of one or more arbitrary metadata key and value pairs. + Extra map[string]string `json:"extra,omitempty"` +} + +// ToAllocationCreateMap assembles a request body based on the contents of a CreateOpts. +func (opts CreateOpts) ToAllocationCreateMap() (map[string]interface{}, error) { + body, err := gophercloud.BuildRequestBody(opts, "") + if err != nil { + return nil, err + } + + return body, nil +} + +// Create requests a node to be created +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + reqBody, err := opts.ToAllocationCreateMap() + if err != nil { + r.Err = err + return + } + + _, r.Err = client.Post(createURL(client), reqBody, &r.Body, nil) + return +} + +type AllocationState string + +var ( + Allocating AllocationState = "allocating" + Active = "active" + Error = "error" +) + +// ListOptsBuilder allows extensions to add additional parameters to the List request. +type ListOptsBuilder interface { + ToAllocationListQuery() (string, error) +} + +// ListOpts allows the filtering and sorting of paginated collections through the API. +type ListOpts struct { + // Filter the list of allocations by the node UUID or name. + Node string `q:"node"` + + // Filter the list of returned nodes, and only return the ones with the specified resource class. + ResourceClass string `q:"resource_class"` + + // Filter the list of allocations by the allocation state, one of active, allocating or error. + State AllocationState `q:"state"` + + // One or more fields to be returned in the response. + Fields []string `q:"fields"` + + // Requests a page size of items. + Limit int `q:"limit"` + + // The ID of the last-seen item + Marker string `q:"marker"` + + // Sorts the response by the requested sort direction. + // Valid value is asc (ascending) or desc (descending). Default is asc. + SortDir string `q:"sort_dir"` + + // Sorts the response by the this attribute value. Default is id. + SortKey string `q:"sort_key"` +} + +// ToAllocationListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToAllocationListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List makes a request against the API to list allocations accessible to you. +func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(client) + if opts != nil { + query, err := opts.ToAllocationListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return AllocationPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Get requests the details of an allocation 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}, + }) + return +} + +// Delete requests the deletion of an allocation +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = client.Delete(deleteURL(client, id), nil) + return +} diff --git a/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/allocations/results.go b/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/allocations/results.go new file mode 100644 index 00000000000..cbd2115523e --- /dev/null +++ b/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/allocations/results.go @@ -0,0 +1,114 @@ +package allocations + +import ( + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +type Allocation struct { + // The UUID for the resource. + UUID string `json:"uuid"` + + // A list of UUIDs of the nodes that are candidates for this allocation. + CandidateNodes []string `json:"candidate_nodes"` + + // The error message for the allocation if it is in the error state, null otherwise. + LastError string `json:"last_error"` + + // The unique name of the allocation. + Name string `json:"name"` + + // The UUID of the node assigned to the allocation. Will be null if a node is not yet assigned. + NodeUUID string `json:"node_uuid"` + + // The current state of the allocation. One of: allocation, active, error + State string `json:"state"` + + // The resource class requested for the allocation. + ResourceClass string `json:"resource_class"` + + // The list of the traits requested for the allocation. + Traits []string `json:"traits"` + + // A set of one or more arbitrary metadata key and value pairs. + Extra map[string]string `json:"extra"` + + // The UTC date and time when the resource was created, ISO 8601 format. + CreatedAt time.Time `json:"created_at"` + + // The UTC date and time when the resource was updated, ISO 8601 format. May be “null”. + UpdatedAt time.Time `json:"updated_at"` + + // A list of relative links. Includes the self and bookmark links. + Links []interface{} `json:"links"` +} + +type allocationResult struct { + gophercloud.Result +} + +func (r allocationResult) Extract() (*Allocation, error) { + var s Allocation + err := r.ExtractInto(&s) + return &s, err +} + +func (r allocationResult) ExtractInto(v interface{}) error { + return r.Result.ExtractIntoStructPtr(v, "") +} + +func ExtractAllocationsInto(r pagination.Page, v interface{}) error { + return r.(AllocationPage).Result.ExtractIntoSlicePtr(v, "allocations") +} + +// AllocationPage abstracts the raw results of making a List() request against +// the API. +type AllocationPage struct { + pagination.LinkedPageBase +} + +// IsEmpty returns true if a page contains no Allocation results. +func (r AllocationPage) IsEmpty() (bool, error) { + s, err := ExtractAllocations(r) + return len(s) == 0, err +} + +// NextPageURL uses the response's embedded link reference to navigate to the +// next page of results. +func (r AllocationPage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"allocations_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// ExtractAllocations interprets the results of a single page from a List() call, +// producing a slice of Allocation entities. +func ExtractAllocations(r pagination.Page) ([]Allocation, error) { + var s []Allocation + err := ExtractAllocationsInto(r, &s) + return s, err +} + +// GetResult is the response from a Get operation. Call its Extract +// method to interpret it as a Allocation. +type GetResult struct { + allocationResult +} + +// CreateResult is the response from a Create operation. +type CreateResult struct { + allocationResult +} + +// DeleteResult is the response from a Delete operation. Call its ExtractErr +// method to determine if the call succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} diff --git a/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/allocations/urls.go b/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/allocations/urls.go new file mode 100644 index 00000000000..7163bbe334c --- /dev/null +++ b/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/allocations/urls.go @@ -0,0 +1,23 @@ +package allocations + +import "github.com/gophercloud/gophercloud" + +func createURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("allocations") +} + +func listURL(client *gophercloud.ServiceClient) string { + return createURL(client) +} + +func resourceURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("allocations", id) +} + +func deleteURL(client *gophercloud.ServiceClient, id string) string { + return resourceURL(client, id) +} + +func getURL(client *gophercloud.ServiceClient, id string) string { + return resourceURL(client, id) +} diff --git a/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes/doc.go b/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes/doc.go new file mode 100644 index 00000000000..37f60b5f3ef --- /dev/null +++ b/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes/doc.go @@ -0,0 +1,130 @@ +/* +Package nodes provides information and interaction with the nodes API +resource in the OpenStack Bare Metal service. + +Example to List Nodes with Detail + + nodes.ListDetail(client, nodes.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) { + nodeList, err := nodes.ExtractNodes(page) + if err != nil { + return false, err + } + + for _, n := range nodeList { + // Do something + } + + return true, nil + }) + +Example to List Nodes + + listOpts := nodes.ListOpts{ + ProvisionState: nodes.Deploying, + Fields: []string{"name"}, + } + + nodes.List(client, listOpts).EachPage(func(page pagination.Page) (bool, error) { + nodeList, err := nodes.ExtractNodes(page) + if err != nil { + return false, err + } + + for _, n := range nodeList { + // Do something + } + + return true, nil + }) + +Example to Create Node + + createOpts := nodes.CreateOpts + Driver: "ipmi", + BootInterface: "pxe", + Name: "coconuts", + DriverInfo: map[string]interface{}{ + "ipmi_port": "6230", + "ipmi_username": "admin", + "deploy_kernel": "http://172.22.0.1/images/tinyipa-stable-rocky.vmlinuz", + "ipmi_address": "192.168.122.1", + "deploy_ramdisk": "http://172.22.0.1/images/tinyipa-stable-rocky.gz", + "ipmi_password": "admin", + }, + } + + createNode, err := nodes.Create(client, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Get Node + + showNode, err := nodes.Get(client, "c9afd385-5d89-4ecb-9e1c-68194da6b474").Extract() + if err != nil { + panic(err) + } + +Example to Update Node + + updateOpts := nodes.UpdateOpts{ + nodes.UpdateOperation{ + Op: ReplaceOp, + Path: "/maintenance", + Value: "true", + }, + } + + updateNode, err := nodes.Update(client, "c9afd385-5d89-4ecb-9e1c-68194da6b474", updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete Node + + err = nodes.Delete(client, "c9afd385-5d89-4ecb-9e1c-68194da6b474").ExtractErr() + if err != nil { + panic(err) + } + +Example to Validate Node + + validation, err := nodes.Validate(client, "a62b8495-52e2-407b-b3cb-62775d04c2b8").Extract() + if err != nil { + panic(err) + } + +Example to inject non-masking interrupts + + err := nodes.InjectNMI(client, "a62b8495-52e2-407b-b3cb-62775d04c2b8").ExtractErr() + if err != nil { + panic(err) + } + +Example to get array of supported boot devices for a node + + bootDevices, err := nodes.GetSupportedBootDevices(client, "a62b8495-52e2-407b-b3cb-62775d04c2b8").Extract() + if err != nil { + panic(err) + } + +Example to set boot device for a node + + bootOpts := nodes.BootDeviceOpts{ + BootDevice: "pxe", + Persistent: false, + } + + err := nodes.SetBootDevice(client, "a62b8495-52e2-407b-b3cb-62775d04c2b8", bootOpts).ExtractErr() + if err != nil { + panic(err) + } + +Example to get boot device for a node + + bootDevice, err := nodes.GetBootDevice(client, "a62b8495-52e2-407b-b3cb-62775d04c2b8").Extract() + if err != nil { + panic(err) + } +*/ +package nodes diff --git a/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes/requests.go b/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes/requests.go new file mode 100644 index 00000000000..4707f7b250d --- /dev/null +++ b/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes/requests.go @@ -0,0 +1,593 @@ +package nodes + +import ( + "fmt" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToNodeListQuery() (string, error) + ToNodeListDetailQuery() (string, error) +} + +// Provision state reports the current provision state of the node, these are only used in filtering +type ProvisionState string + +const ( + Enroll ProvisionState = "enroll" + Verifying ProvisionState = "verifying" + Manageable ProvisionState = "manageable" + Available ProvisionState = "available" + Active ProvisionState = "active" + DeployWait ProvisionState = "wait call-back" + Deploying ProvisionState = "deploying" + DeployFail ProvisionState = "deploy failed" + DeployDone ProvisionState = "deploy complete" + Deleting ProvisionState = "deleting" + Deleted ProvisionState = "deleted" + Cleaning ProvisionState = "cleaning" + CleanWait ProvisionState = "clean wait" + CleanFail ProvisionState = "clean failed" + Error ProvisionState = "error" + Rebuild ProvisionState = "rebuild" + Inspecting ProvisionState = "inspecting" + InspectFail ProvisionState = "inspect failed" + InspectWait ProvisionState = "inspect wait" + Adopting ProvisionState = "adopting" + AdoptFail ProvisionState = "adopt failed" + Rescue ProvisionState = "rescue" + RescueFail ProvisionState = "rescue failed" + Rescuing ProvisionState = "rescuing" + UnrescueFail ProvisionState = "unrescue failed" +) + +// TargetProvisionState is used when setting the provision state for a node. +type TargetProvisionState string + +const ( + TargetActive TargetProvisionState = "active" + TargetDeleted TargetProvisionState = "deleted" + TargetManage TargetProvisionState = "manage" + TargetProvide TargetProvisionState = "provide" + TargetInspect TargetProvisionState = "inspect" + TargetAbort TargetProvisionState = "abort" + TargetClean TargetProvisionState = "clean" + TargetAdopt TargetProvisionState = "adopt" + TargetRescue TargetProvisionState = "rescue" + TargetUnrescue TargetProvisionState = "unrescue" +) + +// 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 node attributes you want to see returned. Marker and Limit are used +// for pagination. +type ListOpts struct { + // Filter the list by specific instance UUID + InstanceUUID string `q:"instance_uuid"` + + // Filter the list by chassis UUID + ChassisUUID string `q:"chassis_uuid"` + + // Filter the list by maintenance set to True or False + Maintenance bool `q:"maintenance"` + + // Nodes which are, or are not, associated with an instance_uuid. + Associated bool `q:"associated"` + + // Only return those with the specified provision_state. + ProvisionState ProvisionState `q:"provision_state"` + + // Filter the list with the specified driver. + Driver string `q:"driver"` + + // Filter the list with the specified resource class. + ResourceClass string `q:"resource_class"` + + // Filter the list with the specified conductor_group. + ConductorGroup string `q:"conductor_group"` + + // Filter the list with the specified fault. + Fault string `q:"fault"` + + // One or more fields to be returned in the response. + Fields []string `q:"fields"` + + // Requests a page size of items. + Limit int `q:"limit"` + + // The ID of the last-seen item. + Marker string `q:"marker"` + + // Sorts the response by the requested sort direction. + SortDir string `q:"sort_dir"` + + // Sorts the response by the this attribute value. + SortKey string `q:"sort_key"` + + // A string or UUID of the tenant who owns the baremetal node. + Owner string `q:"owner"` +} + +// ToNodeListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToNodeListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List makes a request against the API to list nodes accessible to you. +func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(client) + if opts != nil { + query, err := opts.ToNodeListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return NodePage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// ToNodeListDetailQuery formats a ListOpts into a query string for the list details API. +func (opts ListOpts) ToNodeListDetailQuery() (string, error) { + // Detail endpoint can't filter by Fields + if len(opts.Fields) > 0 { + return "", fmt.Errorf("fields is not a valid option when getting a detailed listing of nodes") + } + + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// Return a list of bare metal Nodes with complete details. Some filtering is possible by passing in flags in ListOpts, +// but you cannot limit by the fields returned. +func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + // This URL is deprecated. In the future, we should compare the microversion and if >= 1.43, hit the listURL + // with ListOpts{Detail: true,} + url := listDetailURL(client) + if opts != nil { + query, err := opts.ToNodeListDetailQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return NodePage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Get requests details on a single node, 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}, + }) + return +} + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToNodeCreateMap() (map[string]interface{}, error) +} + +// CreateOpts specifies node creation parameters. +type CreateOpts struct { + // The boot interface for a Node, e.g. “pxe”. + BootInterface string `json:"boot_interface,omitempty"` + + // The conductor group for a node. Case-insensitive string up to 255 characters, containing a-z, 0-9, _, -, and .. + ConductorGroup string `json:"conductor_group,omitempty"` + + // The console interface for a node, e.g. “no-console”. + ConsoleInterface string `json:"console_interface,omitempty"` + + // The deploy interface for a node, e.g. “iscsi”. + DeployInterface string `json:"deploy_interface,omitempty"` + + // All the metadata required by the driver to manage this Node. List of fields varies between drivers, and can + // be retrieved from the /v1/drivers//properties resource. + DriverInfo map[string]interface{} `json:"driver_info,omitempty"` + + // name of the driver used to manage this Node. + Driver string `json:"driver,omitempty"` + + // A set of one or more arbitrary metadata key and value pairs. + Extra map[string]interface{} `json:"extra,omitempty"` + + // The interface used for node inspection, e.g. “no-inspect”. + InspectInterface string `json:"inspect_interface,omitempty"` + + // Interface for out-of-band node management, e.g. “ipmitool”. + ManagementInterface string `json:"management_interface,omitempty"` + + // Human-readable identifier for the Node resource. May be undefined. Certain words are reserved. + Name string `json:"name,omitempty"` + + // Which Network Interface provider to use when plumbing the network connections for this Node. + NetworkInterface string `json:"network_interface,omitempty"` + + // Interface used for performing power actions on the node, e.g. “ipmitool”. + PowerInterface string `json:"power_interface,omitempty"` + + // Physical characteristics of this Node. Populated during inspection, if performed. Can be edited via the REST + // API at any time. + Properties map[string]interface{} `json:"properties,omitempty"` + + // Interface used for configuring RAID on this node, e.g. “no-raid”. + RAIDInterface string `json:"raid_interface,omitempty"` + + // The interface used for node rescue, e.g. “no-rescue”. + RescueInterface string `json:"rescue_interface,omitempty"` + + // A string which can be used by external schedulers to identify this Node as a unit of a specific type + // of resource. + ResourceClass string `json:"resource_class,omitempty"` + + // Interface used for attaching and detaching volumes on this node, e.g. “cinder”. + StorageInterface string `json:"storage_interface,omitempty"` + + // The UUID for the resource. + UUID string `json:"uuid,omitempty"` + + // Interface for vendor-specific functionality on this node, e.g. “no-vendor”. + VendorInterface string `json:"vendor_interface,omitempty"` + + // A string or UUID of the tenant who owns the baremetal node. + Owner string `json:"owner,omitempty"` +} + +// ToNodeCreateMap assembles a request body based on the contents of a CreateOpts. +func (opts CreateOpts) ToNodeCreateMap() (map[string]interface{}, error) { + body, err := gophercloud.BuildRequestBody(opts, "") + if err != nil { + return nil, err + } + + return body, nil +} + +// Create requests a node to be created +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + reqBody, err := opts.ToNodeCreateMap() + if err != nil { + r.Err = err + return + } + + _, r.Err = client.Post(createURL(client), reqBody, &r.Body, nil) + return +} + +type Patch interface { + ToNodeUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts is a slice of Patches used to update a node +type UpdateOpts []Patch + +type UpdateOp string + +const ( + ReplaceOp UpdateOp = "replace" + AddOp UpdateOp = "add" + RemoveOp UpdateOp = "remove" +) + +type UpdateOperation struct { + Op UpdateOp `json:"op" required:"true"` + Path string `json:"path" required:"true"` + Value interface{} `json:"value,omitempty"` +} + +func (opts UpdateOperation) ToNodeUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "") +} + +// Update requests that a node be updated +func Update(client *gophercloud.ServiceClient, id string, opts UpdateOpts) (r UpdateResult) { + body := make([]map[string]interface{}, len(opts)) + for i, patch := range opts { + result, err := patch.ToNodeUpdateMap() + if err != nil { + r.Err = err + return + } + + body[i] = result + } + _, r.Err = client.Patch(updateURL(client, id), body, &r.Body, &gophercloud.RequestOpts{ + JSONBody: &body, + OkCodes: []int{200}, + }) + return +} + +// Delete requests that a node be removed +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = client.Delete(deleteURL(client, id), nil) + return +} + +// Request that Ironic validate whether the Node’s driver has enough information to manage the Node. This polls each +// interface on the driver, and returns the status of that interface. +func Validate(client *gophercloud.ServiceClient, id string) (r ValidateResult) { + _, r.Err = client.Get(validateURL(client, id), &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// Inject NMI (Non-Masking Interrupts) for the given Node. This feature can be used for hardware diagnostics, and +// actual support depends on a driver. +func InjectNMI(client *gophercloud.ServiceClient, id string) (r InjectNMIResult) { + _, r.Err = client.Put(injectNMIURL(client, id), map[string]string{}, nil, &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + return +} + +type BootDeviceOpts struct { + BootDevice string `json:"boot_device"` // e.g., 'pxe', 'disk', etc. + Persistent bool `json:"persistent"` // Whether this is one-time or not +} + +// BootDeviceOptsBuilder allows extensions to add additional parameters to the +// SetBootDevice request. +type BootDeviceOptsBuilder interface { + ToBootDeviceMap() (map[string]interface{}, error) +} + +// ToBootDeviceSetMap assembles a request body based on the contents of a BootDeviceOpts. +func (opts BootDeviceOpts) ToBootDeviceMap() (map[string]interface{}, error) { + body, err := gophercloud.BuildRequestBody(opts, "") + if err != nil { + return nil, err + } + + return body, nil +} + +// Set the boot device for the given Node, and set it persistently or for one-time boot. The exact behaviour +// of this depends on the hardware driver. +func SetBootDevice(client *gophercloud.ServiceClient, id string, bootDevice BootDeviceOptsBuilder) (r SetBootDeviceResult) { + reqBody, err := bootDevice.ToBootDeviceMap() + if err != nil { + r.Err = err + return + } + + _, r.Err = client.Put(bootDeviceURL(client, id), reqBody, nil, &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + return +} + +// Get the current boot device for the given Node. +func GetBootDevice(client *gophercloud.ServiceClient, id string) (r BootDeviceResult) { + _, r.Err = client.Get(bootDeviceURL(client, id), &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// Retrieve the acceptable set of supported boot devices for a specific Node. +func GetSupportedBootDevices(client *gophercloud.ServiceClient, id string) (r SupportedBootDeviceResult) { + _, r.Err = client.Get(supportedBootDeviceURL(client, id), &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// A cleaning step has required keys ‘interface’ and ‘step’, and optional key ‘args’. If specified, +// the value for ‘args’ is a keyword variable argument dictionary that is passed to the cleaning step +// method. +type CleanStep struct { + Interface string `json:"interface" required:"true"` + Step string `json:"step" required:"true"` + Args map[string]string `json:"args,omitempty"` +} + +// ProvisionStateOptsBuilder allows extensions to add additional parameters to the +// ChangeProvisionState request. +type ProvisionStateOptsBuilder interface { + ToProvisionStateMap() (map[string]interface{}, error) +} + +// Starting with Ironic API version 1.56, a configdrive may be a JSON object with structured data. +// Prior to this version, it must be a base64-encoded, gzipped ISO9660 image. +type ConfigDrive struct { + MetaData map[string]interface{} `json:"meta_data,omitempty"` + NetworkData map[string]interface{} `json:"network_data,omitempty"` + UserData interface{} `json:"user_data,omitempty"` +} + +// ProvisionStateOpts for a request to change a node's provision state. A config drive should be base64-encoded +// gzipped ISO9660 image. +type ProvisionStateOpts struct { + Target TargetProvisionState `json:"target" required:"true"` + ConfigDrive interface{} `json:"configdrive,omitempty"` + CleanSteps []CleanStep `json:"clean_steps,omitempty"` + RescuePassword string `json:"rescue_password,omitempty"` +} + +// ToProvisionStateMap assembles a request body based on the contents of a CreateOpts. +func (opts ProvisionStateOpts) ToProvisionStateMap() (map[string]interface{}, error) { + body, err := gophercloud.BuildRequestBody(opts, "") + if err != nil { + return nil, err + } + + return body, nil +} + +// Request a change to the Node’s provision state. Acceptable target states depend on the Node’s current provision +// state. More detailed documentation of the Ironic State Machine is available in the developer docs. +func ChangeProvisionState(client *gophercloud.ServiceClient, id string, opts ProvisionStateOptsBuilder) (r ChangeStateResult) { + reqBody, err := opts.ToProvisionStateMap() + if err != nil { + r.Err = err + return + } + + _, r.Err = client.Put(provisionStateURL(client, id), reqBody, nil, &gophercloud.RequestOpts{ + OkCodes: []int{202}, + }) + return +} + +type TargetPowerState string + +// TargetPowerState is used when changing the power state of a node. +const ( + PowerOn TargetPowerState = "power on" + PowerOff TargetPowerState = "power off" + Rebooting TargetPowerState = "rebooting" + SoftPowerOff TargetPowerState = "soft power off" + SoftRebooting TargetPowerState = "soft rebooting" +) + +// PowerStateOptsBuilder allows extensions to add additional parameters to the ChangePowerState request. +type PowerStateOptsBuilder interface { + ToPowerStateMap() (map[string]interface{}, error) +} + +// PowerStateOpts for a request to change a node's power state. +type PowerStateOpts struct { + Target TargetPowerState `json:"target" required:"true"` + Timeout int `json:"timeout,omitempty"` +} + +// ToPowerStateMap assembles a request body based on the contents of a PowerStateOpts. +func (opts PowerStateOpts) ToPowerStateMap() (map[string]interface{}, error) { + body, err := gophercloud.BuildRequestBody(opts, "") + if err != nil { + return nil, err + } + + return body, nil +} + +// Request to change a Node's power state. +func ChangePowerState(client *gophercloud.ServiceClient, id string, opts PowerStateOptsBuilder) (r ChangePowerStateResult) { + reqBody, err := opts.ToPowerStateMap() + if err != nil { + r.Err = err + return + } + + _, r.Err = client.Put(powerStateURL(client, id), reqBody, nil, &gophercloud.RequestOpts{ + OkCodes: []int{202}, + }) + return +} + +// This is the desired RAID configuration on the bare metal node. +type RAIDConfigOpts struct { + LogicalDisks []LogicalDisk `json:"logical_disks"` +} + +// RAIDConfigOptsBuilder allows extensions to modify a set RAID config request. +type RAIDConfigOptsBuilder interface { + ToRAIDConfigMap() (map[string]interface{}, error) +} + +// RAIDLevel type is used to specify the RAID level for a logical disk. +type RAIDLevel string + +const ( + RAID0 RAIDLevel = "0" + RAID1 RAIDLevel = "1" + RAID2 RAIDLevel = "2" + RAID5 RAIDLevel = "5" + RAID6 RAIDLevel = "6" + RAID10 RAIDLevel = "1+0" + RAID50 RAIDLevel = "5+0" + RAID60 RAIDLevel = "6+0" +) + +// DiskType is used to specify the disk type for a logical disk, e.g. hdd or ssd. +type DiskType string + +const ( + HDD DiskType = "hdd" + SSD DiskType = "ssd" +) + +// InterfaceType is used to specify the interface for a logical disk. +type InterfaceType string + +const ( + SATA DiskType = "sata" + SCSI DiskType = "scsi" + SAS DiskType = "sas" +) + +type LogicalDisk struct { + // Size (Integer) of the logical disk to be created in GiB. If unspecified, "MAX" will be used. + SizeGB *int `json:"size_gb"` + + // RAID level for the logical disk. + RAIDLevel RAIDLevel `json:"raid_level" required:"true"` + + // Name of the volume. Should be unique within the Node. If not specified, volume name will be auto-generated. + VolumeName string `json:"volume_name,omitempty"` + + // Set to true if this is the root volume. At most one logical disk can have this set to true. + IsRootVolume *bool `json:"is_root_volume,omitempty"` + + // Set to true if this logical disk can share physical disks with other logical disks. + SharePhysicalDisks *bool `json:"share_physical_disks,omitempty"` + + // If this is not specified, disk type will not be a criterion to find backing physical disks + DiskType DiskType `json:"disk_type,omitempty"` + + // If this is not specified, interface type will not be a criterion to find backing physical disks. + InterfaceType InterfaceType `json:"interface_type,omitempty"` + + // Integer, number of disks to use for the logical disk. Defaults to minimum number of disks required + // for the particular RAID level. + NumberOfPhysicalDisks int `json:"number_of_physical_disks,omitempty"` + + // The name of the controller as read by the RAID interface. + Controller string `json:"controller,omitempty"` + + // A list of physical disks to use as read by the RAID interface. + PhysicalDisks []string `json:"physical_disks,omitempty"` +} + +func (opts RAIDConfigOpts) ToRAIDConfigMap() (map[string]interface{}, error) { + body, err := gophercloud.BuildRequestBody(opts, "") + if err != nil { + return nil, err + } + + for _, v := range body["logical_disks"].([]interface{}) { + if logicalDisk, ok := v.(map[string]interface{}); ok { + if logicalDisk["size_gb"] == nil { + logicalDisk["size_gb"] = "MAX" + } + } + } + + return body, nil +} + +// Request to change a Node's RAID config. +func SetRAIDConfig(client *gophercloud.ServiceClient, id string, raidConfigOptsBuilder RAIDConfigOptsBuilder) (r ChangeStateResult) { + reqBody, err := raidConfigOptsBuilder.ToRAIDConfigMap() + if err != nil { + r.Err = err + return + } + + _, r.Err = client.Put(raidConfigURL(client, id), reqBody, nil, &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + return +} diff --git a/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes/results.go b/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes/results.go new file mode 100644 index 00000000000..8d61b8d3bd4 --- /dev/null +++ b/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes/results.go @@ -0,0 +1,313 @@ +package nodes + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +type nodeResult struct { + gophercloud.Result +} + +// Extract interprets any nodeResult as a Node, if possible. +func (r nodeResult) Extract() (*Node, error) { + var s Node + err := r.ExtractInto(&s) + return &s, err +} + +// Extract interprets a BootDeviceResult as BootDeviceOpts, if possible. +func (r BootDeviceResult) Extract() (*BootDeviceOpts, error) { + var s BootDeviceOpts + err := r.ExtractInto(&s) + return &s, err +} + +// Extract interprets a SupportedBootDeviceResult as an array of supported boot devices, if possible. +func (r SupportedBootDeviceResult) Extract() ([]string, error) { + var s struct { + Devices []string `json:"supported_boot_devices"` + } + + err := r.ExtractInto(&s) + return s.Devices, err +} + +// Extract interprets a ValidateResult as NodeValidation, if possible. +func (r ValidateResult) Extract() (*NodeValidation, error) { + var s NodeValidation + err := r.ExtractInto(&s) + return &s, err +} + +func (r nodeResult) ExtractInto(v interface{}) error { + return r.Result.ExtractIntoStructPtr(v, "") +} + +func ExtractNodesInto(r pagination.Page, v interface{}) error { + return r.(NodePage).Result.ExtractIntoSlicePtr(v, "nodes") +} + +// Node represents a node in the OpenStack Bare Metal API. +type Node struct { + // UUID for the resource. + UUID string `json:"uuid"` + + // Identifier for the Node resource. May be undefined. Certain words are reserved. + Name string `json:"name"` + + // Current power state of this Node. Usually, “power on” or “power off”, but may be “None” + // if Ironic is unable to determine the power state (eg, due to hardware failure). + PowerState string `json:"power_state"` + + // A power state transition has been requested, this field represents the requested (ie, “target”) + // state either “power on”, “power off”, “rebooting”, “soft power off” or “soft rebooting”. + TargetPowerState string `json:"target_power_state"` + + // Current provisioning state of this Node. + ProvisionState string `json:"provision_state"` + + // A provisioning action has been requested, this field represents the requested (ie, “target”) state. Note + // that a Node may go through several states during its transition to this target state. For instance, when + // requesting an instance be deployed to an AVAILABLE Node, the Node may go through the following state + // change progression: AVAILABLE -> DEPLOYING -> DEPLOYWAIT -> DEPLOYING -> ACTIVE + TargetProvisionState string `json:"target_provision_state"` + + // Whether or not this Node is currently in “maintenance mode”. Setting a Node into maintenance mode removes it + // from the available resource pool and halts some internal automation. This can happen manually (eg, via an API + // request) or automatically when Ironic detects a hardware fault that prevents communication with the machine. + Maintenance bool `json:"maintenance"` + + // Description of the reason why this Node was placed into maintenance mode + MaintenanceReason string `json:"maintenance_reason"` + + // Fault indicates the active fault detected by ironic, typically the Node is in “maintenance mode”. None means no + // fault has been detected by ironic. “power failure” indicates ironic failed to retrieve power state from this + // node. There are other possible types, e.g., “clean failure” and “rescue abort failure”. + Fault string `json:"fault"` + + // Error from the most recent (last) transaction that started but failed to finish. + LastError string `json:"last_error"` + + // Name of an Ironic Conductor host which is holding a lock on this node, if a lock is held. Usually “null”, + // but this field can be useful for debugging. + Reservation string `json:"reservation"` + + // Name of the driver. + Driver string `json:"driver"` + + // The metadata required by the driver to manage this Node. List of fields varies between drivers, and can be + // retrieved from the /v1/drivers//properties resource. + DriverInfo map[string]interface{} `json:"driver_info"` + + // Metadata set and stored by the Node’s driver. This field is read-only. + DriverInternalInfo map[string]interface{} `json:"driver_internal_info"` + + // Characteristics of this Node. Populated by ironic-inspector during inspection. May be edited via the REST + // API at any time. + Properties map[string]interface{} `json:"properties"` + + // Used to customize the deployed image. May include root partition size, a base 64 encoded config drive, and other + // metadata. Note that this field is erased automatically when the instance is deleted (this is done by requesting + // the Node provision state be changed to DELETED). + InstanceInfo map[string]interface{} `json:"instance_info"` + + // ID of the Nova instance associated with this Node. + InstanceUUID string `json:"instance_uuid"` + + // ID of the chassis associated with this Node. May be empty or None. + ChassisUUID string `json:"chassis_uuid"` + + // Set of one or more arbitrary metadata key and value pairs. + Extra map[string]interface{} `json:"extra"` + + // Whether console access is enabled or disabled on this node. + ConsoleEnabled bool `json:"console_enabled"` + + // The current RAID configuration of the node. Introduced with the cleaning feature. + RAIDConfig map[string]interface{} `json:"raid_config"` + + // The requested RAID configuration of the node, which will be applied when the Node next transitions + // through the CLEANING state. Introduced with the cleaning feature. + TargetRAIDConfig map[string]interface{} `json:"target_raid_config"` + + // Current clean step. Introduced with the cleaning feature. + CleanStep map[string]interface{} `json:"clean_step"` + + // Current deploy step. + DeployStep map[string]interface{} `json:"deploy_step"` + + // String which can be used by external schedulers to identify this Node as a unit of a specific type of resource. + // For more details, see: https://docs.openstack.org/ironic/latest/install/configure-nova-flavors.html + ResourceClass string `json:"resource_class"` + + // Boot interface for a Node, e.g. “pxe”. + BootInterface string `json:"boot_interface"` + + // Console interface for a node, e.g. “no-console”. + ConsoleInterface string `json:"console_interface"` + + // Deploy interface for a node, e.g. “iscsi”. + DeployInterface string `json:"deploy_interface"` + + // Interface used for node inspection, e.g. “no-inspect”. + InspectInterface string `json:"inspect_interface"` + + // For out-of-band node management, e.g. “ipmitool”. + ManagementInterface string `json:"management_interface"` + + // Network Interface provider to use when plumbing the network connections for this Node. + NetworkInterface string `json:"network_interface"` + + // used for performing power actions on the node, e.g. “ipmitool”. + PowerInterface string `json:"power_interface"` + + // Used for configuring RAID on this node, e.g. “no-raid”. + RAIDInterface string `json:"raid_interface"` + + // Interface used for node rescue, e.g. “no-rescue”. + RescueInterface string `json:"rescue_interface"` + + // Used for attaching and detaching volumes on this node, e.g. “cinder”. + StorageInterface string `json:"storage_interface"` + + // Array of traits for this node. + Traits []string `json:"traits"` + + // For vendor-specific functionality on this node, e.g. “no-vendor”. + VendorInterface string `json:"vendor_interface"` + + // Conductor group for a node. Case-insensitive string up to 255 characters, containing a-z, 0-9, _, -, and .. + ConductorGroup string `json:"conductor_group"` + + // The node is protected from undeploying, rebuilding and deletion. + Protected bool `json:"protected"` + + // Reason the node is marked as protected. + ProtectedReason string `json:"protected_reason"` + + // A string or UUID of the tenant who owns the baremetal node. + Owner string `json:"owner"` +} + +// NodePage 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 ExtractNodes call. +type NodePage struct { + pagination.LinkedPageBase +} + +// IsEmpty returns true if a page contains no Node results. +func (r NodePage) IsEmpty() (bool, error) { + s, err := ExtractNodes(r) + return len(s) == 0, err +} + +// NextPageURL uses the response's embedded link reference to navigate to the +// next page of results. +func (r NodePage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"nodes_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// ExtractNodes interprets the results of a single page from a List() call, +// producing a slice of Node entities. +func ExtractNodes(r pagination.Page) ([]Node, error) { + var s []Node + err := ExtractNodesInto(r, &s) + return s, err +} + +// GetResult is the response from a Get operation. Call its Extract +// method to interpret it as a Node. +type GetResult struct { + nodeResult +} + +// CreateResult is the response from a Create operation. +type CreateResult struct { + nodeResult +} + +// UpdateResult is the response from an Update operation. Call its Extract +// method to interpret it as a Node. +type UpdateResult struct { + nodeResult +} + +// DeleteResult is the response from a Delete operation. Call its ExtractErr +// method to determine if the call succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// ValidateResult is the response from a Validate operation. Call its Extract +// method to interpret it as a NodeValidation struct. +type ValidateResult struct { + gophercloud.Result +} + +// InjectNMIResult is the response from an InjectNMI operation. Call its ExtractErr +// method to determine if the call succeeded or failed. +type InjectNMIResult struct { + gophercloud.ErrResult +} + +// BootDeviceResult is the response from a GetBootDevice operation. Call its Extract +// method to interpret it as a BootDeviceOpts struct. +type BootDeviceResult struct { + gophercloud.Result +} + +// BootDeviceResult is the response from a GetBootDevice operation. Call its Extract +// method to interpret it as a BootDeviceOpts struct. +type SetBootDeviceResult struct { + gophercloud.ErrResult +} + +// SupportedBootDeviceResult is the response from a GetSupportedBootDevices operation. Call its Extract +// method to interpret it as an array of supported boot device values. +type SupportedBootDeviceResult struct { + gophercloud.Result +} + +// ChangePowerStateResult is the response from a ChangePowerState operation. Call its ExtractErr +// method to determine if the call succeeded or failed. +type ChangePowerStateResult struct { + gophercloud.ErrResult +} + +// Each element in the response will contain a “result” variable, which will have a value of “true” or “false”, and +// also potentially a reason. A value of nil indicates that the Node’s driver does not support that interface. +type DriverValidation struct { + Result bool `json:"result"` + Reason string `json:"reason"` +} + +// Ironic validates whether the Node’s driver has enough information to manage the Node. This polls each interface on +// the driver, and returns the status of that interface as an DriverValidation struct. +type NodeValidation struct { + Boot DriverValidation `json:"boot"` + Console DriverValidation `json:"console"` + Deploy DriverValidation `json:"deploy"` + Inspect DriverValidation `json:"inspect"` + Management DriverValidation `json:"management"` + Network DriverValidation `json:"network"` + Power DriverValidation `json:"power"` + RAID DriverValidation `json:"raid"` + Rescue DriverValidation `json:"rescue"` + Storage DriverValidation `json:"storage"` +} + +// ChangeStateResult is the response from any state change operation. Call its ExtractErr +// method to determine if the call succeeded or failed. +type ChangeStateResult struct { + gophercloud.ErrResult +} diff --git a/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes/urls.go b/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes/urls.go new file mode 100644 index 00000000000..c7ef5503655 --- /dev/null +++ b/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes/urls.go @@ -0,0 +1,59 @@ +package nodes + +import "github.com/gophercloud/gophercloud" + +func createURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("nodes") +} + +func listURL(client *gophercloud.ServiceClient) string { + return createURL(client) +} + +func listDetailURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("nodes", "detail") +} + +func deleteURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("nodes", 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 validateURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("nodes", id, "validate") +} + +func injectNMIURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("nodes", id, "management", "inject_nmi") +} + +func bootDeviceURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("nodes", id, "management", "boot_device") +} + +func supportedBootDeviceURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("nodes", id, "management", "boot_device", "supported") +} + +func statesResourceURL(client *gophercloud.ServiceClient, id string, state string) string { + return client.ServiceURL("nodes", id, "states", state) +} + +func powerStateURL(client *gophercloud.ServiceClient, id string) string { + return statesResourceURL(client, id, "power") +} + +func provisionStateURL(client *gophercloud.ServiceClient, id string) string { + return statesResourceURL(client, id, "provision") +} + +func raidConfigURL(client *gophercloud.ServiceClient, id string) string { + return statesResourceURL(client, id, "raid") +} diff --git a/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/ports/doc.go b/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/ports/doc.go new file mode 100644 index 00000000000..eb0579bed57 --- /dev/null +++ b/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/ports/doc.go @@ -0,0 +1,85 @@ +/* + Package ports contains the functionality to Listing, Searching, Creating, Updating, + and Deleting of bare metal Port resources + + API reference: https://developer.openstack.org/api-ref/baremetal/#ports-ports + + +Example to List Ports with Detail + + ports.ListDetail(client, nil).EachPage(func(page pagination.Page) (bool, error) { + portList, err := ports.ExtractPorts(page) + if err != nil { + return false, err + } + + for _, n := range portList { + // Do something + } + + return true, nil + }) + +Example to List Ports + + listOpts := ports.ListOpts{ + Limit: 10, + } + + ports.List(client, listOpts).EachPage(func(page pagination.Page) (bool, error) { + portList, err := ports.ExtractPorts(page) + if err != nil { + return false, err + } + + for _, n := range portList { + // Do something + } + + return true, nil + }) + +Example to Create a Port + + createOpts := ports.CreateOpts{ + NodeUUID: "e8920409-e07e-41bb-8cc1-72acb103e2dd", + Address: "00:1B:63:84:45:E6", + PhysicalNetwork: "my-network", + } + + createPort, err := ports.Create(client, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Get a Port + + showPort, err := ports.Get(client, "c9afd385-5d89-4ecb-9e1c-68194da6b474").Extract() + if err != nil { + panic(err) + } + +Example to Update a Port + + updateOpts := ports.UpdateOpts{ + ports.UpdateOperation{ + Op: ReplaceOp, + Path: "/address", + Value: "22:22:22:22:22:22", + }, + } + + updatePort, err := ports.Update(client, "c9afd385-5d89-4ecb-9e1c-68194da6b474", updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Port + + err = ports.Delete(client, "c9afd385-5d89-4ecb-9e1c-68194da6b474").ExtractErr() + if err != nil { + panic(err) + } + +*/ +package ports diff --git a/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/ports/requests.go b/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/ports/requests.go new file mode 100644 index 00000000000..a5da3f83478 --- /dev/null +++ b/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/ports/requests.go @@ -0,0 +1,216 @@ +package ports + +import ( + "fmt" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToPortListQuery() (string, error) + ToPortListDetailQuery() (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 node attributes you want to see returned. Marker and Limit are used +// for pagination. +type ListOpts struct { + // Filter the list by the name or uuid of the Node + Node string `q:"node"` + + // Filter the list by the Node uuid + NodeUUID string `q:"node_uuid"` + + // Filter the list with the specified Portgroup (name or UUID) + PortGroup string `q:"portgroup"` + + // Filter the list with the specified physical hardware address, typically MAC + Address string `q:"address"` + + // One or more fields to be returned in the response. + Fields []string `q:"fields"` + + // Requests a page size of items. + Limit int `q:"limit"` + + // The ID of the last-seen item + Marker string `q:"marker"` + + // Sorts the response by the requested sort direction. + // Valid value is asc (ascending) or desc (descending). Default is asc. + SortDir string `q:"sort_dir"` + + // Sorts the response by the this attribute value. Default is id. + SortKey string `q:"sort_key"` +} + +// ToPortListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToPortListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List makes a request against the API to list ports accessible to you. +func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(client) + if opts != nil { + query, err := opts.ToPortListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return PortPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// ToPortListDetailQuery formats a ListOpts into a query string for the list details API. +func (opts ListOpts) ToPortListDetailQuery() (string, error) { + // Detail endpoint can't filter by Fields + if len(opts.Fields) > 0 { + return "", fmt.Errorf("fields is not a valid option when getting a detailed listing of ports") + } + + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// ListDetail - Return a list ports with complete details. +// Some filtering is possible by passing in flags in "ListOpts", +// but you cannot limit by the fields returned. +func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listDetailURL(client) + if opts != nil { + query, err := opts.ToPortListDetailQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return PortPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Get - requests the details off a port, 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}, + }) + return +} + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToPortCreateMap() (map[string]interface{}, error) +} + +// CreateOpts specifies port creation parameters. +type CreateOpts struct { + // UUID of the Node this resource belongs to. + NodeUUID string `json:"node_uuid,omitempty"` + + // Physical hardware address of this network Port, + // typically the hardware MAC address. + Address string `json:"address,omitempty"` + + // UUID of the Portgroup this resource belongs to. + PortGroupUUID string `json:"portgroup_uuid,omitempty"` + + // The Port binding profile. If specified, must contain switch_id (only a MAC + // address or an OpenFlow based datapath_id of the switch are accepted in this + // field) and port_id (identifier of the physical port on the switch to which + // node’s port is connected to) fields. switch_info is an optional string + // field to be used to store any vendor-specific information. + LocalLinkConnection map[string]interface{} `json:"local_link_connection,omitempty"` + + // Indicates whether PXE is enabled or disabled on the Port. + PXEEnabled *bool `json:"pxe_enabled,omitempty"` + + // The name of the physical network to which a port is connected. May be empty. + PhysicalNetwork string `json:"physical_network,omitempty"` + + // A set of one or more arbitrary metadata key and value pairs. + Extra map[string]interface{} `json:"extra,omitempty"` + + // Indicates whether the Port is a Smart NIC port. + IsSmartNIC *bool `json:"is_smartnic,omitempty"` +} + +// ToPortCreateMap assembles a request body based on the contents of a CreateOpts. +func (opts CreateOpts) ToPortCreateMap() (map[string]interface{}, error) { + body, err := gophercloud.BuildRequestBody(opts, "") + if err != nil { + return nil, err + } + + return body, nil +} + +// Create - requests the creation of a port +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + reqBody, err := opts.ToPortCreateMap() + if err != nil { + r.Err = err + return + } + + _, r.Err = client.Post(createURL(client), reqBody, &r.Body, nil) + return +} + +// TODO Update +type Patch interface { + ToPortUpdateMap() map[string]interface{} +} + +// UpdateOpts is a slice of Patches used to update a port +type UpdateOpts []Patch + +type UpdateOp string + +const ( + ReplaceOp UpdateOp = "replace" + AddOp UpdateOp = "add" + RemoveOp UpdateOp = "remove" +) + +type UpdateOperation struct { + Op UpdateOp `json:"op,required"` + Path string `json:"path,required"` + Value interface{} `json:"value,omitempty"` +} + +func (opts UpdateOperation) ToPortUpdateMap() map[string]interface{} { + return map[string]interface{}{ + "op": opts.Op, + "path": opts.Path, + "value": opts.Value, + } +} + +// Update - requests the update of a port +func Update(client *gophercloud.ServiceClient, id string, opts UpdateOpts) (r UpdateResult) { + body := make([]map[string]interface{}, len(opts)) + for i, patch := range opts { + body[i] = patch.ToPortUpdateMap() + } + + _, r.Err = client.Patch(updateURL(client, id), body, &r.Body, &gophercloud.RequestOpts{ + JSONBody: &body, + OkCodes: []int{200}, + }) + return +} + +// Delete - requests the deletion of a port +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = client.Delete(deleteURL(client, id), nil) + return +} diff --git a/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/ports/results.go b/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/ports/results.go new file mode 100644 index 00000000000..506b6c64a7e --- /dev/null +++ b/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/ports/results.go @@ -0,0 +1,131 @@ +package ports + +import ( + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +type portResult struct { + gophercloud.Result +} + +func (r portResult) Extract() (*Port, error) { + var s Port + err := r.ExtractInto(&s) + return &s, err +} + +func (r portResult) ExtractInto(v interface{}) error { + return r.Result.ExtractIntoStructPtr(v, "") +} + +func ExtractPortsInto(r pagination.Page, v interface{}) error { + return r.(PortPage).Result.ExtractIntoSlicePtr(v, "ports") +} + +// Port represents a port in the OpenStack Bare Metal API. +type Port struct { + // UUID for the resource. + UUID string `json:"uuid"` + + // Physical hardware address of this network Port, + // typically the hardware MAC address. + Address string `json:"address"` + + // UUID of the Node this resource belongs to. + NodeUUID string `json:"node_uuid"` + + // UUID of the Portgroup this resource belongs to. + PortGroupUUID string `json:"portgroup_uuid"` + + // The Port binding profile. If specified, must contain switch_id (only a MAC + // address or an OpenFlow based datapath_id of the switch are accepted in this + // field) and port_id (identifier of the physical port on the switch to which + // node’s port is connected to) fields. switch_info is an optional string + // field to be used to store any vendor-specific information. + LocalLinkConnection map[string]interface{} `json:"local_link_connection"` + + // Indicates whether PXE is enabled or disabled on the Port. + PXEEnabled bool `json:"pxe_enabled"` + + // The name of the physical network to which a port is connected. + // May be empty. + PhysicalNetwork string `json:"physical_network"` + + // Internal metadata set and stored by the Port. This field is read-only. + InternalInfo map[string]interface{} `json:"internal_info"` + + // A set of one or more arbitrary metadata key and value pairs. + Extra map[string]interface{} `json:"extra"` + + // The UTC date and time when the resource was created, ISO 8601 format. + CreatedAt time.Time `json:"created_at"` + + // The UTC date and time when the resource was updated, ISO 8601 format. + // May be “null”. + UpdatedAt time.Time `json:"updated_at"` + + // A list of relative links. Includes the self and bookmark links. + Links []interface{} `json:"links"` + + // Indicates whether the Port is a Smart NIC port. + IsSmartNIC bool `json:"is_smartnic"` +} + +// PortPage abstracts the raw results of making a List() request against +// the API. +type PortPage struct { + pagination.LinkedPageBase +} + +// IsEmpty returns true if a page contains no Port results. +func (r PortPage) IsEmpty() (bool, error) { + s, err := ExtractPorts(r) + return len(s) == 0, err +} + +// NextPageURL uses the response's embedded link reference to navigate to the +// next page of results. +func (r PortPage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"ports_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// ExtractPorts interprets the results of a single page from a List() call, +// producing a slice of Port entities. +func ExtractPorts(r pagination.Page) ([]Port, error) { + var s []Port + err := ExtractPortsInto(r, &s) + return s, err +} + +// GetResult is the response from a Get operation. Call its Extract +// method to interpret it as a Port. +type GetResult struct { + portResult +} + +// CreateResult is the response from a Create operation. +type CreateResult struct { + portResult +} + +// UpdateResult is the response from an Update operation. Call its Extract +// method to interpret it as a Port. +type UpdateResult struct { + portResult +} + +// DeleteResult is the response from a Delete operation. Call its ExtractErr +// method to determine if the call succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} diff --git a/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/ports/urls.go b/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/ports/urls.go new file mode 100644 index 00000000000..436c9541503 --- /dev/null +++ b/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetal/v1/ports/urls.go @@ -0,0 +1,31 @@ +package ports + +import "github.com/gophercloud/gophercloud" + +func createURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("ports") +} + +func listURL(client *gophercloud.ServiceClient) string { + return createURL(client) +} + +func listDetailURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("ports", "detail") +} + +func resourceURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("ports", id) +} + +func deleteURL(client *gophercloud.ServiceClient, id string) string { + return resourceURL(client, id) +} + +func getURL(client *gophercloud.ServiceClient, id string) string { + return resourceURL(client, id) +} + +func updateURL(client *gophercloud.ServiceClient, id string) string { + return resourceURL(client, id) +} diff --git a/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/noauth/doc.go b/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/noauth/doc.go new file mode 100644 index 00000000000..014180c28b4 --- /dev/null +++ b/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/noauth/doc.go @@ -0,0 +1,15 @@ +/* +Package noauth provides support for noauth bare metal introspection endpoints. + +Example of obtaining and using a client: + + client, err := noauth.NewBareMetalIntrospectionNoAuth(noauth.EndpointOpts{ + IronicInspectorEndpoint: "http://localhost:5050/v1/", + }) + if err != nil { + panic(err) + } + + introspection.GetIntrospectionStatus(client, "a62b8495-52e2-407b-b3cb-62775d04c2b8") +*/ +package noauth diff --git a/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/noauth/requests.go b/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/noauth/requests.go new file mode 100644 index 00000000000..a1c0857c96c --- /dev/null +++ b/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/noauth/requests.go @@ -0,0 +1,36 @@ +package noauth + +import ( + "fmt" + + "github.com/gophercloud/gophercloud" +) + +// EndpointOpts specifies a "noauth" Ironic Inspector Endpoint. +type EndpointOpts struct { + // IronicInspectorEndpoint [required] is currently only used with "noauth" Ironic introspection. + // An Ironic inspector endpoint with "auth_strategy=noauth" is necessary, for example: + // http://ironic.example.com:5050/v1. + IronicInspectorEndpoint string +} + +func initClientOpts(client *gophercloud.ProviderClient, eo EndpointOpts) (*gophercloud.ServiceClient, error) { + sc := new(gophercloud.ServiceClient) + if eo.IronicInspectorEndpoint == "" { + return nil, fmt.Errorf("IronicInspectorEndpoint is required") + } + + sc.Endpoint = gophercloud.NormalizeURL(eo.IronicInspectorEndpoint) + sc.ProviderClient = client + return sc, nil +} + +// NewBareMetalIntrospectionNoAuth creates a ServiceClient that may be used to access a +// "noauth" bare metal introspection service. +func NewBareMetalIntrospectionNoAuth(eo EndpointOpts) (*gophercloud.ServiceClient, error) { + sc, err := initClientOpts(&gophercloud.ProviderClient{}, eo) + + sc.Type = "baremetal-inspector" + + return sc, err +} diff --git a/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection/doc.go b/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection/doc.go new file mode 100644 index 00000000000..42382386945 --- /dev/null +++ b/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection/doc.go @@ -0,0 +1,59 @@ +/* +Package introspection contains the functionality for Starting introspection, +Get introspection status, List all introspection statuses, Abort an +introspection, Get stored introspection data and reapply introspection on +stored data. + +API reference https://developer.openstack.org/api-ref/baremetal-introspection/#node-introspection + +Example to Start Introspection + + err := introspection.StartIntrospection(client, NodeUUID, introspection.StartOpts{}).ExtractErr() + if err != nil { + panic(err) + } + +Example to Get an Introspection status + + _, err := introspection.GetIntrospectionStatus(client, NodeUUID).Extract() + if err != nil { + panic(err) + } + +Example to List all introspection statuses + + introspection.ListIntrospections(client.ServiceClient(), nil).EachPage(func(page pagination.Page) (bool, error) { + introspectionsList, err := introspection.ExtractIntrospections(page) + if err != nil { + return false, err + } + + for _, n := range introspectionsList { + // Do something + } + + return true, nil + }) + +Example to Abort an Introspection + + err := introspection.AbortIntrospection(client, NodeUUID).ExtractErr() + if err != nil { + panic(err) + } + +Example to Get stored Introspection Data + + v, err := introspection.GetIntrospectionData(c, NodeUUID).Extract() + if err != nil { + panic(err) + } + +Example to apply Introspection Data + + err := introspection.ApplyIntrospectionData(c, NodeUUID).ExtractErr() + if err != nil { + panic(err) + } +*/ +package introspection diff --git a/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection/requests.go b/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection/requests.go new file mode 100644 index 00000000000..a2d02ccde7d --- /dev/null +++ b/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection/requests.go @@ -0,0 +1,116 @@ +package introspection + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// ListIntrospectionsOptsBuilder allows extensions to add additional parameters to the +// ListIntrospections request. +type ListIntrospectionsOptsBuilder interface { + ToIntrospectionsListQuery() (string, error) +} + +// ListIntrospectionsOpts allows the filtering and sorting of paginated collections through +// the Introspection API. Filtering is achieved by passing in struct field values that map to +// the node attributes you want to see returned. Marker and Limit are used +// for pagination. +type ListIntrospectionsOpts struct { + // Requests a page size of items. + Limit int `q:"limit"` + + // The ID of the last-seen item. + Marker string `q:"marker"` +} + +// ToIntrospectionsListQuery formats a ListIntrospectionsOpts into a query string. +func (opts ListIntrospectionsOpts) ToIntrospectionsListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// ListIntrospections makes a request against the Inspector API to list the current introspections. +func ListIntrospections(client *gophercloud.ServiceClient, opts ListIntrospectionsOptsBuilder) pagination.Pager { + url := listIntrospectionsURL(client) + if opts != nil { + query, err := opts.ToIntrospectionsListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + var rpage = IntrospectionPage{pagination.LinkedPageBase{PageResult: r}} + return rpage + }) +} + +// GetIntrospectionStatus makes a request against the Inspector API to get the +// status of a single introspection. +func GetIntrospectionStatus(client *gophercloud.ServiceClient, nodeID string) (r GetIntrospectionStatusResult) { + _, r.Err = client.Get(introspectionURL(client, nodeID), &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// StartOptsBuilder allows extensions to add additional parameters to the +// Start request. +type StartOptsBuilder interface { + ToStartIntrospectionQuery() (string, error) +} + +// StartOpts represents options to start an introspection. +type StartOpts struct { + // Whether the current installation of ironic-inspector can manage PXE booting of nodes. + ManageBoot *bool `q:"manage_boot"` +} + +// ToStartIntrospectionQuery converts a StartOpts into a request. +func (opts StartOpts) ToStartIntrospectionQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// StartIntrospection initiate hardware introspection for node NodeID . +// All power management configuration for this node needs to be done prior to calling the endpoint. +func StartIntrospection(client *gophercloud.ServiceClient, nodeID string, opts StartOptsBuilder) (r StartResult) { + _, err := opts.ToStartIntrospectionQuery() + if err != nil { + r.Err = err + return + } + + _, r.Err = client.Post(introspectionURL(client, nodeID), nil, nil, &gophercloud.RequestOpts{ + OkCodes: []int{202}, + }) + + return +} + +// AbortIntrospection abort running introspection. +func AbortIntrospection(client *gophercloud.ServiceClient, nodeID string) (r AbortResult) { + _, r.Err = client.Post(abortIntrospectionURL(client, nodeID), nil, nil, &gophercloud.RequestOpts{ + OkCodes: []int{202}, + }) + + return +} + +// GetIntrospectionData return stored data from successful introspection. +func GetIntrospectionData(client *gophercloud.ServiceClient, nodeID string) (r DataResult) { + _, r.Err = client.Get(introspectionDataURL(client, nodeID), &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// ReApplyIntrospection triggers introspection on stored unprocessed data. +// No data is allowed to be sent along with the request. +func ReApplyIntrospection(client *gophercloud.ServiceClient, nodeID string) (r ApplyDataResult) { + _, r.Err = client.Post(introspectionUnprocessedDataURL(client, nodeID), nil, nil, &gophercloud.RequestOpts{ + OkCodes: []int{202}, + }) + + return +} diff --git a/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection/results.go b/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection/results.go new file mode 100644 index 00000000000..5cb35e999a0 --- /dev/null +++ b/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection/results.go @@ -0,0 +1,293 @@ +package introspection + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +type introspectionResult struct { + gophercloud.Result +} + +// Extract interprets any introspectionResult as an Introspection, if possible. +func (r introspectionResult) Extract() (*Introspection, error) { + var s Introspection + err := r.ExtractInto(&s) + return &s, err +} + +// ExtractInto will extract a response body into an Introspection struct. +func (r introspectionResult) ExtractInto(v interface{}) error { + return r.Result.ExtractIntoStructPtr(v, "") +} + +// ExtractIntrospectionsInto will extract a collection of introspectResult pages into a +// slice of Introspection entities. +func ExtractIntrospectionsInto(r pagination.Page, v interface{}) error { + return r.(IntrospectionPage).Result.ExtractIntoSlicePtr(v, "introspection") +} + +// ExtractIntrospections interprets the results of a single page from a +// ListIntrospections() call, producing a slice of Introspection entities. +func ExtractIntrospections(r pagination.Page) ([]Introspection, error) { + var s []Introspection + err := ExtractIntrospectionsInto(r, &s) + return s, err +} + +// IntrospectionPage abstracts the raw results of making a ListIntrospections() +// request against the Inspector 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 ExtractIntrospections call. +type IntrospectionPage struct { + pagination.LinkedPageBase +} + +// Introspection represents an introspection in the OpenStack Bare Metal Introspector API. +type Introspection struct { + // Whether introspection is finished + Finished bool `json:"finished"` + + // State of the introspection + State string `json:"state"` + + // Error message, can be null; "Canceled by operator" in case introspection was aborted + Error string `json:"error"` + + // UUID of the introspection + UUID string `json:"uuid"` + + // UTC ISO8601 timestamp + StartedAt time.Time `json:"-"` + + // UTC ISO8601 timestamp or null + FinishedAt time.Time `json:"-"` + + // Link to the introspection URL + Links []interface{} `json:"links"` +} + +// IsEmpty returns true if a page contains no Introspection results. +func (r IntrospectionPage) IsEmpty() (bool, error) { + s, err := ExtractIntrospections(r) + return len(s) == 0, err +} + +// NextPageURL uses the response's embedded link reference to navigate to the +// next page of results. +func (r IntrospectionPage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"introspection_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// UnmarshalJSON trie to convert values for started_at and finished_at from the +// json response into RFC3339 standard. Since Introspection API can remove the +// Z from the format, if the conversion fails, it falls back to an RFC3339 +// with no Z format supported by gophercloud. +func (r *Introspection) UnmarshalJSON(b []byte) error { + type tmp Introspection + var s struct { + tmp + StartedAt string `json:"started_at"` + FinishedAt string `json:"finished_at"` + } + + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + + *r = Introspection(s.tmp) + + if s.StartedAt != "" { + t, err := time.Parse(time.RFC3339, s.StartedAt) + if err != nil { + t, err = time.Parse(gophercloud.RFC3339NoZ, s.StartedAt) + if err != nil { + return err + } + } + r.StartedAt = t + } + + if s.FinishedAt != "" { + t, err := time.Parse(time.RFC3339, s.FinishedAt) + if err != nil { + t, err = time.Parse(gophercloud.RFC3339NoZ, s.FinishedAt) + if err != nil { + return err + } + } + r.FinishedAt = t + } + + return nil +} + +// GetIntrospectionStatusResult is the response from a GetIntrospectionStatus operation. +// Call its Extract method to interpret it as an Introspection. +type GetIntrospectionStatusResult struct { + introspectionResult +} + +// StartResult is the response from a StartIntrospection operation. +// Call its ExtractErr method to determine if the call succeeded or failed. +type StartResult struct { + gophercloud.ErrResult +} + +// AbortResult is the response from an AbortIntrospection operation. +// Call its ExtractErr method to determine if the call succeeded or failed. +type AbortResult struct { + gophercloud.ErrResult +} + +// Data represents the full introspection data collected. +// The format and contents of the stored data depends on the ramdisk used +// and plugins enabled both in the ramdisk and in inspector itself. +// This structure has been provided for basic compatibility but it +// will need extensions +type Data struct { + AllInterfaces map[string]BaseInterfaceType `json:"all_interfaces"` + BootInterface string `json:"boot_interface"` + CPUArch string `json:"cpu_arch"` + CPUs int `json:"cpus"` + Error string `json:"error"` + Interfaces map[string]BaseInterfaceType `json:"interfaces"` + Inventory InventoryType `json:"inventory"` + IPMIAddress string `json:"ipmi_address"` + LocalGB int `json:"local_gb"` + MACs []string `json:"macs"` + MemoryMB int `json:"memory_mb"` + RootDisk RootDiskType `json:"root_disk"` +} + +// Sub Types defined under Data and deeper in the structure + +type BaseInterfaceType struct { + ClientID string `json:"client_id"` + IP string `json:"ip"` + MAC string `json:"mac"` + PXE bool `json:"pxe"` + LLDPProcessed map[string]interface{} `json:"lldp_processed"` +} + +type BootInfoType struct { + CurrentBootMode string `json:"current_boot_mode"` + PXEInterface string `json:"pxe_interface"` +} + +type CPUType struct { + Architecture string `json:"architecture"` + Count int `json:"count"` + Flags []string `json:"flags"` + Frequency string `json:"frequency"` + ModelName string `json:"model_name"` +} + +type LLDPTLVType struct { + Type int + Value string +} + +type InterfaceType struct { + BIOSDevName string `json:"biosdevname"` + ClientID string `json:"client_id"` + HasCarrier bool `json:"has_carrier"` + IPV4Address string `json:"ipv4_address"` + IPV6Address string `json:"ipv6_address"` + LLDP []LLDPTLVType `json:"lldp"` + MACAddress string `json:"mac_address"` + Name string `json:"name"` + Product string `json:"product"` + Vendor string `json:"vendor"` +} + +type InventoryType struct { + BmcAddress string `json:"bmc_address"` + Boot BootInfoType `json:"boot"` + CPU CPUType `json:"cpu"` + Disks []RootDiskType `json:"disks"` + Interfaces []InterfaceType `json:"interfaces"` + Memory MemoryType `json:"memory"` + SystemVendor SystemVendorType `json:"system_vendor"` +} + +type MemoryType struct { + PhysicalMb int `json:"physical_mb"` + Total int `json:"total"` +} + +type RootDiskType struct { + Hctl string `json:"hctl"` + Model string `json:"model"` + Name string `json:"name"` + Rotational bool `json:"rotational"` + Serial string `json:"serial"` + Size int `json:"size"` + Vendor string `json:"vendor"` + Wwn string `json:"wwn"` + WwnVendorExtension string `json:"wwn_vendor_extension"` + WwnWithExtension string `json:"wwn_with_extension"` +} + +type SystemVendorType struct { + Manufacturer string `json:"manufacturer"` + ProductName string `json:"product_name"` + SerialNumber string `json:"serial_number"` +} + +// UnmarshalJSON interprets an LLDP TLV [key, value] pair as an LLDPTLVType structure +func (r *LLDPTLVType) UnmarshalJSON(data []byte) error { + var list []interface{} + if err := json.Unmarshal(data, &list); err != nil { + return err + } + + if len(list) != 2 { + return fmt.Errorf("Invalid LLDP TLV key-value pair") + } + + fieldtype, ok := list[0].(float64) + if !ok { + return fmt.Errorf("LLDP TLV key is not number") + } + + value, ok := list[1].(string) + if !ok { + return fmt.Errorf("LLDP TLV value is not string") + } + + r.Type = int(fieldtype) + r.Value = value + return nil +} + +// Extract interprets any IntrospectionDataResult as IntrospectionData, if possible. +func (r DataResult) Extract() (*Data, error) { + var s Data + err := r.ExtractInto(&s) + return &s, err +} + +// DataResult represents the response from a GetIntrospectionData operation. +// Call its Extract method to interpret it as a Data. +type DataResult struct { + gophercloud.Result +} + +// ApplyDataResult is the response from an ApplyData operation. +// Call its ExtractErr method to determine if the call succeeded or failed. +type ApplyDataResult struct { + gophercloud.ErrResult +} diff --git a/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection/urls.go b/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection/urls.go new file mode 100644 index 00000000000..e480613749a --- /dev/null +++ b/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection/urls.go @@ -0,0 +1,23 @@ +package introspection + +import "github.com/gophercloud/gophercloud" + +func listIntrospectionsURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("introspection") +} + +func introspectionURL(client *gophercloud.ServiceClient, nodeID string) string { + return client.ServiceURL("introspection", nodeID) +} + +func abortIntrospectionURL(client *gophercloud.ServiceClient, nodeID string) string { + return client.ServiceURL("introspection", nodeID, "abort") +} + +func introspectionDataURL(client *gophercloud.ServiceClient, nodeID string) string { + return client.ServiceURL("introspection", nodeID, "data") +} + +func introspectionUnprocessedDataURL(client *gophercloud.ServiceClient, nodeID string) string { + return client.ServiceURL("introspection", nodeID, "data", "unprocessed") +} diff --git a/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/utils/openstack/baremetal/v1/nodes/configdrive.go b/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/utils/openstack/baremetal/v1/nodes/configdrive.go new file mode 100644 index 00000000000..aeaf53a3f52 --- /dev/null +++ b/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/utils/openstack/baremetal/v1/nodes/configdrive.go @@ -0,0 +1,121 @@ +package nodes + +import ( + "encoding/base64" + "encoding/json" + "io/ioutil" + "os" + "path/filepath" +) + +// A ConfigDrive struct will be used to create a base64-encoded, gzipped ISO9660 image for use with Ironic. +type ConfigDrive struct { + UserData UserDataBuilder `json:"user_data"` + MetaData map[string]interface{} `json:"meta_data"` + NetworkData map[string]interface{} `json:"network_data"` + Version string `json:"-"` + BuildDirectory string `json:"-"` +} + +// Interface to let us specify a raw string, or a map for the user data +type UserDataBuilder interface { + ToUserData() ([]byte, error) +} + +type UserDataMap map[string]interface{} +type UserDataString string + +// Converts a UserDataMap to JSON-string +func (data UserDataMap) ToUserData() ([]byte, error) { + return json.MarshalIndent(data, "", "\t") +} + +func (data UserDataString) ToUserData() ([]byte, error) { + return []byte(data), nil +} + +type ConfigDriveBuilder interface { + ToConfigDrive() (string, error) +} + +// Writes out a ConfigDrive to a temporary directory, and returns the path +func (configDrive ConfigDrive) ToDirectory() (string, error) { + // Create a temporary directory for our config drive + directory, err := ioutil.TempDir(configDrive.BuildDirectory, "gophercloud") + if err != nil { + return "", err + } + + // Build up the paths for OpenStack + var version string + if configDrive.Version == "" { + version = "latest" + } else { + version = configDrive.Version + } + + path := filepath.FromSlash(directory + "/openstack/" + version) + if err := os.MkdirAll(path, 0755); err != nil { + return "", err + } + + // Dump out user data + if configDrive.UserData != nil { + userDataPath := filepath.FromSlash(path + "/user_data") + data, err := configDrive.UserData.ToUserData() + if err != nil { + return "", err + } + + if err := ioutil.WriteFile(userDataPath, data, 0644); err != nil { + return "", err + } + } + + // Dump out meta data + if configDrive.MetaData != nil { + metaDataPath := filepath.FromSlash(path + "/meta_data.json") + data, err := json.Marshal(configDrive.MetaData) + if err != nil { + return "", err + } + + if err := ioutil.WriteFile(metaDataPath, data, 0644); err != nil { + return "", err + } + } + + // Dump out network data + if configDrive.NetworkData != nil { + networkDataPath := filepath.FromSlash(path + "/network_data.json") + data, err := json.Marshal(configDrive.NetworkData) + if err != nil { + return "", err + } + + if err := ioutil.WriteFile(networkDataPath, data, 0644); err != nil { + return "", err + } + } + + return directory, nil +} + +// Writes out the ConfigDrive struct to a directory structure, and then +// packs it as a base64-encoded gzipped ISO9660 image. +func (configDrive ConfigDrive) ToConfigDrive() (string, error) { + directory, err := configDrive.ToDirectory() + if err != nil { + return "", err + } + defer os.RemoveAll(directory) + + // Pack result as gzipped ISO9660 file + result, err := PackDirectoryAsISO(directory) + if err != nil { + return "", err + } + + // Return as base64-encoded data + return base64.StdEncoding.EncodeToString(result), nil +} diff --git a/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/utils/openstack/baremetal/v1/nodes/doc.go b/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/utils/openstack/baremetal/v1/nodes/doc.go new file mode 100644 index 00000000000..1f8b31f0e05 --- /dev/null +++ b/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/utils/openstack/baremetal/v1/nodes/doc.go @@ -0,0 +1,34 @@ +package nodes + +/* +Package nodes provides utilities for working with Ironic's baremetal API. + +* Building a config drive + +As part of provisioning a node, you may need a config drive that contains user data, metadata, and network data +stored inside a base64-encoded gzipped ISO9660 file. These utilities can create that for you. + +For example: + + configDrive = nodes.ConfigDrive{ + UserData: nodes.UserDataMap{ + "ignition": map[string]string{ + "version": "2.2.0", + }, + "systemd": map[string]interface{}{ + "units": []map[string]interface{}{{ + "name": "example.service", + "enabled": true, + }, + }, + }, + } + +Then to upload this to Ironic as a using gophercloud: + + err = nodes.ChangeProvisionState(client, uuid, nodes.ProvisionStateOpts{ + Target: "active", + ConfigDrive: configDrive.ToConfigDrive(), + }).ExtractErr() + +*/ diff --git a/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/utils/openstack/baremetal/v1/nodes/util.go b/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/utils/openstack/baremetal/v1/nodes/util.go new file mode 100644 index 00000000000..d5c63acbf98 --- /dev/null +++ b/pkg/terraform/exec/plugins/vendor/github.com/gophercloud/utils/openstack/baremetal/v1/nodes/util.go @@ -0,0 +1,59 @@ +package nodes + +import ( + "bytes" + "compress/gzip" + "fmt" + "io/ioutil" + "os" + "os/exec" +) + +// Gzips a file +func GzipFile(path string) ([]byte, error) { + var buf bytes.Buffer + + w := gzip.NewWriter(&buf) + contents, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + _, err = w.Write(contents) + if err != nil { + return nil, err + } + + err = w.Close() + if err != nil { + return nil, err + } + + result := buf.Bytes() + return result, nil +} + +// Packs a directory into a gzipped ISO image +func PackDirectoryAsISO(path string) ([]byte, error) { + iso, err := ioutil.TempFile("", "gophercloud-iso") + if err != nil { + return nil, err + } + iso.Close() + defer os.Remove(iso.Name()) + cmd := exec.Command( + "mkisofs", + "-o", iso.Name(), + "-ldots", + "-allow-lowercase", + "-allow-multidot", "-l", + "-publisher", "gophercloud", + "-quiet", "-J", + "-r", "-V", "config-2", + path, + ) + if err = cmd.Run(); err != nil { + return nil, fmt.Errorf("error creating configdrive iso: %s", err.Error()) + } + + return GzipFile(iso.Name()) +} diff --git a/pkg/terraform/exec/plugins/vendor/github.com/openshift-metalkube/terraform-provider-ironic/LICENSE b/pkg/terraform/exec/plugins/vendor/github.com/openshift-metalkube/terraform-provider-ironic/LICENSE new file mode 100644 index 00000000000..261eeb9e9f8 --- /dev/null +++ b/pkg/terraform/exec/plugins/vendor/github.com/openshift-metalkube/terraform-provider-ironic/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/pkg/terraform/exec/plugins/vendor/github.com/openshift-metalkube/terraform-provider-ironic/ironic/data_source_ironic_introspection.go b/pkg/terraform/exec/plugins/vendor/github.com/openshift-metalkube/terraform-provider-ironic/ironic/data_source_ironic_introspection.go new file mode 100644 index 00000000000..16fa5bdfa6f --- /dev/null +++ b/pkg/terraform/exec/plugins/vendor/github.com/openshift-metalkube/terraform-provider-ironic/ironic/data_source_ironic_introspection.go @@ -0,0 +1,115 @@ +package ironic + +import ( + "fmt" + "github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection" + "github.com/hashicorp/terraform/helper/schema" + "time" +) + +// Schema resource for an introspection data source, that has some selected details about the node exposed. +func dataSourceIronicIntrospection() *schema.Resource { + return &schema.Resource{ + Read: dataSourceIronicIntrospectionRead, + Schema: map[string]*schema.Schema{ + "uuid": { + Type: schema.TypeString, + Required: true, + }, + "finished": { + Type: schema.TypeBool, + Computed: true, + }, + "error": { + Type: schema.TypeString, + Computed: true, + }, + "started_at": { + Type: schema.TypeString, + Computed: true, + }, + "finished_at": { + Type: schema.TypeString, + Computed: true, + }, + "state": { + Type: schema.TypeString, + Computed: true, + }, + "interfaces": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeMap, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + Description: "A list of interfaces that were discovered", + }, + "cpu_arch": { + Type: schema.TypeString, + Computed: true, + Description: "CPU architecture (e.g., x86_64)", + }, + "cpu_count": { + Type: schema.TypeInt, + Computed: true, + Description: "The number of CPU's", + }, + "memory_mb": { + Type: schema.TypeInt, + Computed: true, + Description: "Memory in megabytes", + }, + }, + } +} + +func dataSourceIronicIntrospectionRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(Clients).Inspector + if client == nil { + return fmt.Errorf("inspector endpoint was not defined") + } + + uuid := d.Get("uuid").(string) + + status, err := introspection.GetIntrospectionStatus(client, uuid).Extract() + if err != nil { + return fmt.Errorf("could not get introspection status: %s", err.Error()) + } + + d.Set("finished", status.Finished) + d.Set("finished_at", status.FinishedAt) + d.Set("started_at", status.StartedAt) + d.Set("error", status.Error) + d.Set("state", status.State) + + if status.Finished { + data, err := introspection.GetIntrospectionData(client, uuid).Extract() + if err != nil { + return fmt.Errorf("could not get introspection data: %s", err.Error()) + } + + // Network interface data + var interfaces []map[string]string + for k, v := range data.AllInterfaces { + interfaces = append(interfaces, map[string]string{ + "name": k, + "mac": v.MAC, + "ip": v.IP, + }) + } + d.Set("interfaces", interfaces) + + // CPU data + d.Set("cpu_arch", data.CPUArch) + d.Set("cpu_count", data.CPUs) + + // Memory info + d.Set("memory_mb", data.MemoryMB) + } + + d.SetId(time.Now().UTC().String()) + return nil +} diff --git a/pkg/terraform/exec/plugins/vendor/github.com/openshift-metalkube/terraform-provider-ironic/ironic/provider.go b/pkg/terraform/exec/plugins/vendor/github.com/openshift-metalkube/terraform-provider-ironic/ironic/provider.go new file mode 100644 index 00000000000..3049aa26cf7 --- /dev/null +++ b/pkg/terraform/exec/plugins/vendor/github.com/openshift-metalkube/terraform-provider-ironic/ironic/provider.go @@ -0,0 +1,97 @@ +package ironic + +import ( + "fmt" + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/baremetal/noauth" + noauthintrospection "github.com/gophercloud/gophercloud/openstack/baremetalintrospection/noauth" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/terraform" + "log" +) + +// Clients stores the client connection information for Ironic and Inspector +type Clients struct { + Ironic *gophercloud.ServiceClient + Inspector *gophercloud.ServiceClient +} + +// Provider Ironic +func Provider() terraform.ResourceProvider { + return &schema.Provider{ + Schema: map[string]*schema.Schema{ + "url": { + Type: schema.TypeString, + Required: true, + DefaultFunc: schema.EnvDefaultFunc("IRONIC_ENDPOINT", ""), + Description: descriptions["url"], + }, + "inspector": { + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("IRONIC_INSPECTOR_ENDPOINT", ""), + Description: descriptions["inspector"], + }, + "microversion": { + Type: schema.TypeString, + Required: true, + DefaultFunc: schema.EnvDefaultFunc("IRONIC_MICROVERSION", "1.52"), + Description: descriptions["microversion"], + }, + }, + ResourcesMap: map[string]*schema.Resource{ + "ironic_node_v1": resourceNodeV1(), + "ironic_port_v1": resourcePortV1(), + "ironic_allocation_v1": resourceAllocationV1(), + "ironic_deployment": resourceDeployment(), + }, + DataSourcesMap: map[string]*schema.Resource{ + "ironic_introspection": dataSourceIronicIntrospection(), + }, + ConfigureFunc: configureProvider, + } +} + +var descriptions map[string]string + +func init() { + descriptions = map[string]string{ + "url": "The authentication endpoint for Ironic", + "inspector": "The endpoint for Ironic inspector", + "microversion": "The microversion to use for Ironic", + } +} + +// Creates a noauth Ironic client +func configureProvider(schema *schema.ResourceData) (interface{}, error) { + var clients Clients + + url := schema.Get("url").(string) + if url == "" { + return nil, fmt.Errorf("url is required for ironic provider") + } + log.Printf("[DEBUG] Ironic endpoint is %s", url) + + ironic, err := noauth.NewBareMetalNoAuth(noauth.EndpointOpts{ + IronicEndpoint: url, + }) + if err != nil { + return nil, err + } + ironic.Microversion = schema.Get("microversion").(string) + clients.Ironic = ironic + + inspectorURL := schema.Get("inspector").(string) + if inspectorURL != "" { + log.Printf("[DEBUG] Inspector endpoint is %s", inspectorURL) + inspector, err := noauthintrospection.NewBareMetalIntrospectionNoAuth(noauthintrospection.EndpointOpts{ + IronicInspectorEndpoint: inspectorURL, + }) + if err != nil { + return nil, fmt.Errorf("could not configure inspector endpoint: %s", err.Error()) + } + clients.Inspector = inspector + } + + return clients, err +} diff --git a/pkg/terraform/exec/plugins/vendor/github.com/openshift-metalkube/terraform-provider-ironic/ironic/resource_ironic_allocation_v1.go b/pkg/terraform/exec/plugins/vendor/github.com/openshift-metalkube/terraform-provider-ironic/ironic/resource_ironic_allocation_v1.go new file mode 100644 index 00000000000..b4c7199cff3 --- /dev/null +++ b/pkg/terraform/exec/plugins/vendor/github.com/openshift-metalkube/terraform-provider-ironic/ironic/resource_ironic_allocation_v1.go @@ -0,0 +1,171 @@ +package ironic + +import ( + "fmt" + "log" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/baremetal/v1/allocations" + "github.com/hashicorp/terraform/helper/schema" +) + +// Schema resource definition for an Ironic allocation. +func resourceAllocationV1() *schema.Resource { + return &schema.Resource{ + Create: resourceAllocationV1Create, + Read: resourceAllocationV1Read, + Delete: resourceAllocationV1Delete, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "resource_class": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "candidate_nodes": { + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Optional: true, + ForceNew: true, + Computed: true, + }, + "traits": { + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Optional: true, + ForceNew: true, + }, + "extra": { + Type: schema.TypeMap, + Optional: true, + ForceNew: true, + }, + "node_uuid": { + Type: schema.TypeString, + Computed: true, + }, + "state": { + Type: schema.TypeString, + Computed: true, + }, + "last_error": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +// Create an allocation, including driving Ironic's state machine +func resourceAllocationV1Create(d *schema.ResourceData, meta interface{}) error { + client := meta.(Clients).Ironic + + result, err := allocations.Create(client, allocationSchemaToCreateOpts(d)).Extract() + if err != nil { + return err + } + + d.SetId(result.UUID) + + // Wait for state to change from allocating + var state string + timeout := 1 * time.Minute + checkInterval := 2 * time.Second + + getState := func() string { + resourceAllocationV1Read(d, meta) + return d.Get("state").(string) + } + + for state = getState(); state == "allocating"; state = getState() { + log.Printf("[DEBUG] Requested allocation %s; current state is '%s'\n", d.Id(), state) + + time.Sleep(checkInterval) + checkInterval += 2 + timeout -= checkInterval + if timeout < 0 { + return fmt.Errorf("timed out waiting for allocation") + } + } + + if state == "error" { + err := d.Get("last_error").(string) + resourceAllocationV1Delete(d, meta) + d.SetId("") + return fmt.Errorf("error creating resource: %s", err) + } + + return nil +} + +// Read the allocation's data from Ironic +func resourceAllocationV1Read(d *schema.ResourceData, meta interface{}) error { + client := meta.(Clients).Ironic + + result, err := allocations.Get(client, d.Id()).Extract() + if err != nil { + return err + } + + d.Set("name", result.Name) + d.Set("resource_class", result.ResourceClass) + d.Set("candidate_nodes", result.CandidateNodes) + d.Set("traits", result.Traits) + d.Set("extra", result.Extra) + d.Set("node_uuid", result.NodeUUID) + d.Set("state", result.State) + d.Set("last_error", result.LastError) + + return nil +} + +// Delete an allocation from Ironic if it exists +func resourceAllocationV1Delete(d *schema.ResourceData, meta interface{}) error { + client := meta.(Clients).Ironic + + _, err := allocations.Get(client, d.Id()).Extract() + if _, ok := err.(gophercloud.ErrDefault404); ok { + return nil + } + + return allocations.Delete(client, d.Id()).ExtractErr() +} + +func allocationSchemaToCreateOpts(d *schema.ResourceData) *allocations.CreateOpts { + candidateNodesRaw := d.Get("candidate_nodes").([]interface{}) + traitsRaw := d.Get("traits").([]interface{}) + extraRaw := d.Get("extra").(map[string]interface{}) + + candidateNodes := make([]string, len(candidateNodesRaw)) + for i := range candidateNodesRaw { + candidateNodes[i] = candidateNodesRaw[i].(string) + } + + traits := make([]string, len(traitsRaw)) + for i := range traitsRaw { + traits[i] = traitsRaw[i].(string) + } + + extra := make(map[string]string) + for k, v := range extraRaw { + extra[k] = v.(string) + } + + return &allocations.CreateOpts{ + Name: d.Get("name").(string), + ResourceClass: d.Get("resource_class").(string), + CandidateNodes: candidateNodes, + Traits: traits, + Extra: extra, + } +} diff --git a/pkg/terraform/exec/plugins/vendor/github.com/openshift-metalkube/terraform-provider-ironic/ironic/resource_ironic_deployment.go b/pkg/terraform/exec/plugins/vendor/github.com/openshift-metalkube/terraform-provider-ironic/ironic/resource_ironic_deployment.go new file mode 100644 index 00000000000..e5f4f87b0b7 --- /dev/null +++ b/pkg/terraform/exec/plugins/vendor/github.com/openshift-metalkube/terraform-provider-ironic/ironic/resource_ironic_deployment.go @@ -0,0 +1,116 @@ +package ironic + +import ( + "fmt" + "github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes" + utils "github.com/gophercloud/utils/openstack/baremetal/v1/nodes" + "github.com/hashicorp/terraform/helper/schema" +) + +// Schema resource definition for an Ironic deployment. +func resourceDeployment() *schema.Resource { + return &schema.Resource{ + Create: resourceDeploymentCreate, + Read: resourceDeploymentRead, + Delete: resourceDeploymentDelete, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "node_uuid": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "instance_info": { + Type: schema.TypeMap, + Required: true, + ForceNew: true, + }, + "user_data": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "network_data": { + Type: schema.TypeMap, + Optional: true, + ForceNew: true, + }, + "metadata": { + Type: schema.TypeMap, + Optional: true, + ForceNew: true, + }, + "provision_state": { + Type: schema.TypeString, + Computed: true, + }, + "last_error": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +// Create an deployment, including driving Ironic's state machine +func resourceDeploymentCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(Clients).Ironic + + // Reload the resource before returning + defer resourceDeploymentRead(d, meta) + + // Set instance info + instanceInfo := d.Get("instance_info").(map[string]interface{}) + if instanceInfo != nil { + _, err := nodes.Update(client, d.Get("node_uuid").(string), nodes.UpdateOpts{ + nodes.UpdateOperation{ + Op: nodes.AddOp, + Path: "/instance_info", + Value: instanceInfo, + }, + }).Extract() + if err != nil { + return fmt.Errorf("could not update instance info: %s", err) + } + } + + // Create config drive + configDrive := utils.ConfigDrive{ + UserData: utils.UserDataString(d.Get("user_data").(string)), + NetworkData: d.Get("network_data").(map[string]interface{}), + MetaData: d.Get("metadata").(map[string]interface{}), + } + + d.SetId(d.Get("node_uuid").(string)) + + // Deploy the node - drive Ironic state machine until node is 'active' + return ChangeProvisionStateToTarget(client, d.Id(), "active", &configDrive) +} + +// Read the deployment's data from Ironic +func resourceDeploymentRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(Clients).Ironic + + // Ensure node exists first + id := d.Get("node_uuid").(string) + result, err := nodes.Get(client, id).Extract() + if err != nil { + return fmt.Errorf("could not find node %s: %s", id, err) + } + + d.Set("provision_state", result.ProvisionState) + d.Set("last_error", result.LastError) + + return nil +} + +// Delete an deployment from Ironic - this cleans the node and returns it's state to 'available' +func resourceDeploymentDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(Clients).Ironic + return ChangeProvisionStateToTarget(client, d.Id(), "deleted", nil) +} diff --git a/pkg/terraform/exec/plugins/vendor/github.com/openshift-metalkube/terraform-provider-ironic/ironic/resource_ironic_node_v1.go b/pkg/terraform/exec/plugins/vendor/github.com/openshift-metalkube/terraform-provider-ironic/ironic/resource_ironic_node_v1.go new file mode 100644 index 00000000000..1c94f4e8572 --- /dev/null +++ b/pkg/terraform/exec/plugins/vendor/github.com/openshift-metalkube/terraform-provider-ironic/ironic/resource_ironic_node_v1.go @@ -0,0 +1,485 @@ +package ironic + +import ( + "fmt" + "log" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes" + "github.com/gophercloud/gophercloud/openstack/baremetal/v1/ports" + "github.com/hashicorp/terraform/helper/schema" +) + +// Schema resource definition for an Ironic node. +func resourceNodeV1() *schema.Resource { + return &schema.Resource{ + Create: resourceNodeV1Create, + Read: resourceNodeV1Read, + Update: resourceNodeV1Update, + Delete: resourceNodeV1Delete, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Optional: true, + }, + "boot_interface": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "clean": { + Type: schema.TypeBool, + Optional: true, + }, + "conductor_group": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "console_interface": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "deploy_interface": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "driver": { + Type: schema.TypeString, + Required: true, + }, + "driver_info": { + Type: schema.TypeMap, + Optional: true, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + /* FIXME: Password updates aren't considered. How can I know if the *local* data changed? */ + /* FIXME: Support drivers other than IPMI */ + if k == "driver_info.ipmi_password" && old == "******" { + return true + } + + return false + }, + + // driver_info could contain passwords + Sensitive: true, + }, + "properties": { + Type: schema.TypeMap, + Optional: true, + }, + "root_device": { + Type: schema.TypeMap, + Optional: true, + }, + "extra": { + Type: schema.TypeMap, + Optional: true, + }, + "inspect_interface": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "instance_uuid": { + Type: schema.TypeString, + Computed: true, + }, + "inspect": { + Type: schema.TypeBool, + Optional: true, + }, + "available": { + Type: schema.TypeBool, + Optional: true, + }, + "manage": { + Type: schema.TypeBool, + Optional: true, + }, + "management_interface": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "network_interface": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "power_interface": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "raid_interface": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "rescue_interface": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "resource_class": { + Type: schema.TypeString, + Optional: true, + }, + "storage_interface": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "vendor_interface": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "owner": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "ports": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeMap, + }, + }, + "provision_state": { + Type: schema.TypeString, + Computed: true, + }, + "power_state": { + Type: schema.TypeString, + Computed: true, + }, + "target_power_state": { + Type: schema.TypeString, + Optional: true, + + // If power_state is same as target_power_state, we have no changes to apply + DiffSuppressFunc: func(_, old, new string, d *schema.ResourceData) bool { + return new == d.Get("power_state").(string) + }, + }, + "power_state_timeout": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + }, + } +} + +// Create a node, including driving Ironic's state machine +func resourceNodeV1Create(d *schema.ResourceData, meta interface{}) error { + client := meta.(Clients).Ironic + + // Create the node object in Ironic + createOpts := schemaToCreateOpts(d) + result, err := nodes.Create(client, createOpts).Extract() + if err != nil { + d.SetId("") + return err + } + + // Setting the ID is what tells terraform we were successful in creating the node + log.Printf("[DEBUG] Node created with ID %s\n", d.Id()) + d.SetId(result.UUID) + + // Create ports as part of the node object - you may also use the native port resource + portSet := d.Get("ports").(*schema.Set) + if portSet != nil { + portList := portSet.List() + for _, portInterface := range portList { + port := portInterface.(map[string]interface{}) + + // Terraform map can't handle bool... seriously. + var pxeEnabled bool + if port["pxe_enabled"] != nil { + if port["pxe_enabled"] == "true" { + pxeEnabled = true + } else { + pxeEnabled = false + } + + } + // FIXME: All values other than address and pxe + portCreateOpts := ports.CreateOpts{ + NodeUUID: d.Id(), + Address: port["address"].(string), + PXEEnabled: &pxeEnabled, + } + _, err := ports.Create(client, portCreateOpts).Extract() + if err != nil { + resourcePortV1Read(d, meta) + return err + } + } + } + + // Make node manageable + if d.Get("manage").(bool) || d.Get("clean").(bool) || d.Get("inspect").(bool) { + if err := ChangeProvisionStateToTarget(client, d.Id(), "manage", nil); err != nil { + return fmt.Errorf("could not manage: %s", err) + } + } + + // Clean node + if d.Get("clean").(bool) { + if err := ChangeProvisionStateToTarget(client, d.Id(), "clean", nil); err != nil { + return fmt.Errorf("could not clean: %s", err) + } + } + + // Inspect node + if d.Get("inspect").(bool) { + if err := ChangeProvisionStateToTarget(client, d.Id(), "inspect", nil); err != nil { + return fmt.Errorf("could not inspect: %s", err) + } + } + + // Make node available + if d.Get("available").(bool) { + if err := ChangeProvisionStateToTarget(client, d.Id(), "provide", nil); err != nil { + return fmt.Errorf("could not make node available: %s", err) + } + } + + // Change power state, if required + if targetPowerState := d.Get("target_power_state").(string); targetPowerState != "" { + err := changePowerState(client, d, nodes.TargetPowerState(targetPowerState)) + if err != nil { + return fmt.Errorf("could not change power state: %s", err) + } + } + + return resourceNodeV1Read(d, meta) +} + +// Read the node's data from Ironic +func resourceNodeV1Read(d *schema.ResourceData, meta interface{}) error { + client := meta.(Clients).Ironic + + node, err := nodes.Get(client, d.Id()).Extract() + if err != nil { + d.SetId("") + return err + } + + // TODO: Ironic's Create is different than the Node object itself, GET returns things like the + // RaidConfig, we need to add those and handle them in CREATE + d.Set("boot_interface", node.BootInterface) + d.Set("conductor_group", node.ConductorGroup) + d.Set("console_interface", node.ConsoleInterface) + d.Set("deploy_interface", node.DeployInterface) + d.Set("driver", node.Driver) + d.Set("driver_info", node.DriverInfo) + d.Set("extra", node.Extra) + d.Set("inspect_interface", node.InspectInterface) + d.Set("instance_uuid", node.InstanceUUID) + d.Set("management_interface", node.ManagementInterface) + d.Set("name", node.Name) + d.Set("network_interface", node.NetworkInterface) + d.Set("owner", node.Owner) + d.Set("power_interface", node.PowerInterface) + d.Set("power_state", node.PowerState) + d.Set("root_device", node.Properties["root_device"]) + delete(node.Properties, "root_device") + d.Set("properties", node.Properties) + d.Set("raid_interface", node.RAIDInterface) + d.Set("rescue_interface", node.RescueInterface) + d.Set("resource_class", node.ResourceClass) + d.Set("storage_interface", node.StorageInterface) + d.Set("vendor_interface", node.VendorInterface) + d.Set("provision_state", node.ProvisionState) + + return nil +} + +// Update a node's state based on the terraform config - TODO: handle everything +func resourceNodeV1Update(d *schema.ResourceData, meta interface{}) error { + client := meta.(Clients).Ironic + + d.Partial(true) + + stringFields := []string{ + "boot_interface", + "conductor_group", + "console_interface", + "deploy_interface", + "driver", + "inspect_interface", + "management_interface", + "name", + "network_interface", + "owner", + "power_interface", + "raid_interface", + "rescue_interface", + "resource_class", + "storage_interface", + "vendor_interface", + } + + for _, field := range stringFields { + if d.HasChange(field) { + opts := nodes.UpdateOpts{ + nodes.UpdateOperation{ + Op: nodes.ReplaceOp, + Path: fmt.Sprintf("/%s", field), + Value: d.Get(field).(string), + }, + } + + if _, err := nodes.Update(client, d.Id(), opts).Extract(); err != nil { + return err + } + } + } + + // Make node manageable + if (d.HasChange("manage") && d.Get("manage").(bool)) || + (d.HasChange("clean") && d.Get("clean").(bool)) || + (d.HasChange("inspect") && d.Get("inspect").(bool)) { + if err := ChangeProvisionStateToTarget(client, d.Id(), "manage", nil); err != nil { + return fmt.Errorf("could not manage: %s", err) + } + } + + // Update power state if required + if targetPowerState := d.Get("target_power_state").(string); d.HasChange("target_power_state") && targetPowerState != "" { + if err := changePowerState(client, d, nodes.TargetPowerState(targetPowerState)); err != nil { + return err + } + } + + // Clean node + if d.HasChange("clean") && d.Get("clean").(bool) { + if err := ChangeProvisionStateToTarget(client, d.Id(), "clean", nil); err != nil { + return fmt.Errorf("could not clean: %s", err) + } + } + + // Inspect node + if d.HasChange("inspect") && d.Get("inspect").(bool) { + if err := ChangeProvisionStateToTarget(client, d.Id(), "inspect", nil); err != nil { + return fmt.Errorf("could not inspect: %s", err) + } + } + + // Make node available + if d.HasChange("available") && d.Get("available").(bool) { + if err := ChangeProvisionStateToTarget(client, d.Id(), "provide", nil); err != nil { + return fmt.Errorf("could not make node available: %s", err) + } + } + + if d.HasChange("properties") || d.HasChange("root_device") { + properties := propertiesMerge(d, "root_device") + opts := nodes.UpdateOpts{ + nodes.UpdateOperation{ + Op: nodes.AddOp, + Path: "/properties", + Value: properties, + }, + } + if _, err := nodes.Update(client, d.Id(), opts).Extract(); err != nil { + return err + } + } + + d.Partial(false) + + return resourceNodeV1Read(d, meta) +} + +// Delete a node from Ironic +func resourceNodeV1Delete(d *schema.ResourceData, meta interface{}) error { + client := meta.(Clients).Ironic + if err := ChangeProvisionStateToTarget(client, d.Id(), "deleted", nil); err != nil { + return err + } + + return nodes.Delete(client, d.Id()).ExtractErr() +} + +func propertiesMerge(d *schema.ResourceData, key string) map[string]interface{} { + properties := d.Get("properties").(map[string]interface{}) + properties[key] = d.Get(key).(map[string]interface{}) + return properties +} + +// Convert terraform schema to gophercloud CreateOpts +// TODO: Is there a better way to do this? Annotations? +func schemaToCreateOpts(d *schema.ResourceData) *nodes.CreateOpts { + properties := propertiesMerge(d, "root_device") + return &nodes.CreateOpts{ + BootInterface: d.Get("boot_interface").(string), + ConductorGroup: d.Get("conductor_group").(string), + ConsoleInterface: d.Get("console_interface").(string), + DeployInterface: d.Get("deploy_interface").(string), + Driver: d.Get("driver").(string), + DriverInfo: d.Get("driver_info").(map[string]interface{}), + Extra: d.Get("extra").(map[string]interface{}), + InspectInterface: d.Get("inspect_interface").(string), + ManagementInterface: d.Get("management_interface").(string), + Name: d.Get("name").(string), + NetworkInterface: d.Get("network_interface").(string), + Owner: d.Get("owner").(string), + PowerInterface: d.Get("power_interface").(string), + Properties: properties, + RAIDInterface: d.Get("raid_interface").(string), + RescueInterface: d.Get("rescue_interface").(string), + ResourceClass: d.Get("resource_class").(string), + StorageInterface: d.Get("storage_interface").(string), + VendorInterface: d.Get("vendor_interface").(string), + } +} + +// Call Ironic's API and change the power state of the node +func changePowerState(client *gophercloud.ServiceClient, d *schema.ResourceData, target nodes.TargetPowerState) error { + opts := nodes.PowerStateOpts{ + Target: target, + } + + timeout := d.Get("power_state_timeout").(int) + if timeout != 0 { + opts.Timeout = timeout + } else { + timeout = 300 // used below for how long to wait for Ironic to finish + } + + if err := nodes.ChangePowerState(client, d.Id(), opts).ExtractErr(); err != nil { + return err + } + + // Wait for target_power_state to be empty, i.e. Ironic thinks it's finished + checkInterval := 5 + + for { + node, err := nodes.Get(client, d.Id()).Extract() + if err != nil { + return err + } + + if node.TargetPowerState == "" { + break + } + + time.Sleep(time.Duration(checkInterval) * time.Second) + timeout -= checkInterval + if timeout <= 0 { + return fmt.Errorf("timed out waiting for power state change") + } + } + + return nil +} diff --git a/pkg/terraform/exec/plugins/vendor/github.com/openshift-metalkube/terraform-provider-ironic/ironic/resource_ironic_port_v1.go b/pkg/terraform/exec/plugins/vendor/github.com/openshift-metalkube/terraform-provider-ironic/ironic/resource_ironic_port_v1.go new file mode 100644 index 00000000000..df5d42b0ede --- /dev/null +++ b/pkg/terraform/exec/plugins/vendor/github.com/openshift-metalkube/terraform-provider-ironic/ironic/resource_ironic_port_v1.go @@ -0,0 +1,109 @@ +package ironic + +import ( + "github.com/gophercloud/gophercloud/openstack/baremetal/v1/ports" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourcePortV1() *schema.Resource { + return &schema.Resource{ + Create: resourcePortV1Create, + Read: resourcePortV1Read, + Update: resourcePortV1Update, + Delete: resourcePortV1Delete, + + Schema: map[string]*schema.Schema{ + "node_uuid": { + Type: schema.TypeString, + Optional: true, + }, + "address": { + Type: schema.TypeString, + Optional: true, + }, + "port_group_uuid": { + Type: schema.TypeString, + Optional: true, + }, + "local_link_connection": { + Type: schema.TypeMap, + Optional: true, + }, + "pxe_enabled": { + Type: schema.TypeBool, + Optional: true, + }, + "physical_network": { + Type: schema.TypeString, + Optional: true, + }, + "extra": { + Type: schema.TypeMap, + Optional: true, + }, + "is_smart_nic": { + Type: schema.TypeBool, + Optional: true, + }, + }, + } +} + +func resourcePortV1Create(d *schema.ResourceData, meta interface{}) error { + client := meta.(Clients).Ironic + opts := portSchemaToCreateOpts(d) + result, err := ports.Create(client, opts).Extract() + if err != nil { + return err + } + d.SetId(result.UUID) + + return resourcePortV1Read(d, meta) +} + +func resourcePortV1Read(d *schema.ResourceData, meta interface{}) error { + client := meta.(Clients).Ironic + port, err := ports.Get(client, d.Id()).Extract() + if err != nil { + return err + } + + d.Set("address", port.Address) + d.Set("node_uuid", port.NodeUUID) + d.Set("port_group_id", port.PortGroupUUID) + d.Set("local_link_collection", port.LocalLinkConnection) + d.Set("pxe_enabled", port.PXEEnabled) + d.Set("physical_network", port.PhysicalNetwork) + d.Set("extra", port.Extra) + d.Set("is_smart_nic", port.IsSmartNIC) + + return nil +} + +func resourcePortV1Update(d *schema.ResourceData, meta interface{}) error { + return nil + +} + +func resourcePortV1Delete(d *schema.ResourceData, meta interface{}) error { + return nil + +} + +func portSchemaToCreateOpts(d *schema.ResourceData) *ports.CreateOpts { + pxeEnabled := d.Get("pxe_enabled").(bool) + isSmartNic := d.Get("is_smart_nic").(bool) + + opts := ports.CreateOpts{ + NodeUUID: d.Get("node_uuid").(string), + Address: d.Get("address").(string), + PortGroupUUID: d.Get("port_group_uuid").(string), + //LocalLinkConnection: d.Get("local_link_collection").(map[string]interface{}), + PXEEnabled: &pxeEnabled, + PhysicalNetwork: d.Get("physical_network").(string), + //Extra: d.Get("extra").(map[string]interface{}), + IsSmartNIC: &isSmartNic, + } + + return &opts +} diff --git a/pkg/terraform/exec/plugins/vendor/github.com/openshift-metalkube/terraform-provider-ironic/ironic/workflow.go b/pkg/terraform/exec/plugins/vendor/github.com/openshift-metalkube/terraform-provider-ironic/ironic/workflow.go new file mode 100644 index 00000000000..7dd26fee243 --- /dev/null +++ b/pkg/terraform/exec/plugins/vendor/github.com/openshift-metalkube/terraform-provider-ironic/ironic/workflow.go @@ -0,0 +1,294 @@ +package ironic + +import ( + "fmt" + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes" + utils "github.com/gophercloud/utils/openstack/baremetal/v1/nodes" + "log" + "time" +) + +// provisionStateWorkflow is used to track state through the process of updating's it's provision state +type provisionStateWorkflow struct { + client *gophercloud.ServiceClient + node nodes.Node + uuid string + target nodes.TargetProvisionState + wait time.Duration + + configDrive *utils.ConfigDrive +} + +// ChangeProvisionStateToTarget drives Ironic's state machine through the process to reach our desired end state. This requires multiple +// possibly long-running steps. If required, we'll build a config drive ISO for deployment. +func ChangeProvisionStateToTarget(client *gophercloud.ServiceClient, uuid string, target nodes.TargetProvisionState, configDrive *utils.ConfigDrive) error { + + // Run the provisionStateWorkflow - this could take a while + wf := provisionStateWorkflow{ + target: target, + client: client, + wait: 5 * time.Second, + uuid: uuid, + configDrive: configDrive, + } + + err := wf.run() + return err +} + +// Keep driving the state machine forward +func (workflow *provisionStateWorkflow) run() error { + log.Printf("[INFO] Beginning provisioning workflow, will try to change node to state '%s'", workflow.target) + + for { + done, err := workflow.next() + if done || err != nil { + return err + } + + time.Sleep(workflow.wait) + } + + return nil +} + +// Do the next thing to get us to our target state +func (workflow *provisionStateWorkflow) next() (done bool, err error) { + // Refresh the node on each run + if err := workflow.reloadNode(); err != nil { + return true, err + } + + log.Printf("[DEBUG] Node current state is '%s'", workflow.node.ProvisionState) + + switch target := nodes.TargetProvisionState(workflow.target); target { + case nodes.TargetManage: + return workflow.toManageable() + case nodes.TargetProvide: + return workflow.toAvailable() + case nodes.TargetActive: + return workflow.toActive() + case nodes.TargetDeleted: + return workflow.toDeleted() + case nodes.TargetClean: + return workflow.toClean() + case nodes.TargetInspect: + return workflow.toInspect() + default: + return true, fmt.Errorf("unknown target state '%s'", target) + } +} + +// Change a node to "manageable" stable +func (workflow *provisionStateWorkflow) toManageable() (done bool, err error) { + switch state := workflow.node.ProvisionState; state { + case "manageable": + // We're done! + return true, err + case "enroll", + "adopt failed", + "clean failed", + "inspect failed", + "available": + return workflow.changeProvisionState(nodes.TargetManage) + case "verifying": + // Not done, no error - Ironic is working + return false, nil + + default: + return true, fmt.Errorf("cannot go from state '%s' to state 'manageable'", state) + } + + return false, nil +} + +// Clean a node +func (workflow *provisionStateWorkflow) toClean() (done bool, err error) { + // Node must be manageable first + workflow.reloadNode() + if workflow.node.ProvisionState != string(nodes.Manageable) { + if err := ChangeProvisionStateToTarget(workflow.client, workflow.uuid, nodes.TargetManage, nil); err != nil { + return true, err + } + } + + // Set target to clean + workflow.changeProvisionState(nodes.TargetClean) + + for { + workflow.reloadNode() + state := workflow.node.ProvisionState + + switch state { + case "manageable": + return true, nil + case "cleaning", + "clean wait": + // Not done, no error - Ironic is working + continue + default: + return true, fmt.Errorf("could not clean node, node is currently '%s', last error was '%s'", state, workflow.node.LastError) + } + } + + return true, nil +} + +// Inspect a node +func (workflow *provisionStateWorkflow) toInspect() (done bool, err error) { + // Node must be manageable first + workflow.reloadNode() + if workflow.node.ProvisionState != string(nodes.Manageable) { + if err := ChangeProvisionStateToTarget(workflow.client, workflow.uuid, nodes.TargetManage, nil); err != nil { + return true, err + } + } + + // Set target to inspect + workflow.changeProvisionState(nodes.TargetInspect) + + for { + workflow.reloadNode() + state := workflow.node.ProvisionState + + switch state { + case "manageable": + return true, nil + case "inspecting", + "inspect wait": + // Not done, no error - Ironic is working + continue + default: + return true, fmt.Errorf("could not inspect node, node is currently '%s', last error was '%s'", state, workflow.node.LastError) + } + } + + return true, nil +} + +// Change a node to "available" state +func (workflow *provisionStateWorkflow) toAvailable() (done bool, err error) { + switch state := workflow.node.ProvisionState; state { + case "available": + // We're done! + return true, nil + case "cleaning", + "clean wait": + // Not done, no error - Ironic is working + log.Printf("[DEBUG] Node %s is '%s', waiting for Ironic to finish.", workflow.uuid, state) + return false, nil + case "manageable": + // From manageable, we can go to provide + log.Printf("[DEBUG] Node %s is '%s', going to change to 'available'", workflow.uuid, state) + return workflow.changeProvisionState(nodes.TargetProvide) + default: + // Otherwise we have to get into manageable state first + log.Printf("[DEBUG] Node %s is '%s', going to change to 'manageable'.", workflow.uuid, state) + _, err := workflow.toManageable() + if err != nil { + return true, err + } + return false, nil + } + + return false, nil +} + +// Change a node to "active" state +func (workflow *provisionStateWorkflow) toActive() (bool, error) { + + switch state := workflow.node.ProvisionState; state { + case "active": + // We're done! + log.Printf("[DEBUG] Node %s is 'active', we are done.", workflow.uuid) + return true, nil + case "deploying", + "wait call-back": + // Not done, no error - Ironic is working + log.Printf("[DEBUG] Node %s is '%s', waiting for Ironic to finish.", workflow.uuid, state) + return false, nil + case "available": + // From available, we can go to active + log.Printf("[DEBUG] Node %s is 'available', going to change to 'active'.", workflow.uuid) + workflow.wait = 30 * time.Second // Deployment takes a while + return workflow.changeProvisionState(nodes.TargetActive) + default: + // Otherwise we have to get into available state first + log.Printf("[DEBUG] Node %s is '%s', going to change to 'available'.", workflow.uuid, state) + _, err := workflow.toAvailable() + if err != nil { + return true, err + } + return false, nil + } +} + +// Change a node to be "deleted," and remove the object from Ironic +func (workflow *provisionStateWorkflow) toDeleted() (bool, error) { + switch state := workflow.node.ProvisionState; state { + case "manageable", + "available", + "enroll": + // We're done deleting the node + return true, nil + case "cleaning", + "deleting": + // Not done, no error - Ironic is working + log.Printf("[DEBUG] Node %s is '%s', waiting for Ironic to finish.", workflow.uuid, state) + return false, nil + case "active", + "wait call-back", + "deploy failed", + "error": + log.Printf("[DEBUG] Node %s is '%s', going to change to 'deleted'.", workflow.uuid, state) + return workflow.changeProvisionState(nodes.TargetDeleted) + case "inspect failed", + "clean failed": + // We have to get into manageable state first + log.Printf("[DEBUG] Node %s is '%s', going to change to 'manageable'.", workflow.uuid, state) + _, err := workflow.toManageable() + if err != nil { + return true, err + } + return false, nil + default: + return true, fmt.Errorf("cannot delete node in state '%s'", state) + } + + return false, nil +} + +// Builds the ProvisionStateOpts to send to Ironic -- including config drive. +func (workflow *provisionStateWorkflow) buildProvisionStateOpts(target nodes.TargetProvisionState) (*nodes.ProvisionStateOpts, error) { + opts := nodes.ProvisionStateOpts{ + Target: target, + } + + // If we're deploying, then build a config drive to send to Ironic + if target == "active" { + configDriveData, err := workflow.configDrive.ToConfigDrive() + if err != nil { + return nil, err + } + opts.ConfigDrive = configDriveData + } + + return &opts, nil +} + +// Call Ironic's API and issue the change provision state request. +func (workflow *provisionStateWorkflow) changeProvisionState(target nodes.TargetProvisionState) (done bool, err error) { + opts, err := workflow.buildProvisionStateOpts(target) + if err != nil { + log.Printf("[ERROR] Unable to construct provisioning state options: %s", err.Error()) + return true, err + } + + return false, nodes.ChangeProvisionState(workflow.client, workflow.uuid, *opts).ExtractErr() +} + +// Call Ironic's API and reload the node's current state +func (workflow *provisionStateWorkflow) reloadNode() error { + return nodes.Get(workflow.client, workflow.uuid).ExtractInto(&workflow.node) +}