mirror of
https://github.com/linuxkit/linuxkit.git
synced 2025-07-20 17:49:10 +00:00
Merge pull request #2255 from thebsdbox/openStack_Push
OpenStack Push support
This commit is contained in:
commit
78e5ddc675
@ -64,6 +64,7 @@ Currently supported platforms are:
|
||||
- [Amazon Web Services](docs/platform-aws.md)
|
||||
- [Google Cloud](docs/platform-gcp.md)
|
||||
- [Microsoft Azure](docs/platform-azure.md)
|
||||
- [OpenStack](docs/platform-openstack.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(" azure\n")
|
||||
fmt.Printf(" gcp\n")
|
||||
fmt.Printf(" openstack\n")
|
||||
fmt.Printf(" vcenter\n")
|
||||
fmt.Printf("\n")
|
||||
fmt.Printf("'options' are the backend specific options.\n")
|
||||
@ -38,6 +39,8 @@ func push(args []string) {
|
||||
pushAzure(args[1:])
|
||||
case "gcp":
|
||||
pushGcp(args[1:])
|
||||
case "openstack":
|
||||
pushOpenstack(args[1:])
|
||||
case "vcenter":
|
||||
pushVCenter(args[1:])
|
||||
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