2020-06-18 16:13:24 +02:00
/ *
2020-09-22 12:13:00 +02:00
Copyright 2020 Docker Compose CLI authors
2020-06-18 16:13:24 +02:00
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
2020-05-07 04:58:04 +02:00
package convert
import (
2020-10-16 17:40:45 +02:00
"context"
2020-05-07 04:58:04 +02:00
"fmt"
2020-09-14 18:32:38 +02:00
"strconv"
2020-05-07 04:58:04 +02:00
"strings"
2020-11-23 22:15:15 +01:00
"github.com/pkg/errors"
"github.com/Azure/azure-sdk-for-go/services/containerinstance/mgmt/2019-12-01/containerinstance"
2020-10-16 17:40:45 +02:00
"github.com/Azure/go-autorest/autorest/to"
2020-11-23 22:15:15 +01:00
2020-05-07 04:58:04 +02:00
"github.com/compose-spec/compose-go/types"
2020-05-19 15:50:57 +02:00
2020-11-23 22:15:15 +01:00
"github.com/docker/compose-cli/aci/login"
2021-01-15 16:51:31 +01:00
"github.com/docker/compose-cli/api/errdefs"
2020-05-07 04:58:04 +02:00
)
2020-10-16 17:40:45 +02:00
const (
2020-11-04 01:47:07 +01:00
// 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"
2020-10-16 17:40:45 +02:00
volumeReadOnly = "read_only"
)
2020-11-10 16:58:56 +01:00
func ( p projectAciHelper ) getAciFileVolumes ( ctx context . Context , helper login . StorageLogin ) ( [ ] containerinstance . Volume , error ) {
2020-10-16 17:40:45 +02:00
var azureFileVolumesSlice [ ] containerinstance . Volume
for name , v := range p . Volumes {
2020-11-04 01:47:07 +01:00
if v . Driver == AzureFileDriverName {
shareName , ok := v . DriverOpts [ VolumeDriveroptsShareNameKey ]
2020-10-16 17:40:45 +02:00
if ! ok {
2020-11-10 16:58:56 +01:00
return nil , fmt . Errorf ( "cannot retrieve fileshare name for Azurefile" )
2020-10-16 17:40:45 +02:00
}
2020-11-04 01:47:07 +01:00
accountName , ok := v . DriverOpts [ VolumeDriveroptsAccountNameKey ]
2020-10-16 17:40:45 +02:00
if ! ok {
2020-11-10 16:58:56 +01:00
return nil , fmt . Errorf ( "cannot retrieve account name for Azurefile" )
2020-10-16 17:40:45 +02:00
}
readOnly , ok := v . DriverOpts [ volumeReadOnly ]
if ! ok {
readOnly = "false"
}
ro , err := strconv . ParseBool ( readOnly )
if err != nil {
2020-11-10 16:58:56 +01:00
return nil , fmt . Errorf ( "invalid mode %q for volume" , readOnly )
2020-10-16 17:40:45 +02:00
}
accountKey , err := helper . GetAzureStorageAccountKey ( ctx , accountName )
if err != nil {
2020-11-10 16:58:56 +01:00
return nil , err
2020-10-16 17:40:45 +02:00
}
aciVolume := containerinstance . Volume {
Name : to . StringPtr ( name ) ,
AzureFile : & containerinstance . AzureFileVolume {
ShareName : to . StringPtr ( shareName ) ,
StorageAccountName : to . StringPtr ( accountName ) ,
StorageAccountKey : to . StringPtr ( accountKey ) ,
ReadOnly : & ro ,
} ,
}
azureFileVolumesSlice = append ( azureFileVolumesSlice , aciVolume )
}
}
2020-11-10 16:58:56 +01:00
return azureFileVolumesSlice , nil
2020-10-16 17:40:45 +02:00
}
2020-11-10 16:58:56 +01:00
func ( s serviceConfigAciHelper ) getAciFileVolumeMounts ( ) ( [ ] containerinstance . VolumeMount , error ) {
2020-10-16 17:40:45 +02:00
var aciServiceVolumes [ ] containerinstance . VolumeMount
for _ , sv := range s . Volumes {
2020-11-10 16:58:56 +01:00
if sv . Type == string ( types . VolumeTypeBind ) {
return [ ] containerinstance . VolumeMount { } , fmt . Errorf ( "host path (%q) not allowed as volume source, you need to reference an Azure File Share defined in the 'volumes' section" , sv . Source )
2020-10-16 17:40:45 +02:00
}
aciServiceVolumes = append ( aciServiceVolumes , containerinstance . VolumeMount {
Name : to . StringPtr ( sv . Source ) ,
MountPath : to . StringPtr ( sv . Target ) ,
} )
}
return aciServiceVolumes , nil
}
2020-05-07 04:58:04 +02:00
// GetRunVolumes return volume configurations for a project and a single service
// this is meant to be used as a compose project of a single service
func GetRunVolumes ( volumes [ ] string ) ( map [ string ] types . VolumeConfig , [ ] types . ServiceVolumeConfig , error ) {
var serviceConfigVolumes [ ] types . ServiceVolumeConfig
projectVolumes := make ( map [ string ] types . VolumeConfig , len ( volumes ) )
for i , v := range volumes {
var vi volumeInput
err := vi . parse ( fmt . Sprintf ( "volume-%d" , i ) , v )
if err != nil {
return nil , nil , err
}
2020-09-14 18:32:38 +02:00
readOnly := strconv . FormatBool ( vi . readonly )
2020-05-07 04:58:04 +02:00
projectVolumes [ vi . name ] = types . VolumeConfig {
Name : vi . name ,
2020-11-04 01:47:07 +01:00
Driver : AzureFileDriverName ,
2020-05-07 04:58:04 +02:00
DriverOpts : map [ string ] string {
2020-11-04 01:47:07 +01:00
VolumeDriveroptsAccountNameKey : vi . storageAccount ,
VolumeDriveroptsShareNameKey : vi . fileshare ,
2020-09-14 18:32:38 +02:00
volumeReadOnly : readOnly ,
2020-05-07 04:58:04 +02:00
} ,
}
sv := types . ServiceVolumeConfig {
2020-11-04 01:47:07 +01:00
Type : AzureFileDriverName ,
2020-09-14 18:32:38 +02:00
Source : vi . name ,
Target : vi . target ,
ReadOnly : vi . readonly ,
2020-05-07 04:58:04 +02:00
}
serviceConfigVolumes = append ( serviceConfigVolumes , sv )
}
return projectVolumes , serviceConfigVolumes , nil
}
type volumeInput struct {
2020-09-10 16:07:22 +02:00
name string
storageAccount string
fileshare string
target string
2020-09-14 18:32:38 +02:00
readonly bool
2020-05-07 04:58:04 +02:00
}
2020-09-14 18:32:38 +02:00
// parse takes a candidate string and creates a volumeInput
// Candidates take the form of <source>[:<target>][:<permissions>]
// Source is of the form `<storage account>/<fileshare>`
// If only the source is specified then the target is set to `/run/volumes/<fileshare>`
// Target is an absolute path in the container of the form `/path/to/mount`
// Permissions can only be set if the target is set
// If set, permissions must be `rw` or `ro`
func ( v * volumeInput ) parse ( name string , candidate string ) error {
2020-08-13 15:21:09 +02:00
v . name = name
2020-09-14 18:32:38 +02:00
tokens := strings . Split ( candidate , ":" )
sourceTokens := strings . Split ( tokens [ 0 ] , "/" )
if len ( sourceTokens ) != 2 || sourceTokens [ 0 ] == "" {
return errors . Wrapf ( errdefs . ErrParsingFailed , "volume specification %q does not include a storage account before '/'" , candidate )
2020-05-07 04:58:04 +02:00
}
2020-09-10 16:07:22 +02:00
if sourceTokens [ 1 ] == "" {
2020-09-14 18:32:38 +02:00
return errors . Wrapf ( errdefs . ErrParsingFailed , "volume specification %q does not include a storage file fileshare after '/'" , candidate )
2020-05-07 04:58:04 +02:00
}
2020-09-14 18:32:38 +02:00
v . storageAccount = sourceTokens [ 0 ]
2020-09-10 16:07:22 +02:00
v . fileshare = sourceTokens [ 1 ]
2020-09-14 18:32:38 +02:00
switch len ( tokens ) {
case 1 : // source only
2020-09-10 16:07:22 +02:00
v . target = "/run/volumes/" + v . fileshare
2020-09-14 18:32:38 +02:00
case 2 : // source and target
v . target = tokens [ 1 ]
case 3 : // source, target, and permissions
v . target = tokens [ 1 ]
permissions := strings . ToLower ( tokens [ 2 ] )
if permissions != "ro" && permissions != "rw" {
return errors . Wrapf ( errdefs . ErrParsingFailed , "volume specification %q has an invalid mode %q" , candidate , permissions )
}
v . readonly = permissions == "ro"
default :
return errors . Wrapf ( errdefs . ErrParsingFailed , "volume specification %q has invalid format" , candidate )
2020-05-07 04:58:04 +02:00
}
2020-09-14 18:32:38 +02:00
2020-05-07 04:58:04 +02:00
return nil
}