Skip to content

Commit

Permalink
initial IBM cloud (#1439)
Browse files Browse the repository at this point in the history
* initial ibm cloud support

* .
  • Loading branch information
eyberg committed Mar 8, 2023
1 parent c5e21c5 commit 024f643
Show file tree
Hide file tree
Showing 7 changed files with 859 additions and 0 deletions.
88 changes: 88 additions & 0 deletions provider/ibm/ibm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package ibm

import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"strings"

"github.com/nanovms/ops/lepton"
"github.com/nanovms/ops/types"
)

// ProviderName of the cloud platform provider
const ProviderName = "ibm"

// IBM Provider to interact with IBM infrastructure
type IBM struct {
Storage *Objects
token string
iam string
}

// NewProvider IBM
func NewProvider() *IBM {
return &IBM{}
}

// Initialize provider
func (v *IBM) Initialize(config *types.ProviderConfig) error {
v.token = os.Getenv("TOKEN")
if v.token == "" {
return fmt.Errorf("TOKEN is not set")
}

v.setIAMToken()
return nil
}

// Token is the return type for a new IAM token.
type Token struct {
AccessToken string `json:"access_token"`
}

func (v *IBM) setIAMToken() {

uri := "https://iam.cloud.ibm.com/oidc/token"

data := url.Values{}
data.Set("apikey", v.token)
data.Set("response_type", "cloud_iam")
data.Set("grant_type", "urn:ibm:params:oauth:grant-type:apikey")

client := &http.Client{}
r, err := http.NewRequest(http.MethodPost, uri, strings.NewReader(data.Encode()))
if err != nil {
fmt.Println(err)
}

r.Header.Add("Content-Type", "application/x-www-form-urlencoded")
r.Header.Add("Accept", "application/json")

res, err := client.Do(r)
if err != nil {
fmt.Println(err)
}
defer res.Body.Close()

body, err := io.ReadAll(res.Body)
if err != nil {
fmt.Println(err)
}

it := &Token{}
err = json.Unmarshal(body, &it)
if err != nil {
fmt.Println(err)
}

v.iam = it.AccessToken
}

// GetStorage returns storage interface for cloud provider
func (v *IBM) GetStorage() lepton.Storage {
return v.Storage
}
248 changes: 248 additions & 0 deletions provider/ibm/ibm_image.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
package ibm

import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"os/exec"
"path/filepath"

"github.com/dustin/go-humanize"
"github.com/nanovms/ops/lepton"
"github.com/nanovms/ops/log"
"github.com/nanovms/ops/types"
"github.com/olekukonko/tablewriter"
)

// BuildImage to be upload on IBM
func (v *IBM) BuildImage(ctx *lepton.Context) (string, error) {
c := ctx.Config()
err := lepton.BuildImage(*c)
if err != nil {
return "", err
}

return v.CustomizeImage(ctx)
}

// BuildImageWithPackage to upload on IBM.
func (v *IBM) BuildImageWithPackage(ctx *lepton.Context, pkgpath string) (string, error) {
c := ctx.Config()
err := lepton.BuildImageFromPackage(pkgpath, *c)
if err != nil {
return "", err
}
return v.CustomizeImage(ctx)
}

func (v *IBM) destroyImage(snapshotid string) {
}

// CreateImage - Creates image on IBM using nanos images
func (v *IBM) CreateImage(ctx *lepton.Context, imagePath string) error {
// also worth gzipping

icow := imagePath + ".qcow2"

args := []string{
"convert", "-f", "raw", "-O", "qcow2", imagePath, icow,
}

cmd := exec.Command("qemu-img", args...)
out, err := cmd.Output()
if err != nil {
fmt.Println(err)
fmt.Println(out)
}

store := &Objects{
token: v.iam,
}

v.Storage = store
err = v.Storage.CopyToBucket(ctx.Config(), icow)
if err != nil {
return err
}

imgName := ctx.Config().CloudConfig.ImageName

v.createImage(ctx, icow, imgName)
return nil
}

func (v *IBM) createImage(ctx *lepton.Context, icow string, imgName string) {
baseName := filepath.Base(icow)

c := ctx.Config()
zone := c.CloudConfig.Zone

region := extractRegionFromZone(zone)

bucket := c.CloudConfig.BucketName

uri := "https://" + region + ".iaas.cloud.ibm.com/v1/images?version=2023-02-16&generation=2"

rgroup := v.getDefaultResourceGroup()

j := `{
"name": "` + imgName + `",
"operating_system": {
"name": "ubuntu-18-04-amd64"
},
"file": {
"href": "cos://` + region + `/` + bucket + `/` + baseName + `"
},
"resource_group": {
"id": "` + rgroup + `"
}
}`

reqBody := []byte(j)

client := &http.Client{}
req, err := http.NewRequest("POST", uri, bytes.NewBuffer(reqBody))
if err != nil {
fmt.Println(err)
}

req.Header.Add("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+v.iam)
req.Header.Add("Accept", "application/json")

res, err := client.Do(req)
if err != nil {
fmt.Println(err)
}
defer res.Body.Close()

body, err := io.ReadAll(res.Body)
if err != nil {
fmt.Println(err)
}

fmt.Println(string(body))
}

// ImageListResponse is the set of instances available from IBM in an
// images list call.
type ImageListResponse struct {
Images []Image `json:"images"`
}

// Image represents a given IBM image configuration.
type Image struct {
ID string `json:"id"`
Name string `json:"name"`
Status string `json:"status"`
CreatedAt string `json:"created_at"`
}

// GetImages return all images on IBM
// needs tags added
func (v *IBM) GetImages(ctx *lepton.Context) ([]lepton.CloudImage, error) {
client := &http.Client{}

c := ctx.Config()
zone := c.CloudConfig.Zone

region := extractRegionFromZone(zone)

uri := "https://" + region + ".iaas.cloud.ibm.com/v1/images?version=2023-02-28&generation=2&visibility=private"

req, err := http.NewRequest("GET", uri, nil)
if err != nil {
fmt.Println(err)
}

req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+v.iam)

res, err := client.Do(req)
if err != nil {
fmt.Println(err)
}
defer res.Body.Close()

body, err := io.ReadAll(res.Body)
if err != nil {
fmt.Println(err)
}

ilr := &ImageListResponse{}
err = json.Unmarshal(body, &ilr)
if err != nil {
fmt.Println(err)
}

var images []lepton.CloudImage

for _, img := range ilr.Images {
images = append(images, lepton.CloudImage{
ID: img.ID,
Name: img.Name,
Status: img.Status,
Path: "",
})
}

return images, nil

}

// ListImages lists images on IBM
func (v *IBM) ListImages(ctx *lepton.Context) error {
images, err := v.GetImages(ctx)
if err != nil {
fmt.Println(err)
}

table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"ID", "Name", "Date created", "Size", "Status"})
table.SetHeaderColor(
tablewriter.Colors{tablewriter.Bold, tablewriter.FgCyanColor},
tablewriter.Colors{tablewriter.Bold, tablewriter.FgCyanColor},
tablewriter.Colors{tablewriter.Bold, tablewriter.FgCyanColor},
tablewriter.Colors{tablewriter.Bold, tablewriter.FgCyanColor},
tablewriter.Colors{tablewriter.Bold, tablewriter.FgCyanColor})
table.SetRowLine(true)

for _, image := range images {
var row []string
row = append(row, image.ID)
row = append(row, image.Name)
row = append(row, "")
row = append(row, humanize.Bytes(uint64(image.Size)))
row = append(row, image.Status)
table.Append(row)
}

table.Render()

return nil
}

// DeleteImage deletes image from v
func (v *IBM) DeleteImage(ctx *lepton.Context, snapshotID string) error {
return nil
}

// SyncImage syncs image from provider to another provider
func (v *IBM) SyncImage(config *types.Config, target lepton.Provider, image string) error {
log.Warn("not yet implemented")
return nil
}

// ResizeImage is not supported on IBM.
func (v *IBM) ResizeImage(ctx *lepton.Context, imagename string, hbytes string) error {
return fmt.Errorf("operation not supported")
}

// CustomizeImage returns image path with adaptations needed by cloud provider
func (v *IBM) CustomizeImage(ctx *lepton.Context) (string, error) {
imagePath := ctx.Config().RunConfig.ImageName
return imagePath, nil
}

0 comments on commit 024f643

Please sign in to comment.