зеркало из https://github.com/docker/compose-cli.git
Add volumes to run command
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
This commit is contained in:
Родитель
7603a3b832
Коммит
b25a6b4bd6
|
@ -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,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -15,6 +15,7 @@ type Opts struct {
|
|||
Name string
|
||||
Publish []string
|
||||
Labels []string
|
||||
Volumes []string
|
||||
}
|
||||
|
||||
// ToContainerConfig convert run options to a container configuration
|
||||
|
|
|
@ -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
|
||||
|
|
Загрузка…
Ссылка в новой задаче