mirror of
https://github.com/linuxkit/linuxkit.git
synced 2025-07-20 17:49:10 +00:00
OpenStack Push support
Signed-off-by: Dan Finneran <daniel.finneran@gmail.com>
This commit is contained in:
parent
5545f3085a
commit
41f2d2c256
@ -64,6 +64,7 @@ Currently supported platforms are:
|
|||||||
- [Amazon Web Services](docs/platform-aws.md)
|
- [Amazon Web Services](docs/platform-aws.md)
|
||||||
- [Google Cloud](docs/platform-gcp.md)
|
- [Google Cloud](docs/platform-gcp.md)
|
||||||
- [Microsoft Azure](docs/platform-azure.md)
|
- [Microsoft Azure](docs/platform-azure.md)
|
||||||
|
- [OpenStack](docs/platform-openstack.md)
|
||||||
- [packet.net](docs/platform-packet.md)
|
- [packet.net](docs/platform-packet.md)
|
||||||
|
|
||||||
|
|
||||||
|
53
docs/platform-openstack.md
Normal file
53
docs/platform-openstack.md
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# LinuxKit with OpenStack
|
||||||
|
|
||||||
|
LinuxKit interacts with OpenStack through its native APIs and requires access
|
||||||
|
to both an OpenStack Keystone server for authentication and a OpenStack Glance
|
||||||
|
server in order to host the LinuxKit images.
|
||||||
|
|
||||||
|
Supported (Tested) Versions:
|
||||||
|
|
||||||
|
- OpenStack Ocata Release
|
||||||
|
- Keystone v3 API
|
||||||
|
- Glance v2 API
|
||||||
|
|
||||||
|
##Push
|
||||||
|
|
||||||
|
### Image types supported:
|
||||||
|
- **ami** (Amazon Machine image)
|
||||||
|
- **vhd** (Hyper-V)
|
||||||
|
- **vhdx** (Hyper-V)
|
||||||
|
- **vmdk** (VMware Disk)
|
||||||
|
- **raw** (Raw disk image)
|
||||||
|
- **qcow2** (Qemu disk image)
|
||||||
|
- **iso** (ISO9660 compatible CD-ROM image)
|
||||||
|
|
||||||
|
A compatible/supported image needs to have the correct extension (must match
|
||||||
|
one from above) in order to be supported by the `linuxkit push openstack`
|
||||||
|
command. The `openstack` backend will use the filename extension to determine
|
||||||
|
the image type, and use the filename as a label for the new image.
|
||||||
|
|
||||||
|
The `openstack` backend also supports OpenStack projects to provide
|
||||||
|
multi-tenancy support when uploading images.
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
The `openstack` backend uses the password authentication method in order to
|
||||||
|
retrieve a token that can be used to interact with the various components of
|
||||||
|
OpenStack. The URLs for the Keystone/Glance server components need to have
|
||||||
|
the ports detailed as below.
|
||||||
|
|
||||||
|
```
|
||||||
|
./linuxkit push openstack \
|
||||||
|
-keystoneAddr=http://keystone.com:5000 \
|
||||||
|
-username=admin \
|
||||||
|
-password=XXXXXXXXXXX \
|
||||||
|
-project=linuxkit \
|
||||||
|
-glanceAddr=http://glance.com:9292 \
|
||||||
|
./linuxkit.iso
|
||||||
|
```
|
||||||
|
|
||||||
|
### Execution Flow
|
||||||
|
1. Log in to OpenStack (Keystone)
|
||||||
|
2. Retrieve the OpenStack Key from the response header
|
||||||
|
3. Create a "queued" image on the glance server and return the new image ID
|
||||||
|
4. Use the new image ID and upload the LinuxKit image under this new ID
|
@ -17,6 +17,7 @@ func pushUsage() {
|
|||||||
fmt.Printf(" aws\n")
|
fmt.Printf(" aws\n")
|
||||||
fmt.Printf(" azure\n")
|
fmt.Printf(" azure\n")
|
||||||
fmt.Printf(" gcp\n")
|
fmt.Printf(" gcp\n")
|
||||||
|
fmt.Printf(" openstack\n")
|
||||||
fmt.Printf(" vcenter\n")
|
fmt.Printf(" vcenter\n")
|
||||||
fmt.Printf("\n")
|
fmt.Printf("\n")
|
||||||
fmt.Printf("'options' are the backend specific options.\n")
|
fmt.Printf("'options' are the backend specific options.\n")
|
||||||
@ -38,6 +39,8 @@ func push(args []string) {
|
|||||||
pushAzure(args[1:])
|
pushAzure(args[1:])
|
||||||
case "gcp":
|
case "gcp":
|
||||||
pushGcp(args[1:])
|
pushGcp(args[1:])
|
||||||
|
case "openstack":
|
||||||
|
pushOpenstack(args[1:])
|
||||||
case "vcenter":
|
case "vcenter":
|
||||||
pushVCenter(args[1:])
|
pushVCenter(args[1:])
|
||||||
case "help", "-h", "-help", "--help":
|
case "help", "-h", "-help", "--help":
|
||||||
|
250
src/cmd/linuxkit/push_openstack.go
Normal file
250
src/cmd/linuxkit/push_openstack.go
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// KeyStoneV3 for OpenStack login
|
||||||
|
type KeyStoneV3 struct {
|
||||||
|
Auth struct {
|
||||||
|
Identity struct {
|
||||||
|
Methods []string `json:"methods"`
|
||||||
|
Password struct {
|
||||||
|
User struct {
|
||||||
|
Domain struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
} `json:"domain"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
} `json:"user"`
|
||||||
|
} `json:"password"`
|
||||||
|
} `json:"identity"`
|
||||||
|
Scope struct {
|
||||||
|
Project struct {
|
||||||
|
Domain struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
} `json:"domain"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
} `json:"project"`
|
||||||
|
} `json:"scope"`
|
||||||
|
} `json:"auth"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GlanceV2Image - the struct for uploading of images
|
||||||
|
type GlanceV2Image struct {
|
||||||
|
ContainerFormat string `json:"container_format"`
|
||||||
|
DiskFormat string `json:"disk_format"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GlanceV2ImageResponse - the struct for uploading of images
|
||||||
|
type GlanceV2ImageResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Tags []interface{} `json:"tags"`
|
||||||
|
ContainerFormat string `json:"container_format"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
Size interface{} `json:"size"`
|
||||||
|
DiskFormat string `json:"disk_format"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
Visibility string `json:"visibility"`
|
||||||
|
Locations []interface{} `json:"locations"`
|
||||||
|
Self string `json:"self"`
|
||||||
|
MinDisk int `json:"min_disk"`
|
||||||
|
Protected bool `json:"protected"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
File string `json:"file"`
|
||||||
|
Checksum interface{} `json:"checksum"`
|
||||||
|
Owner string `json:"owner"`
|
||||||
|
VirtualSize interface{} `json:"virtual_size"`
|
||||||
|
MinRAM int `json:"min_ram"`
|
||||||
|
Schema string `json:"schema"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process the run arguments and execute run
|
||||||
|
func pushOpenstack(args []string) {
|
||||||
|
flags := flag.NewFlagSet("openstack", flag.ExitOnError)
|
||||||
|
invoked := filepath.Base(os.Args[0])
|
||||||
|
flags.Usage = func() {
|
||||||
|
fmt.Printf("USAGE: %s push openstack [options] path\n\n", invoked)
|
||||||
|
fmt.Printf("'path' is the full path to an image that will be uploaded to an OpenStack Image store (glance)\n")
|
||||||
|
fmt.Printf("Options:\n\n")
|
||||||
|
flags.PrintDefaults()
|
||||||
|
}
|
||||||
|
usernameFlag := flags.String("username", "", "Username with permissions to upload image")
|
||||||
|
passwordFlag := flags.String("password", "", "Password for the Username")
|
||||||
|
userDomainFlag := flags.String("userDomain", "Default", "")
|
||||||
|
|
||||||
|
projectName := flags.String("project", "", "Name of the Project to be used")
|
||||||
|
projectDomain := flags.String("projectDomain", "Default", "")
|
||||||
|
|
||||||
|
keystoneAddress := flags.String("keystoneAddr", "", "The hostname/address of the keystone server to AUTH against, including port(5000)")
|
||||||
|
glanceAddress := flags.String("glanceAddr", "", "The hostname/address of the glance server, including port(9292)")
|
||||||
|
|
||||||
|
imageName := flags.String("imageName", "", "A Unique name for the image, if blank the filename will be used")
|
||||||
|
|
||||||
|
if err := flags.Parse(args); err != nil {
|
||||||
|
log.Fatal("Unable to parse args")
|
||||||
|
}
|
||||||
|
|
||||||
|
remArgs := flags.Args()
|
||||||
|
if len(remArgs) == 0 {
|
||||||
|
fmt.Printf("Please specify the path to the image to push\n")
|
||||||
|
flags.Usage()
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
filePath := remArgs[0]
|
||||||
|
// Check that the file both exists, and can be read
|
||||||
|
checkFile(filePath)
|
||||||
|
|
||||||
|
var data KeyStoneV3
|
||||||
|
// Defaulting to password login, other login methods may be added later
|
||||||
|
data.Auth.Identity.Methods = append(data.Auth.Identity.Methods, "password")
|
||||||
|
|
||||||
|
data.Auth.Identity.Password.User.Name = *usernameFlag
|
||||||
|
data.Auth.Identity.Password.User.Domain.Name = *userDomainFlag
|
||||||
|
data.Auth.Identity.Password.User.Password = *passwordFlag
|
||||||
|
data.Auth.Scope.Project.Domain.Name = *projectDomain
|
||||||
|
data.Auth.Scope.Project.Name = *projectName
|
||||||
|
|
||||||
|
payloadBytes, err := json.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error building JSON: %v", err)
|
||||||
|
}
|
||||||
|
body := bytes.NewReader(payloadBytes)
|
||||||
|
req, err := http.NewRequest("POST", fmt.Sprintf("%s/v3/auth/tokens", *keystoneAddress), body)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error Creating HTTP Request: %v", err)
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Return Code: %s Error:%v", resp.Status, err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode == 201 { // OK
|
||||||
|
// Find the OpenStack KeyStone Token
|
||||||
|
openstackToken := resp.Header.Get("x-subject-token")
|
||||||
|
|
||||||
|
log.Debugf("Token => %s", openstackToken)
|
||||||
|
|
||||||
|
if openstackToken == "" {
|
||||||
|
log.Fatalln("Error: Can't locate OpenStack Token in Headers")
|
||||||
|
}
|
||||||
|
// Create a new Image (which will be left in a queued state)
|
||||||
|
imageID := createOpenStackImage(filePath, *imageName, *glanceAddress, openstackToken)
|
||||||
|
// Take the returned ImageID and upload our image to the created ID
|
||||||
|
uploadOpenStackImage(filePath, *glanceAddress, openstackToken, imageID)
|
||||||
|
} else {
|
||||||
|
message, _ := ioutil.ReadAll(resp.Body)
|
||||||
|
log.Fatalf("Error authenticating with OpenStack, Error Details:\n%s", string(message))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createOpenStackImage(filePath string, name string, glanceAddress string, token string) string {
|
||||||
|
// Currently supported image formats that are both supported by LinuxKit and OpenStack Glance V2
|
||||||
|
formats := []string{"ami", "vhd", "vhdx", "vmdk", "raw", "qcow2", "iso"}
|
||||||
|
|
||||||
|
// Find extension of the filename and remove the leading stop
|
||||||
|
fileExtension := strings.Replace(path.Ext(filePath), ".", "", -1)
|
||||||
|
fileName := strings.TrimSuffix(path.Base(filePath), filepath.Ext(filePath))
|
||||||
|
// Check for Supported extension
|
||||||
|
var supportedExtension bool
|
||||||
|
supportedExtension = false
|
||||||
|
for i := 0; i < len(formats); i++ {
|
||||||
|
if strings.ContainsAny(fileExtension, formats[i]) {
|
||||||
|
supportedExtension = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if supportedExtension == false {
|
||||||
|
log.Fatalf("Extension [%s] is not supported", fileExtension)
|
||||||
|
}
|
||||||
|
|
||||||
|
var image GlanceV2Image
|
||||||
|
image.ContainerFormat = "bare"
|
||||||
|
image.DiskFormat = fileExtension
|
||||||
|
if name == "" {
|
||||||
|
image.Name = fileName
|
||||||
|
} else {
|
||||||
|
image.Name = name
|
||||||
|
}
|
||||||
|
|
||||||
|
payloadBytes, err := json.Marshal(image)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error building JSON: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
body := bytes.NewReader(payloadBytes)
|
||||||
|
req, err := http.NewRequest("POST", fmt.Sprintf("%s/v2/images", glanceAddress), body)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error Creating HTTP Request:%v", err)
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("X-Auth-Token", token)
|
||||||
|
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Return Code: %s Error:%v", resp.Status, err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
var responseJSON GlanceV2ImageResponse
|
||||||
|
if resp.StatusCode == 201 { // OK
|
||||||
|
bodyBytes, readErr := ioutil.ReadAll(resp.Body)
|
||||||
|
if readErr != nil {
|
||||||
|
log.Fatalf("%v", readErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(bodyBytes, &responseJSON)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("%v", err)
|
||||||
|
}
|
||||||
|
log.Debugf("New Image ID=> %s", responseJSON.ID)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
message, _ := ioutil.ReadAll(resp.Body)
|
||||||
|
log.Fatalf("Error creating new Image [%s], Error: %s", filePath, string(message))
|
||||||
|
}
|
||||||
|
return responseJSON.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
func uploadOpenStackImage(filePath string, glanceAddress string, token string, imageID string) {
|
||||||
|
|
||||||
|
f, err := os.Open(filePath)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
req, err := http.NewRequest("PUT", fmt.Sprintf("%s/v2/images/%s/file", glanceAddress, imageID), f)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error Creating HTTP Request\n%v", err)
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/octet-stream")
|
||||||
|
req.Header.Set("X-Auth-Token", token)
|
||||||
|
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Return Code: %sError:%v", resp.Status, err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode == 204 { // OK
|
||||||
|
log.Infof("Succesfully uploaded [%s]", filePath)
|
||||||
|
} else {
|
||||||
|
message, _ := ioutil.ReadAll(resp.Body)
|
||||||
|
log.Fatalf("Error uploading [%s] Error:%s", filePath, string(message))
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user