mirror of https://github.com/docker/compose.git
Add auto creation of Azure volumes during `compose up`
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
This commit is contained in:
parent
181b47fcb5
commit
3fbb9bd864
36
aci/aci.go
36
aci/aci.go
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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),
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
Loading…
Reference in New Issue