Skip to content

Commit

Permalink
[OpenStack] Support os-client-config usage for authentication
Browse files Browse the repository at this point in the history
Update Gophercloud dependencies and also bring in the 'utils'
package.  This provides support for configuring access to OpenStack
clouds as detailed in the [official
documentation](https://docs.openstack.org/os-client-config/latest/user/configuration.html).

By relying on this package we can simplify the code required to
interact with OpenStack's APIs.  Support is also provided upstream for
self-signed and insecure SSL configurations.

Tested with a public cloud running OpenStack 'Rocky', the latest release.

Signed-off-by: Nick Jones <nick@dischord.org>
  • Loading branch information
yankcrime committed Jan 19, 2019
1 parent eb7e075 commit 1df6804
Show file tree
Hide file tree
Showing 57 changed files with 4,090 additions and 900 deletions.
29 changes: 2 additions & 27 deletions docs/platform-openstack.md
Expand Up @@ -11,17 +11,7 @@ Supported (tested) versions of the relevant OpenStack APIs are:

## Authentication

LinuxKit's support for OpenStack handles two ways of providing the endpoint and authentication details. You can either set the standard set of environment variables and the commands detailed below will inherit those, or you can explicitly provide them on the command-line as options to `push` and `run`. The examples below use the latter, but if you prefer the former then you'll need to set the following:

```shell
OS_USERNAME="admin"
OS_PASSWORD="xxx"
OS_TENANT_NAME="linuxkit"
OS_AUTH_URL="https://keystone.com:5000/v3"
OS_USER_DOMAIN_NAME=default
OS_CACERT=/path/to/cacert.pem
OS_INSECURE=false
```
LinuxKit's support for OpenStack includes configuring access to your cloud as detailed in the official [os-client-config](https://docs.openstack.org/os-client-config/latest/user/configuration.html) documentation.

## Push

Expand All @@ -40,32 +30,17 @@ Images generated with Moby can be uploaded into OpenStack's image service with `

```shell
./linuxkit push openstack \
-authurl=https://keystone.example.com:5000/v3 \
-username=admin \
-password=XXXXXXXXXXX \
-project=linuxkit \
-img-name=LinuxKitTest
./linuxkit.iso
```

If successful, this will return the image's UUID. If you've set your environment variables up as described above, this command can then be simplified:

```shell
./linuxkit push openstack \
-img-name "LinuxKitTest" \
~/Desktop/linuxkitmage.qcow2
```

## Run

Virtual machines can be launched using `linuxkit run openstack`. As an example:

```shell
linuxkit run openstack \
-authurl https://keystone.example.com:5000/v3 \
-username=admin \
-password=xxx \
-project=linuxkit \
-flavor=hotdog
-keyname=deadline_ed25519 \
-sec-groups=allow_ssh,nginx \
-network c5d02c5f-c625-4539-8aed-1dab3aa85a0a \
Expand Down
48 changes: 6 additions & 42 deletions src/cmd/linuxkit/push_openstack.go
Expand Up @@ -9,9 +9,10 @@ import (
"strings"

"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack"
"github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata"
"github.com/gophercloud/gophercloud/openstack/imageservice/v2/images"
"github.com/gophercloud/utils/openstack/clientconfig"

log "github.com/sirupsen/logrus"
)

Expand All @@ -25,14 +26,7 @@ func pushOpenstack(args []string) {
fmt.Printf("Options:\n\n")
flags.PrintDefaults()
}
authurlFlag := flags.String("authurl", "", "The URL of the OpenStack identity service, i.e https://keystone.example.com:5000/v3")
imageName := flags.String("img-name", "", "A unique name for the image, if blank the filename will be used")
passwordFlag := flags.String("password", "", "Password for the specified username")
projectNameFlag := flags.String("project", "", "Name of the Project (aka Tenant) to be used")
userDomainFlag := flags.String("domain", "Default", "Domain name")
usernameFlag := flags.String("username", "", "Username with permissions to upload image")
cacertFlag := flags.String("cacert", "", "CA certificate bundle file")
insecureFlag := flags.Bool("insecure", false, "Disable server certificate verification")

if err := flags.Parse(args); err != nil {
log.Fatal("Unable to parse args")
Expand All @@ -48,41 +42,15 @@ func pushOpenstack(args []string) {
// Check that the file both exists, and can be read
checkFile(filePath)

authurl := getStringValue(authurlVar, *authurlFlag, "")
password := getStringValue(passwordVar, *passwordFlag, "")
projectName := getStringValue(projectNameVar, *projectNameFlag, "")
userDomain := getStringValue(userDomainVar, *userDomainFlag, "")
username := getStringValue(usernameVar, *usernameFlag, "")
cacert := getStringValue(cacertVar, *cacertFlag, "")
insecure := getBoolValue(insecureVar, *insecureFlag)

authOpts := gophercloud.AuthOptions{
DomainName: userDomain,
IdentityEndpoint: authurl,
Password: password,
TenantName: projectName,
Username: username,
}

provider, err := openstack.NewClient(authOpts.IdentityEndpoint)
if err != nil {
log.Fatalf("Failed to connect to OpenStack: %s", err)
}

provider.HTTPClient, err = openstackHTTPClient(cacert, insecure)
client, err := clientconfig.NewServiceClient("image", nil)
if err != nil {
log.Fatalf("Failed to authenticate with OpenStack: %s", err)
log.Fatalf("Error connecting to your OpenStack cloud: %s", err)
}

err = openstack.Authenticate(provider, authOpts)
if err != nil {
log.Fatalf("Failed to authenticate with OpenStack: %s", err)
}

createOpenStackImage(filePath, *imageName, provider)
createOpenStackImage(filePath, *imageName, client)
}

func createOpenStackImage(filePath string, imageName string, provider *gophercloud.ProviderClient) {
func createOpenStackImage(filePath string, imageName string, client *gophercloud.ServiceClient) {
// Image formats that are supported by both LinuxKit and OpenStack Glance V2
formats := []string{"ami", "vhd", "vhdx", "vmdk", "raw", "qcow2", "iso"}

Expand All @@ -106,10 +74,6 @@ func createOpenStackImage(filePath string, imageName string, provider *gopherclo
imageName = fileName
}

client, err := openstack.NewImageServiceV2(provider, gophercloud.EndpointOpts{})
if err != nil {
log.Fatalf("Unable to create Image V2 client: %s", err)
}
imageOpts := images.CreateOpts{
Name: imageName,
ContainerFormat: "bare",
Expand Down
82 changes: 5 additions & 77 deletions src/cmd/linuxkit/run_openstack.go
@@ -1,57 +1,23 @@
package main

import (
"crypto/tls"
"crypto/x509"
"errors"
"flag"
"fmt"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"strings"

"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs"
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
"github.com/gophercloud/utils/openstack/clientconfig"

log "github.com/sirupsen/logrus"
)

const (
defaultOSFlavor = "m1.tiny"
authurlVar = "OS_AUTH_URL"
usernameVar = "OS_USERNAME"
passwordVar = "OS_PASSWORD"
projectNameVar = "OS_PROJECT_NAME"
userDomainVar = "OS_USER_DOMAIN_NAME"
cacertVar = "OS_CACERT"
insecureVar = "OS_INSECURE"
)

func openstackHTTPClient(cacert string, insecure bool) (http.Client, error) {
if cacert == "" {
return http.Client{}, nil
}

caCertPool := x509.NewCertPool()
caCert, err := ioutil.ReadFile(cacert)
if err != nil {
return http.Client{}, errors.New("Can't read certificate file")
}
caCertPool.AppendCertsFromPEM(caCert)

tlsConfig := &tls.Config{
RootCAs: caCertPool,
InsecureSkipVerify: insecure,
}
tlsConfig.BuildNameToCertificate()
transport := &http.Transport{TLSClientConfig: tlsConfig}

return http.Client{Transport: transport}, nil
}

func runOpenStack(args []string) {
flags := flag.NewFlagSet("openstack", flag.ExitOnError)
invoked := filepath.Base(os.Args[0])
Expand All @@ -62,18 +28,11 @@ func runOpenStack(args []string) {
fmt.Printf("Options:\n\n")
flags.PrintDefaults()
}
authurlFlag := flags.String("authurl", "", "The URL of the OpenStack identity service, i.e https://keystone.example.com:5000/v3")
flavorName := flags.String("flavor", defaultOSFlavor, "Instance size (flavor)")
instanceName := flags.String("instancename", "", "Name of instance. Defaults to the name of the image if not specified")
networkID := flags.String("network", "", "The ID of the network to attach the instance to")
secGroups := flags.String("sec-groups", "", "Security Group names separated by comma")
secGroups := flags.String("sec-groups", "default", "Security Group names separated by comma")
keyName := flags.String("keyname", "", "The name of the SSH keypair to associate with the instance")
passwordFlag := flags.String("password", "", "Password for the specified username")
projectNameFlag := flags.String("project", "", "Name of the Project (aka Tenant) to be used")
userDomainFlag := flags.String("domain", "Default", "Domain name")
usernameFlag := flags.String("username", "", "Username with permissions to create an instance")
cacertFlag := flags.String("cacert", "", "CA certificate bundle file")
insecureFlag := flags.Bool("insecure", false, "Disable server certificate verification")

if err := flags.Parse(args); err != nil {
log.Fatal("Unable to parse args")
Expand All @@ -91,40 +50,9 @@ func runOpenStack(args []string) {
*instanceName = name
}

authurl := getStringValue(authurlVar, *authurlFlag, "")
password := getStringValue(passwordVar, *passwordFlag, "")
projectName := getStringValue(projectNameVar, *projectNameFlag, "")
userDomain := getStringValue(userDomainVar, *userDomainFlag, "")
username := getStringValue(usernameVar, *usernameFlag, "")
cacert := getStringValue(cacertVar, *cacertFlag, "")
insecure := getBoolValue(insecureVar, *insecureFlag)

authOpts := gophercloud.AuthOptions{
DomainName: userDomain,
IdentityEndpoint: authurl,
Password: password,
TenantName: projectName,
Username: username,
}

provider, err := openstack.NewClient(authOpts.IdentityEndpoint)
if err != nil {
log.Fatalf("Failed to connect to OpenStack: %s", err)
}

provider.HTTPClient, err = openstackHTTPClient(cacert, insecure)
if err != nil {
log.Fatalf("Failed to authenticate with OpenStack: %s", err)
}

err = openstack.Authenticate(provider, authOpts)
if err != nil {
log.Fatalf("Failed to authenticate with OpenStack: %s", err)
}

client, err := openstack.NewComputeV2(provider, gophercloud.EndpointOpts{})
client, err := clientconfig.NewServiceClient("compute", nil)
if err != nil {
log.Fatalf("Unable to create Compute V2 client, %s", err)
log.Fatalf("Unable to create Compute client, %s", err)
}

network := servers.Network{
Expand Down
3 changes: 2 additions & 1 deletion src/cmd/linuxkit/vendor.conf
Expand Up @@ -25,7 +25,8 @@ github.com/gogo/protobuf v1.0.0
github.com/golang/protobuf v1.1.0
github.com/google/uuid 7e072fc3a7be179aee6d3359e46015aa8c995314
github.com/googleapis/gax-go 8c5154c0fe5bf18cf649634d4c6df50897a32751
github.com/gophercloud/gophercloud 2804b72cf099b41d2e25c8afcca786f9f962ddee
github.com/gophercloud/gophercloud b9ea9cb68cf5803ea1567c404b549a783c8264b2
github.com/gophercloud/utils 34f5991525d116b3832e0d9409492274f1c06bda
github.com/gorilla/context v1.1
github.com/gorilla/mux v1.1
github.com/gorilla/websocket 21ab95fa12b9bdd8fecf5fa3586aad941cc98785
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 1df6804

Please sign in to comment.