Browse Source

Separate conversion code into own package

Djordje Lukic 5 years ago
parent
commit
5aa31b6bf5
3 changed files with 242 additions and 224 deletions
  1. 11 221
      azure/aci.go
  2. 8 3
      azure/backend.go
  3. 223 0
      azure/convert/convert.go

+ 11 - 221
azure/aci.go

@@ -3,21 +3,15 @@ package azure
 import (
 	"bufio"
 	"context"
-	"encoding/base64"
-	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net/http"
 	"os"
 	"os/signal"
 	"runtime"
 	"strings"
 
-	"github.com/compose-spec/compose-go/types"
-	"github.com/docker/api/compose"
 	"github.com/docker/api/context/store"
-	"github.com/sirupsen/logrus"
 
 	"github.com/gobwas/ws"
 	"github.com/gobwas/ws/wsutil"
@@ -30,25 +24,17 @@ import (
 	tm "github.com/buger/goterm"
 )
 
-const (
-	AzureFileDriverName            = "azure_file"
-	VolumeDriveroptsShareNameKey   = "share_name"
-	VolumeDriveroptsAccountNameKey = "storage_account_name"
-	VolumeDriveroptsAccountKeyKey  = "storage_account_key"
-)
-const singleContainerName = "single--container--aci"
+func init() {
+	// required to get auth.NewAuthorizerFromCLI() work, otherwise getting "The access token has been obtained for wrong audience or resource 'https://vault.azure.net'."
+	_ = os.Setenv("AZURE_KEYVAULT_RESOURCE", "https://management.azure.com")
+}
 
-func CreateACIContainers(ctx context.Context, project compose.Project, aciContext store.AciContext) (c containerinstance.ContainerGroup, err error) {
+func CreateACIContainers(ctx context.Context, aciContext store.AciContext, groupDefinition containerinstance.ContainerGroup) (c containerinstance.ContainerGroup, err error) {
 	containerGroupsClient, err := getContainerGroupsClient(aciContext.SubscriptionID)
 	if err != nil {
 		return c, fmt.Errorf("cannot get container group client: %v", err)
 	}
 
-	groupDefinition, err := convert(project, aciContext)
-	if err != nil {
-		return c, err
-	}
-
 	// Check if the container group already exists
 	_, err = containerGroupsClient.Get(ctx, aciContext.ResourceGroup, *groupDefinition.Name)
 	if err != nil {
@@ -83,14 +69,16 @@ func CreateACIContainers(ctx context.Context, project compose.Project, aciContex
 		return c, err
 	}
 
-	if len(project.Services) > 1 {
+	if len(*containerGroup.Containers) > 1 {
 		var commands []string
-		for _, service := range project.Services {
-			commands = append(commands, fmt.Sprintf("echo 127.0.0.1 %s >> /etc/hosts", service.Name))
+		for _, container := range *containerGroup.Containers {
+			commands = append(commands, fmt.Sprintf("echo 127.0.0.1 %s >> /etc/hosts", *container.Name))
 		}
 		commands = append(commands, "exit")
 
-		response, err := ExecACIContainer(ctx, "/bin/sh", project.Name, project.Services[0].Name, aciContext)
+		containers := *containerGroup.Containers
+		container := containers[0]
+		response, err := ExecACIContainer(ctx, "/bin/sh", *containerGroup.Name, *container.Name, aciContext)
 		if err != nil {
 			return c, err
 		}
@@ -109,135 +97,6 @@ func CreateACIContainers(ctx context.Context, project compose.Project, aciContex
 	return containerGroup, err
 }
 
-type ProjectAciHelper compose.Project
-
-func (p ProjectAciHelper) getAciSecretVolumes() ([]containerinstance.Volume, error) {
-	var secretVolumes []containerinstance.Volume
-	for secretName, filepathToRead := range p.Secrets {
-		var data []byte
-		if strings.HasPrefix(filepathToRead.File, compose.SecretInlineMark) {
-			data = []byte(filepathToRead.File[len(compose.SecretInlineMark):])
-		} else {
-			var err error
-			data, err = ioutil.ReadFile(filepathToRead.File)
-			if err != nil {
-				return secretVolumes, err
-			}
-		}
-		if len(data) == 0 {
-			continue
-		}
-		dataStr := base64.StdEncoding.EncodeToString(data)
-		secretVolumes = append(secretVolumes, containerinstance.Volume{
-			Name: to.StringPtr(secretName),
-			Secret: map[string]*string{
-				secretName: &dataStr,
-			},
-		})
-	}
-	return secretVolumes, nil
-}
-
-func (p ProjectAciHelper) getAciFileVolumes() (map[string]bool, []containerinstance.Volume, error) {
-	azureFileVolumesMap := make(map[string]bool, len(p.Volumes))
-	var azureFileVolumesSlice []containerinstance.Volume
-	for name, v := range p.Volumes {
-		if v.Driver == AzureFileDriverName {
-			shareName, ok := v.DriverOpts[VolumeDriveroptsShareNameKey]
-			if !ok {
-				return nil, nil, fmt.Errorf("cannot retrieve share name for Azurefile")
-			}
-			accountName, ok := v.DriverOpts[VolumeDriveroptsAccountNameKey]
-			if !ok {
-				return nil, nil, fmt.Errorf("cannot retrieve account name for Azurefile")
-			}
-			accountKey, ok := v.DriverOpts[VolumeDriveroptsAccountKeyKey]
-			if !ok {
-				return nil, nil, fmt.Errorf("cannot retrieve account key for Azurefile")
-			}
-			aciVolume := containerinstance.Volume{
-				Name: to.StringPtr(name),
-				AzureFile: &containerinstance.AzureFileVolume{
-					ShareName:          to.StringPtr(shareName),
-					StorageAccountName: to.StringPtr(accountName),
-					StorageAccountKey:  to.StringPtr(accountKey),
-				},
-			}
-			azureFileVolumesMap[name] = true
-			azureFileVolumesSlice = append(azureFileVolumesSlice, aciVolume)
-		}
-	}
-	return azureFileVolumesMap, azureFileVolumesSlice, nil
-}
-
-type ServiceConfigAciHelper types.ServiceConfig
-
-func (s ServiceConfigAciHelper) getAciFileVolumeMounts(volumesCache map[string]bool) ([]containerinstance.VolumeMount, error) {
-	var aciServiceVolumes []containerinstance.VolumeMount
-	for _, sv := range s.Volumes {
-		if !volumesCache[sv.Source] {
-			return []containerinstance.VolumeMount{}, fmt.Errorf("could not find volume source %q", sv.Source)
-		}
-		aciServiceVolumes = append(aciServiceVolumes, containerinstance.VolumeMount{
-			Name:      to.StringPtr(sv.Source),
-			MountPath: to.StringPtr(sv.Target),
-		})
-	}
-	return aciServiceVolumes, nil
-}
-
-func (s ServiceConfigAciHelper) getAciSecretsVolumeMounts() []containerinstance.VolumeMount {
-	var secretVolumeMounts []containerinstance.VolumeMount
-	for _, secret := range s.Secrets {
-		secretsMountPath := "/run/secrets"
-		if secret.Target == "" {
-			secret.Target = secret.Source
-		}
-		// Specifically use "/" here and not filepath.Join() to avoid windows path being sent and used inside containers
-		secretsMountPath = secretsMountPath + "/" + secret.Target
-		vmName := strings.Split(secret.Source, "=")[0]
-		vm := containerinstance.VolumeMount{
-			Name:      to.StringPtr(vmName),
-			MountPath: to.StringPtr(secretsMountPath),
-			ReadOnly:  to.BoolPtr(true), // TODO Confirm if the secrets are read only
-		}
-		secretVolumeMounts = append(secretVolumeMounts, vm)
-	}
-	return secretVolumeMounts
-}
-
-func (s ServiceConfigAciHelper) getAciContainer(volumesCache map[string]bool) (containerinstance.Container, error) {
-	secretVolumeMounts := s.getAciSecretsVolumeMounts()
-	aciServiceVolumes, err := s.getAciFileVolumeMounts(volumesCache)
-	if err != nil {
-		return containerinstance.Container{}, err
-	}
-	allVolumes := append(aciServiceVolumes, secretVolumeMounts...)
-	var volumes *[]containerinstance.VolumeMount
-	if len(allVolumes) == 0 {
-		volumes = nil
-	} else {
-		volumes = &allVolumes
-	}
-	return containerinstance.Container{
-		Name: to.StringPtr(s.Name),
-		ContainerProperties: &containerinstance.ContainerProperties{
-			Image: to.StringPtr(s.Image),
-			Resources: &containerinstance.ResourceRequirements{
-				Limits: &containerinstance.ResourceLimits{
-					MemoryInGB: to.Float64Ptr(1),
-					CPU:        to.Float64Ptr(1),
-				},
-				Requests: &containerinstance.ResourceRequests{
-					MemoryInGB: to.Float64Ptr(1),
-					CPU:        to.Float64Ptr(1),
-				},
-			},
-			VolumeMounts: volumes,
-		},
-	}, nil
-}
-
 // ListACIContainers List available containers
 func ListACIContainers(aciContext store.AciContext) (c []containerinstance.ContainerGroup, err error) {
 	ctx := context.TODO()
@@ -361,75 +220,6 @@ func ExecWebSocketLoopWithCmd(ctx context.Context, wsURL, passwd string, command
 	}
 }
 
-func convert(p compose.Project, aciContext store.AciContext) (containerinstance.ContainerGroup, error) {
-	project := ProjectAciHelper(p)
-	containerGroupName := strings.ToLower(project.Name)
-	volumesCache, volumesSlice, err := project.getAciFileVolumes()
-	if err != nil {
-		return containerinstance.ContainerGroup{}, err
-	}
-	secretVolumes, err := project.getAciSecretVolumes()
-	if err != nil {
-		return containerinstance.ContainerGroup{}, err
-	}
-	allVolumes := append(volumesSlice, secretVolumes...)
-	var volumes *[]containerinstance.Volume
-	if len(allVolumes) == 0 {
-		volumes = nil
-	} else {
-		volumes = &allVolumes
-	}
-	var containers []containerinstance.Container
-	groupDefinition := containerinstance.ContainerGroup{
-		Name:     &containerGroupName,
-		Location: &aciContext.Location,
-		ContainerGroupProperties: &containerinstance.ContainerGroupProperties{
-			OsType:     containerinstance.Linux,
-			Containers: &containers,
-			Volumes:    volumes,
-		},
-	}
-
-	for _, s := range project.Services {
-		service := ServiceConfigAciHelper(s)
-		if s.Name != singleContainerName {
-			logrus.Debugf("Adding %q\n", service.Name)
-		}
-		containerDefinition, err := service.getAciContainer(volumesCache)
-		if err != nil {
-			return containerinstance.ContainerGroup{}, err
-		}
-		if service.Ports != nil {
-			var containerPorts []containerinstance.ContainerPort
-			var groupPorts []containerinstance.Port
-			for _, portConfig := range service.Ports {
-				if portConfig.Published != 0 && portConfig.Published != portConfig.Target {
-					msg := fmt.Sprintf("Port mapping is not supported with ACI, cannot map port %d to %d for container %s",
-						portConfig.Published, portConfig.Target, service.Name)
-					return groupDefinition, errors.New(msg)
-				}
-				portNumber := int32(portConfig.Target)
-				containerPorts = append(containerPorts, containerinstance.ContainerPort{
-					Port: to.Int32Ptr(portNumber),
-				})
-				groupPorts = append(groupPorts, containerinstance.Port{
-					Port:     to.Int32Ptr(portNumber),
-					Protocol: containerinstance.TCP,
-				})
-			}
-			containerDefinition.ContainerProperties.Ports = &containerPorts
-			groupDefinition.ContainerGroupProperties.IPAddress = &containerinstance.IPAddress{
-				Type:  containerinstance.Public,
-				Ports: &groupPorts,
-			}
-		}
-
-		containers = append(containers, containerDefinition)
-	}
-	groupDefinition.ContainerGroupProperties.Containers = &containers
-	return groupDefinition, nil
-}
-
 func cleanLastCommand(lastCommandLen int) {
 	tm.MoveCursorUp(1)
 	tm.MoveCursorForward(lastCommandLen)

+ 8 - 3
azure/backend.go

@@ -9,12 +9,12 @@ import (
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 
-	"github.com/docker/api/context/store"
-
+	"github.com/docker/api/azure/convert"
 	"github.com/docker/api/backend"
 	"github.com/docker/api/compose"
 	"github.com/docker/api/containers"
 	apicontext "github.com/docker/api/context"
+	"github.com/docker/api/context/store"
 )
 
 type containerService struct {
@@ -102,6 +102,11 @@ func (cs *containerService) Run(ctx context.Context, r containers.ContainerConfi
 	}
 
 	logrus.Debugf("Running container %q with name %q\n", r.Image, r.ID)
-	_, err := CreateACIContainers(ctx, project, cs.ctx)
+	groupDefinition, err := convert.ToContainerGroup(cs.ctx, project)
+	if err != nil {
+		return err
+	}
+
+	_, err = CreateACIContainers(ctx, cs.ctx, groupDefinition)
 	return err
 }

+ 223 - 0
azure/convert/convert.go

@@ -0,0 +1,223 @@
+package convert
+
+import (
+	"encoding/base64"
+	"errors"
+	"fmt"
+	"io/ioutil"
+	"strings"
+
+	"github.com/Azure/azure-sdk-for-go/profiles/latest/containerinstance/mgmt/containerinstance"
+	"github.com/Azure/go-autorest/autorest/to"
+	"github.com/compose-spec/compose-go/types"
+	"github.com/docker/api/compose"
+	"github.com/docker/api/context/store"
+	"github.com/sirupsen/logrus"
+)
+
+const (
+	azureFileDriverName            = "azure_file"
+	volumeDriveroptsShareNameKey   = "share_name"
+	volumeDriveroptsAccountNameKey = "storage_account_name"
+	volumeDriveroptsAccountKeyKey  = "storage_account_key"
+	singleContainerName            = "single--container--aci"
+)
+
+func ToContainerGroup(aciContext store.AciContext, p compose.Project) (containerinstance.ContainerGroup, error) {
+	project := projectAciHelper(p)
+	containerGroupName := strings.ToLower(project.Name)
+	volumesCache, volumesSlice, err := project.getAciFileVolumes()
+	if err != nil {
+		return containerinstance.ContainerGroup{}, err
+	}
+	secretVolumes, err := project.getAciSecretVolumes()
+	if err != nil {
+		return containerinstance.ContainerGroup{}, err
+	}
+	allVolumes := append(volumesSlice, secretVolumes...)
+	var volumes *[]containerinstance.Volume
+	if len(allVolumes) == 0 {
+		volumes = nil
+	} else {
+		volumes = &allVolumes
+	}
+	var containers []containerinstance.Container
+	groupDefinition := containerinstance.ContainerGroup{
+		Name:     &containerGroupName,
+		Location: &aciContext.Location,
+		ContainerGroupProperties: &containerinstance.ContainerGroupProperties{
+			OsType:     containerinstance.Linux,
+			Containers: &containers,
+			Volumes:    volumes,
+		},
+	}
+
+	for _, s := range project.Services {
+		service := serviceConfigAciHelper(s)
+		if s.Name != singleContainerName {
+			logrus.Debugf("Adding %q\n", service.Name)
+		}
+		containerDefinition, err := service.getAciContainer(volumesCache)
+		if err != nil {
+			return containerinstance.ContainerGroup{}, err
+		}
+		if service.Ports != nil {
+			var containerPorts []containerinstance.ContainerPort
+			var groupPorts []containerinstance.Port
+			for _, portConfig := range service.Ports {
+				if portConfig.Published != 0 && portConfig.Published != portConfig.Target {
+					msg := fmt.Sprintf("Port mapping is not supported with ACI, cannot map port %d to %d for container %s",
+						portConfig.Published, portConfig.Target, service.Name)
+					return groupDefinition, errors.New(msg)
+				}
+				portNumber := int32(portConfig.Target)
+				containerPorts = append(containerPorts, containerinstance.ContainerPort{
+					Port: to.Int32Ptr(portNumber),
+				})
+				groupPorts = append(groupPorts, containerinstance.Port{
+					Port:     to.Int32Ptr(portNumber),
+					Protocol: containerinstance.TCP,
+				})
+			}
+			containerDefinition.ContainerProperties.Ports = &containerPorts
+			groupDefinition.ContainerGroupProperties.IPAddress = &containerinstance.IPAddress{
+				Type:  containerinstance.Public,
+				Ports: &groupPorts,
+			}
+		}
+
+		containers = append(containers, containerDefinition)
+	}
+	groupDefinition.ContainerGroupProperties.Containers = &containers
+	return groupDefinition, nil
+}
+
+type projectAciHelper compose.Project
+
+func (p projectAciHelper) getAciSecretVolumes() ([]containerinstance.Volume, error) {
+	var secretVolumes []containerinstance.Volume
+	for secretName, filepathToRead := range p.Secrets {
+		var data []byte
+		if strings.HasPrefix(filepathToRead.File, compose.SecretInlineMark) {
+			data = []byte(filepathToRead.File[len(compose.SecretInlineMark):])
+		} else {
+			var err error
+			data, err = ioutil.ReadFile(filepathToRead.File)
+			if err != nil {
+				return secretVolumes, err
+			}
+		}
+		if len(data) == 0 {
+			continue
+		}
+		dataStr := base64.StdEncoding.EncodeToString(data)
+		secretVolumes = append(secretVolumes, containerinstance.Volume{
+			Name: to.StringPtr(secretName),
+			Secret: map[string]*string{
+				secretName: &dataStr,
+			},
+		})
+	}
+	return secretVolumes, nil
+}
+
+func (p projectAciHelper) getAciFileVolumes() (map[string]bool, []containerinstance.Volume, error) {
+	azureFileVolumesMap := make(map[string]bool, len(p.Volumes))
+	var azureFileVolumesSlice []containerinstance.Volume
+	for name, v := range p.Volumes {
+		if v.Driver == azureFileDriverName {
+			shareName, ok := v.DriverOpts[volumeDriveroptsShareNameKey]
+			if !ok {
+				return nil, nil, fmt.Errorf("cannot retrieve share name for Azurefile")
+			}
+			accountName, ok := v.DriverOpts[volumeDriveroptsAccountNameKey]
+			if !ok {
+				return nil, nil, fmt.Errorf("cannot retrieve account name for Azurefile")
+			}
+			accountKey, ok := v.DriverOpts[volumeDriveroptsAccountKeyKey]
+			if !ok {
+				return nil, nil, fmt.Errorf("cannot retrieve account key for Azurefile")
+			}
+			aciVolume := containerinstance.Volume{
+				Name: to.StringPtr(name),
+				AzureFile: &containerinstance.AzureFileVolume{
+					ShareName:          to.StringPtr(shareName),
+					StorageAccountName: to.StringPtr(accountName),
+					StorageAccountKey:  to.StringPtr(accountKey),
+				},
+			}
+			azureFileVolumesMap[name] = true
+			azureFileVolumesSlice = append(azureFileVolumesSlice, aciVolume)
+		}
+	}
+	return azureFileVolumesMap, azureFileVolumesSlice, nil
+}
+
+type serviceConfigAciHelper types.ServiceConfig
+
+func (s serviceConfigAciHelper) getAciFileVolumeMounts(volumesCache map[string]bool) ([]containerinstance.VolumeMount, error) {
+	var aciServiceVolumes []containerinstance.VolumeMount
+	for _, sv := range s.Volumes {
+		if !volumesCache[sv.Source] {
+			return []containerinstance.VolumeMount{}, fmt.Errorf("could not find volume source %q", sv.Source)
+		}
+		aciServiceVolumes = append(aciServiceVolumes, containerinstance.VolumeMount{
+			Name:      to.StringPtr(sv.Source),
+			MountPath: to.StringPtr(sv.Target),
+		})
+	}
+	return aciServiceVolumes, nil
+}
+
+func (s serviceConfigAciHelper) getAciSecretsVolumeMounts() []containerinstance.VolumeMount {
+	var secretVolumeMounts []containerinstance.VolumeMount
+	for _, secret := range s.Secrets {
+		secretsMountPath := "/run/secrets"
+		if secret.Target == "" {
+			secret.Target = secret.Source
+		}
+		// Specifically use "/" here and not filepath.Join() to avoid windows path being sent and used inside containers
+		secretsMountPath = secretsMountPath + "/" + secret.Target
+		vmName := strings.Split(secret.Source, "=")[0]
+		vm := containerinstance.VolumeMount{
+			Name:      to.StringPtr(vmName),
+			MountPath: to.StringPtr(secretsMountPath),
+			ReadOnly:  to.BoolPtr(true), // TODO Confirm if the secrets are read only
+		}
+		secretVolumeMounts = append(secretVolumeMounts, vm)
+	}
+	return secretVolumeMounts
+}
+
+func (s serviceConfigAciHelper) getAciContainer(volumesCache map[string]bool) (containerinstance.Container, error) {
+	secretVolumeMounts := s.getAciSecretsVolumeMounts()
+	aciServiceVolumes, err := s.getAciFileVolumeMounts(volumesCache)
+	if err != nil {
+		return containerinstance.Container{}, err
+	}
+	allVolumes := append(aciServiceVolumes, secretVolumeMounts...)
+	var volumes *[]containerinstance.VolumeMount
+	if len(allVolumes) == 0 {
+		volumes = nil
+	} else {
+		volumes = &allVolumes
+	}
+	return containerinstance.Container{
+		Name: to.StringPtr(s.Name),
+		ContainerProperties: &containerinstance.ContainerProperties{
+			Image: to.StringPtr(s.Image),
+			Resources: &containerinstance.ResourceRequirements{
+				Limits: &containerinstance.ResourceLimits{
+					MemoryInGB: to.Float64Ptr(1),
+					CPU:        to.Float64Ptr(1),
+				},
+				Requests: &containerinstance.ResourceRequests{
+					MemoryInGB: to.Float64Ptr(1),
+					CPU:        to.Float64Ptr(1),
+				},
+			},
+			VolumeMounts: volumes,
+		},
+	}, nil
+
+}