Add auto creation of Azure volumes during `compose up`

Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
This commit is contained in:
Ulysses Souza 2020-11-04 01:47:07 +01:00 committed by Guillaume Tardif
parent 181b47fcb5
commit 3fbb9bd864
6 changed files with 109 additions and 36 deletions

View File

@ -28,6 +28,7 @@ import (
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/to"
tm "github.com/buger/goterm"
"github.com/compose-spec/compose-go/types"
"github.com/gobwas/ws"
"github.com/gobwas/ws/wsutil"
"github.com/morikuni/aec"
@ -35,6 +36,7 @@ import (
"github.com/docker/compose-cli/aci/convert"
"github.com/docker/compose-cli/aci/login"
"github.com/docker/compose-cli/api/client"
"github.com/docker/compose-cli/api/containers"
"github.com/docker/compose-cli/context/store"
"github.com/docker/compose-cli/errdefs"
@ -64,12 +66,46 @@ func createACIContainers(ctx context.Context, aciContext store.AciContext, group
return createOrUpdateACIContainers(ctx, aciContext, groupDefinition)
}
func autocreateFileshares(ctx context.Context, project *types.Project) error {
clt, err := client.New(ctx)
if err != nil {
return err
}
for _, v := range project.Volumes {
if v.Driver != convert.AzureFileDriverName {
return fmt.Errorf("cannot use ACI volume, required driver is %q, found %q", convert.AzureFileDriverName, v.Driver)
}
shareName, ok := v.DriverOpts[convert.VolumeDriveroptsShareNameKey]
if !ok {
return fmt.Errorf("cannot retrieve fileshare name for Azure file share")
}
accountName, ok := v.DriverOpts[convert.VolumeDriveroptsAccountNameKey]
if !ok {
return fmt.Errorf("cannot retrieve account name for Azure file share")
}
_, err = clt.VolumeService().Inspect(ctx, fmt.Sprintf("%s/%s", accountName, shareName))
if err != nil { // Not found, autocreate fileshare
if !errdefs.IsNotFoundError(err) {
return err
}
aciVolumeOpts := &VolumeCreateOptions{
Account: accountName,
}
if _, err = clt.VolumeService().Create(ctx, shareName, aciVolumeOpts); err != nil {
return err
}
}
}
return nil
}
func createOrUpdateACIContainers(ctx context.Context, aciContext store.AciContext, groupDefinition containerinstance.ContainerGroup) error {
w := progress.ContextWriter(ctx)
containerGroupsClient, err := login.NewContainerGroupsClient(aciContext.SubscriptionID)
if err != nil {
return errors.Wrapf(err, "cannot get container group client")
}
groupDisplay := "Group " + *groupDefinition.Name
w.Event(progress.Event{
ID: groupDisplay,

View File

@ -46,18 +46,49 @@ func newComposeService(ctx store.AciContext) aciComposeService {
func (cs *aciComposeService) Up(ctx context.Context, project *types.Project, detach bool) error {
logrus.Debugf("Up on project with name %q", project.Name)
groupDefinition, err := convert.ToContainerGroup(ctx, cs.ctx, *project, cs.storageLogin)
addTag(&groupDefinition, composeContainerTag)
if err := autocreateFileshares(ctx, project); err != nil {
return err
}
groupDefinition, err := convert.ToContainerGroup(ctx, cs.ctx, *project, cs.storageLogin)
if err != nil {
return err
}
addTag(&groupDefinition, composeContainerTag)
return createOrUpdateACIContainers(ctx, cs.ctx, groupDefinition)
}
func (cs aciComposeService) warnKeepVolumeOnDown(ctx context.Context, projectName string) error {
cgClient, err := login.NewContainerGroupsClient(cs.ctx.SubscriptionID)
if err != nil {
return err
}
cg, err := cgClient.Get(ctx, cs.ctx.ResourceGroup, projectName)
if err != nil {
return err
}
if cg.Volumes == nil {
return nil
}
for _, v := range *cg.Volumes {
if v.AzureFile == nil || v.AzureFile.StorageAccountName == nil || v.AzureFile.ShareName == nil {
continue
}
fmt.Printf("WARNING: fileshare \"%s/%s\" will NOT be deleted. Use 'docker volume rm' if you want to delete this volume\n",
*v.AzureFile.StorageAccountName, *v.AzureFile.ShareName)
}
return nil
}
func (cs *aciComposeService) Down(ctx context.Context, project string) error {
logrus.Debugf("Down on project with name %q", project)
if err := cs.warnKeepVolumeOnDown(ctx, project); err != nil {
return err
}
cg, err := deleteACIContainerGroup(ctx, cs.ctx, project)
if err != nil {
return err

View File

@ -32,21 +32,24 @@ import (
)
const (
azureFileDriverName = "azure_file"
volumeDriveroptsShareNameKey = "share_name"
volumeDriveroptsAccountNameKey = "storage_account_name"
// AzureFileDriverName driver name for azure file share
AzureFileDriverName = "azure_file"
// VolumeDriveroptsShareNameKey driver opt for fileshare name
VolumeDriveroptsShareNameKey = "share_name"
// VolumeDriveroptsAccountNameKey driver opt for storage account
VolumeDriveroptsAccountNameKey = "storage_account_name"
volumeReadOnly = "read_only"
)
func (p projectAciHelper) getAciFileVolumes(ctx context.Context, helper login.StorageLogin) ([]containerinstance.Volume, error) {
var azureFileVolumesSlice []containerinstance.Volume
for name, v := range p.Volumes {
if v.Driver == azureFileDriverName {
shareName, ok := v.DriverOpts[volumeDriveroptsShareNameKey]
if v.Driver == AzureFileDriverName {
shareName, ok := v.DriverOpts[VolumeDriveroptsShareNameKey]
if !ok {
return nil, fmt.Errorf("cannot retrieve fileshare name for Azurefile")
}
accountName, ok := v.DriverOpts[volumeDriveroptsAccountNameKey]
accountName, ok := v.DriverOpts[VolumeDriveroptsAccountNameKey]
if !ok {
return nil, fmt.Errorf("cannot retrieve account name for Azurefile")
}
@ -105,15 +108,15 @@ func GetRunVolumes(volumes []string) (map[string]types.VolumeConfig, []types.Ser
readOnly := strconv.FormatBool(vi.readonly)
projectVolumes[vi.name] = types.VolumeConfig{
Name: vi.name,
Driver: azureFileDriverName,
Driver: AzureFileDriverName,
DriverOpts: map[string]string{
volumeDriveroptsAccountNameKey: vi.storageAccount,
volumeDriveroptsShareNameKey: vi.fileshare,
VolumeDriveroptsAccountNameKey: vi.storageAccount,
VolumeDriveroptsShareNameKey: vi.fileshare,
volumeReadOnly: readOnly,
},
}
sv := types.ServiceVolumeConfig{
Type: azureFileDriverName,
Type: AzureFileDriverName,
Source: vi.name,
Target: vi.target,
ReadOnly: vi.readonly,

View File

@ -207,8 +207,8 @@ func getAzurefileVolumeConfig(name string, accountNameKey string, shareNameKey s
Name: name,
Driver: "azure_file",
DriverOpts: map[string]string{
volumeDriveroptsAccountNameKey: accountNameKey,
volumeDriveroptsShareNameKey: shareNameKey,
VolumeDriveroptsAccountNameKey: accountNameKey,
VolumeDriveroptsShareNameKey: shareNameKey,
volumeReadOnly: strconv.FormatBool(readOnly),
},
}

View File

@ -232,7 +232,7 @@ func (cs *aciVolumeService) Delete(ctx context.Context, id string, options inter
}
result, err := fileShareClient.Delete(ctx, cs.aciContext.ResourceGroup, storageAccount, fileshare)
if result.StatusCode == 204 {
if result.HasHTTPStatus(http.StatusNoContent) {
return errors.Wrapf(errdefs.ErrNotFound, "fileshare %q", fileshare)
}
return err
@ -247,8 +247,11 @@ func (cs *aciVolumeService) Inspect(ctx context.Context, id string) (volumes.Vol
if err != nil {
return volumes.Volume{}, err
}
_, err = fileShareClient.Get(ctx, cs.aciContext.ResourceGroup, storageAccount, fileshareName, "")
res, err := fileShareClient.Get(ctx, cs.aciContext.ResourceGroup, storageAccount, fileshareName, "")
if err != nil { // Just checks if it exists
if res.HasHTTPStatus(http.StatusNotFound) {
return volumes.Volume{}, errors.Wrapf(errdefs.ErrNotFound, "account %q, file share %q. Original message %s", storageAccount, fileshareName, err.Error())
}
return volumes.Volume{}, err
}
return toVolume(storageAccount, fileshareName), nil

View File

@ -644,9 +644,7 @@ func TestUpUpdate(t *testing.T) {
)
c := NewParallelE2eCLI(t, binDir)
sID, groupID, location := setupTestResourceGroup(t, c)
composeAccountName := groupID + "-sa"
composeAccountName = strings.ReplaceAll(composeAccountName, "-", "")
composeAccountName = strings.ToLower(composeAccountName)
composeAccountName := strings.ToLower(strings.ReplaceAll(groupID, "-", "") + "sa")
dstDir := filepath.Join(os.TempDir(), "e2e-aci-volume-"+composeAccountName)
srcDir := filepath.Join("..", "composefiles", "aci-demo")
@ -663,28 +661,33 @@ func TestUpUpdate(t *testing.T) {
multiPortComposefile = filepath.Join(dstDir, multiPortComposefile)
volumeID := composeAccountName + "/" + fileshareName
const (
testFileName = "msg.txt"
testFileContent = "VOLUME_OK"
projectName = "acidemo"
)
var (
dnsLabelName = "nginx-" + groupID
fqdn = dnsLabelName + "." + location + ".azurecontainer.io"
)
t.Run("compose up", func(t *testing.T) {
const (
testFileName = "msg.txt"
testFileContent = "VOLUME_OK"
projectName = "acidemo"
)
c.RunDockerCmd("volume", "create", "--storage-account", composeAccountName, fileshareName)
// Bootstrap volume
aciContext := store.AciContext{
SubscriptionID: sID,
Location: location,
ResourceGroup: groupID,
}
uploadTestFile(t, aciContext, composeAccountName, fileshareName, testFileName, testFileContent)
dnsLabelName := "nginx-" + groupID
fqdn := dnsLabelName + "." + location + ".azurecontainer.io"
// Name of Compose project is taken from current folder "acie2e"
c.RunDockerCmd("compose", "up", "-f", singlePortVolumesComposefile, "--domainname", dnsLabelName, "--project-name", projectName)
// Volume should be autocreated by the "compose up"
uploadTestFile(t, aciContext, composeAccountName, fileshareName, testFileName, testFileContent)
})
t.Cleanup(func() {
c.RunDockerCmd("volume", "rm", volumeID)
})
t.Run("check deployed compose app", func(t *testing.T) {
res := c.RunDockerCmd("ps")
out := lines(res.Stdout())
// Check three containers are running
@ -723,9 +726,6 @@ func TestUpUpdate(t *testing.T) {
composeAccountName, fileshareName, projectName),
})
})
t.Cleanup(func() {
c.RunDockerCmd("volume", "rm", volumeID)
})
t.Run("compose ps", func(t *testing.T) {
res := c.RunDockerCmd("compose", "ps", "--project-name", composeProjectName, "--quiet")