فهرست منبع

Add volumes to run command

Signed-off-by: Ulysses Souza <[email protected]>
Ulysses Souza 5 سال پیش
والد
کامیت
b25a6b4bd6
6فایلهای تغییر یافته به همراه183 افزوده شده و 8 حذف شده
  1. 12 4
      azure/backend.go
  2. 110 0
      azure/convert/volume.go
  3. 54 0
      cli/cmd/run/opts.go
  4. 3 3
      cli/cmd/run/run.go
  5. 1 0
      cli/options/run/opts.go
  6. 3 1
      containers/api.go

+ 12 - 4
azure/backend.go

@@ -155,17 +155,25 @@ func (cs *aciContainerService) Run(ctx context.Context, r containers.ContainerCo
 			Published: p.HostPort,
 		})
 	}
+
+	projectVolumes, serviceConfigVolumes, err := convert.GetRunVolumes(r.Volumes)
+	if err != nil {
+		return err
+	}
+
 	project := compose.Project{
 		Name: r.ID,
 		Config: types.Config{
 			Services: []types.ServiceConfig{
 				{
-					Name:   singleContainerName,
-					Image:  r.Image,
-					Ports:  ports,
-					Labels: r.Labels,
+					Name:    singleContainerName,
+					Image:   r.Image,
+					Ports:   ports,
+					Labels:  r.Labels,
+					Volumes: serviceConfigVolumes,
 				},
 			},
+			Volumes: projectVolumes,
 		},
 	}
 

+ 110 - 0
azure/convert/volume.go

@@ -0,0 +1,110 @@
+package convert
+
+import (
+	"errors"
+	"fmt"
+	"net/url"
+	"path/filepath"
+	"strings"
+
+	"github.com/compose-spec/compose-go/types"
+)
+
+// 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
+		}
+		projectVolumes[vi.name] = types.VolumeConfig{
+			Name:   vi.name,
+			Driver: azureFileDriverName,
+			DriverOpts: map[string]string{
+				volumeDriveroptsAccountNameKey: vi.username,
+				volumeDriveroptsAccountKeyKey:  vi.key,
+				volumeDriveroptsShareNameKey:   vi.share,
+			},
+		}
+		sv := types.ServiceVolumeConfig{
+			Type:   azureFileDriverName,
+			Source: vi.name,
+			Target: vi.target,
+		}
+		serviceConfigVolumes = append(serviceConfigVolumes, sv)
+	}
+
+	return projectVolumes, serviceConfigVolumes, nil
+}
+
+type volumeInput struct {
+	name     string
+	username string
+	key      string
+	share    string
+	target   string
+}
+
+func scapeKeySlashes(rawURL string) (string, error) {
+	urlSplit := strings.Split(rawURL, "@")
+	if len(urlSplit) < 1 {
+		return "", errors.New("invalid url format " + rawURL)
+	}
+	userPasswd := strings.ReplaceAll(urlSplit[0], "/", "_")
+	scaped := userPasswd + rawURL[strings.Index(rawURL, "@"):]
+
+	return scaped, nil
+}
+
+func unscapeKey(passwd string) string {
+	return strings.ReplaceAll(passwd, "_", "/")
+}
+
+// Removes the second ':' that separates the source from target
+func volumeURL(pathURL string) (*url.URL, error) {
+	scapedURL, err := scapeKeySlashes(pathURL)
+	if err != nil {
+		return nil, err
+	}
+	pathURL = "//" + scapedURL
+
+	count := strings.Count(pathURL, ":")
+	if count > 2 {
+		return nil, fmt.Errorf("unable to parse volume mount %q", pathURL)
+	}
+	if count == 2 {
+		tokens := strings.Split(pathURL, ":")
+		pathURL = fmt.Sprintf("%s:%s%s", tokens[0], tokens[1], tokens[2])
+	}
+	return url.Parse(pathURL)
+}
+
+func (v *volumeInput) parse(name string, s string) error {
+	volumeURL, err := volumeURL(s)
+	if err != nil {
+		return fmt.Errorf("volume specification %q could not be parsed %q", s, err)
+	}
+	v.username = volumeURL.User.Username()
+	if v.username == "" {
+		return fmt.Errorf("volume specification %q does not include a storage username", v)
+	}
+	passwd, ok := volumeURL.User.Password()
+	if !ok || passwd == "" {
+		return fmt.Errorf("volume specification %q does not include a storage key", v)
+	}
+	v.key = unscapeKey(passwd)
+	v.share = volumeURL.Host
+	if v.share == "" {
+		return fmt.Errorf("volume specification %q does not include a storage file share", v)
+	}
+	v.name = name
+	v.target = volumeURL.Path
+	if v.target == "" {
+		v.target = filepath.Join("/run/volumes/", v.share)
+	}
+	return nil
+}

+ 54 - 0
cli/cmd/run/opts.go

@@ -0,0 +1,54 @@
+package run
+
+import (
+	"fmt"
+	"strconv"
+	"strings"
+
+	"github.com/docker/api/containers"
+)
+
+type runOpts struct {
+	name    string
+	publish []string
+	volumes []string
+}
+
+func toPorts(ports []string) ([]containers.Port, error) {
+	var result []containers.Port
+
+	for _, port := range ports {
+		parts := strings.Split(port, ":")
+		if len(parts) != 2 {
+			return nil, fmt.Errorf("unable to parse ports %q", port)
+		}
+		source, err := strconv.Atoi(parts[0])
+		if err != nil {
+			return nil, err
+		}
+		destination, err := strconv.Atoi(parts[1])
+		if err != nil {
+			return nil, err
+		}
+
+		result = append(result, containers.Port{
+			HostPort:      uint32(source),
+			ContainerPort: uint32(destination),
+		})
+	}
+	return result, nil
+}
+
+func (r *runOpts) toContainerConfig(image string) (containers.ContainerConfig, error) {
+	publish, err := toPorts(r.publish)
+	if err != nil {
+		return containers.ContainerConfig{}, err
+	}
+
+	return containers.ContainerConfig{
+		ID:      r.name,
+		Image:   image,
+		Ports:   publish,
+		Volumes: r.volumes,
+	}, nil
+}

+ 3 - 3
cli/cmd/run/run.go

@@ -54,6 +54,7 @@ func Command() *cobra.Command {
 	cmd.Flags().StringArrayVarP(&opts.Publish, "publish", "p", []string{}, "Publish a container's port(s). [HOST_PORT:]CONTAINER_PORT")
 	cmd.Flags().StringVar(&opts.Name, "name", getRandomName(), "Assign a name to the container")
 	cmd.Flags().StringArrayVarP(&opts.Labels, "label", "l", []string{}, "Set meta data on a container")
+	cmd.Flags().StringArrayVarP(&opts.Volumes, "volume", "v", []string{}, "Volume. Ex: user:key@my_share:/absolute/path/to/target")
 
 	return cmd
 }
@@ -64,18 +65,17 @@ func runRun(ctx context.Context, image string, opts run.Opts) error {
 		return err
 	}
 
-	project, err := opts.ToContainerConfig(image)
+	containerConfig, err := opts.ToContainerConfig(image)
 	if err != nil {
 		return err
 	}
 
-	if err = c.ContainerService().Run(ctx, project); err != nil {
+	if err = c.ContainerService().Run(ctx, containerConfig); err != nil {
 		return err
 	}
 	fmt.Println(opts.Name)
 
 	return nil
-
 }
 
 func getRandomName() string {

+ 1 - 0
cli/options/run/opts.go

@@ -15,6 +15,7 @@ type Opts struct {
 	Name    string
 	Publish []string
 	Labels  []string
+	Volumes []string
 }
 
 // ToContainerConfig convert run options to a container configuration

+ 3 - 1
containers/api.go

@@ -26,7 +26,7 @@ type Port struct {
 	HostPort uint32
 	// ContainerPort is the port number inside the container
 	ContainerPort uint32
-	/// Protocol is the protocol of the port mapping
+	// Protocol is the protocol of the port mapping
 	Protocol string
 	// HostIP is the host ip to use
 	HostIP string
@@ -42,6 +42,8 @@ type ContainerConfig struct {
 	Ports []Port
 	// Labels set labels to the container
 	Labels map[string]string
+	// Volumes to be mounted
+	Volumes []string
 }
 
 // LogsRequest contains configuration about a log request