Skip to content

Commit

Permalink
Added in empty implementation of AKSBucketProvider
Browse files Browse the repository at this point in the history
Implemented Container Exists check

Implemented Azure container creation

Added in logic to retrieve storage account keys

Encapsulated the storage account parsing down close the CLI which appears to be the only part that requires them as separate arguments
  • Loading branch information
Chris Mellard committed Jul 18, 2020
1 parent 28c81d9 commit 62bc947
Show file tree
Hide file tree
Showing 3 changed files with 205 additions and 0 deletions.
120 changes: 120 additions & 0 deletions pkg/cloud/aks/aks.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ package aks
import (
b64 "encoding/base64"
"encoding/json"
"fmt"
"github.com/pkg/errors"
"regexp"
"strings"

"github.com/jenkins-x/jx-logging/pkg/log"
Expand All @@ -28,6 +31,10 @@ type acr struct {
Name string `json:"name"`
}

type containerExists struct {
Exists bool `json:"exists"`
}

type password struct {
Name string `json:"name"`
Value string `json:"value"`
Expand All @@ -46,6 +53,16 @@ type config struct {
Auths map[string]*auth `json:"auths,omitempty"`
}

type AzureStorage interface {
ContainerExists(bucketUrl string) (bool, error)
CreateContainer(bucketUrl string) error
GetStorageAccessKey(storageAccount string) (string, error)
}

var (
azureContainerUriRegExp = regexp.MustCompile(`https://(?P<first>\w+)\.blob\.core\.windows\.net/(?P<second>\w+)`)
)

// NewAzureRunnerWithCommander specific the command runner for Azure CLI.
func NewAzureRunnerWithCommander(runner util.Commander) *AzureRunner {
return &AzureRunner{
Expand Down Expand Up @@ -242,3 +259,106 @@ func (az *AzureRunner) azureCLI(args ...string) (string, error) {
az.Runner.SetArgs(args)
return az.Runner.RunWithoutRetry()
}

func parseContainerUrl(bucketUrl string) (string, string, error) {
match := azureContainerUriRegExp.FindStringSubmatch(bucketUrl)
if len(match) == 3 {
return match[1], match[2], nil
}
return "", "", errors.New(fmt.Sprintf("Azure Blob Container Url %s could not be parsed to determine storage account and container name", bucketUrl))
}

// ContainerExists checks if an Azure Storage Container exists
func (az *AzureRunner) ContainerExists(bucketUrl string) (bool, error) {
storageAccount, bucketName, err := parseContainerUrl(bucketUrl)
if err != nil {
return false, err
}

accessKey, err := az.GetStorageAccessKey(storageAccount)
if err != nil {
return false, err
}

bucketExistsArgs := []string{
"storage",
"container",
"exists",
"-n",
bucketName,
"--account-name",
storageAccount,
"--account-key",
accessKey,
}

cmdResult, err := az.azureCLI(bucketExistsArgs...)

if err != nil {
log.Logger().Infof("Error checking bucket exists: %s, %s", cmdResult, err)
return false, err
}

containerExists := containerExists{}
err = json.Unmarshal([]byte(cmdResult), &containerExists)
if err != nil {
return false, errors.Wrap(err, "unmarshalling Azure container exists command")
}
return containerExists.Exists, nil

}

func (az *AzureRunner) CreateContainer(bucketUrl string) error {
storageAccount, bucketName, err := parseContainerUrl(bucketUrl)
if err != nil {
return err
}

accessKey, err := az.GetStorageAccessKey(storageAccount)
if err != nil {
return err
}

createContainerArgs := []string{
"storage",
"container",
"create",
"-n",
bucketName,
"--account-name",
storageAccount,
"--fail-on-exist",
"--account-key",
accessKey,
}

cmdResult, err := az.azureCLI(createContainerArgs...)

if err != nil {
log.Logger().Infof("Error creating bucket: %s, %s", cmdResult, err)
return err
}

return nil
}

func (az *AzureRunner) GetStorageAccessKey(storageAccount string) (string, error) {
getStorageAccessKeyArgs := []string{
"storage",
"account",
"keys",
"list",
"-n",
storageAccount,
"--query",
"[?keyName=='key1'].value | [0]",
}

cmdResult, err := az.azureCLI(getStorageAccessKeyArgs...)

if err != nil {
return "", err
}

return cmdResult, nil
}
82 changes: 82 additions & 0 deletions pkg/cloud/aks/storage/bucket_provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package storage

import (
"fmt"
"github.com/jenkins-x/jx-logging/pkg/log"
"github.com/jenkins-x/jx/v2/pkg/cloud/aks"
"github.com/jenkins-x/jx/v2/pkg/cloud/buckets"
"github.com/jenkins-x/jx/v2/pkg/config"
"github.com/jenkins-x/jx/v2/pkg/util"
"github.com/pkg/errors"
uuid "github.com/satori/go.uuid"
"io"
"strings"
)

type AKSBucketProvider struct {
Requirements *config.RequirementsConfig
AzureStorage aks.AzureStorage
}

func (b *AKSBucketProvider) CreateNewBucketForCluster(clusterName string, bucketKind string) (string, error) {
uuid4, _ := uuid.NewV4()
bucketName := fmt.Sprintf("%s-%s-%s", clusterName, bucketKind, uuid4.String())

// Max length is 63, https://docs.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata
if len(bucketName) > 63 {
bucketName = bucketName[:63]
}
bucketName = strings.TrimRight(bucketName, "-")
bucketURL := fmt.Sprintf("https://%s.blob.core.windows.net/%s", b.Requirements.Velero.ServiceAccount, bucketName)
err := b.EnsureBucketIsCreated(bucketURL)
if err != nil {
return bucketURL, errors.Wrapf(err, "failed to create bucket %s", bucketURL)
}

return bucketURL, nil
}

func (b *AKSBucketProvider) EnsureBucketIsCreated(bucketURL string) error {

exists, err := b.AzureStorage.ContainerExists(bucketURL)
if err != nil {
return errors.Wrap(err, "checking if the provided container exists")
}
if exists {
return nil
}

log.Logger().Infof("The bucket %s does not exist so lets create it", util.ColorInfo(bucketURL))
err = b.AzureStorage.CreateContainer(bucketURL)
if err != nil {
return errors.Wrapf(err, "there was a problem creating the bucket with URL %s",
bucketURL)
}
return nil
}

func (b *AKSBucketProvider) UploadFileToBucket(r io.Reader, outputName string, bucketURL string) (string, error) {
return "", nil
}

func (b *AKSBucketProvider) DownloadFileFromBucket(bucketURL string) (io.ReadCloser, error) {
return closerReader{}, nil
}

func NewAKSBucketProvider(requirements *config.RequirementsConfig) buckets.Provider {
return &AKSBucketProvider{
Requirements: requirements,
AzureStorage: aks.NewAzureRunner(),
}
}

type closerReader struct {
}

func (closerReader) Close() error {
return nil
}

func (closerReader) Read(p []byte) (n int, err error) {
return 0, nil
}
3 changes: 3 additions & 0 deletions pkg/cloud/factory/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
v1 "github.com/jenkins-x/jx-api/pkg/apis/jenkins.io/v1"
"github.com/jenkins-x/jx-logging/pkg/log"
"github.com/jenkins-x/jx/v2/pkg/cloud"
aksStorage "github.com/jenkins-x/jx/v2/pkg/cloud/aks/storage"
amazonStorage "github.com/jenkins-x/jx/v2/pkg/cloud/amazon/storage"
"github.com/jenkins-x/jx/v2/pkg/cloud/buckets"
"github.com/jenkins-x/jx/v2/pkg/cloud/gke/storage"
Expand All @@ -23,6 +24,8 @@ func NewBucketProvider(requirements *config.RequirementsConfig) buckets.Provider
fallthrough
case cloud.AWS:
return amazonStorage.NewAmazonBucketProvider(requirements)
case cloud.AKS:
return aksStorage.NewAKSBucketProvider(requirements)
default:
// we have an implementation for GKE / EKS but not for AKS so we should fall back to default
// but we don't have every func implemented
Expand Down

0 comments on commit 62bc947

Please sign in to comment.