diff --git a/azure/backend.go b/azure/backend.go index c4fc75699..4268b4f71 100644 --- a/azure/backend.go +++ b/azure/backend.go @@ -162,41 +162,11 @@ func (cs *aciContainerService) Run(ctx context.Context, r containers.ContainerCo return errors.New(fmt.Sprintf("invalid container name. ACI container name cannot include %q", composeContainerSeparator)) } - var ports []types.ServicePortConfig - for _, p := range r.Ports { - ports = append(ports, types.ServicePortConfig{ - Target: p.ContainerPort, - Published: p.HostPort, - }) - } - - projectVolumes, serviceConfigVolumes, err := convert.GetRunVolumes(r.Volumes) + project, err := convert.ContainerToComposeProject(r, singleContainerName) if err != nil { return err } - project := types.Project{ - Name: r.ID, - Services: []types.ServiceConfig{ - { - Name: singleContainerName, - Image: r.Image, - Ports: ports, - Labels: r.Labels, - Volumes: serviceConfigVolumes, - Deploy: &types.DeployConfig{ - Resources: types.Resources{ - Limits: &types.Resource{ - NanoCPUs: fmt.Sprintf("%f", r.CPULimit), - MemoryBytes: types.UnitBytes(r.MemLimit.Value()), - }, - }, - }, - }, - }, - Volumes: projectVolumes, - } - logrus.Debugf("Running container %q with name %q\n", r.Image, r.ID) groupDefinition, err := convert.ToContainerGroup(cs.ctx, project) if err != nil { diff --git a/azure/convert/container.go b/azure/convert/container.go new file mode 100644 index 000000000..024f928c1 --- /dev/null +++ b/azure/convert/container.go @@ -0,0 +1,63 @@ +package convert + +import ( + "fmt" + "strings" + + "github.com/compose-spec/compose-go/types" + + "github.com/docker/api/containers" +) + +// ContainerToComposeProject convert container config to compose project +func ContainerToComposeProject(r containers.ContainerConfig, containerID string) (types.Project, error) { + var ports []types.ServicePortConfig + for _, p := range r.Ports { + ports = append(ports, types.ServicePortConfig{ + Target: p.ContainerPort, + Published: p.HostPort, + }) + } + + projectVolumes, serviceConfigVolumes, err := GetRunVolumes(r.Volumes) + if err != nil { + return types.Project{}, err + } + + project := types.Project{ + Name: r.ID, + Services: []types.ServiceConfig{ + { + Name: containerID, + Image: r.Image, + Ports: ports, + Labels: r.Labels, + Volumes: serviceConfigVolumes, + Environment: toComposeEnvs(r.Environment), + Deploy: &types.DeployConfig{ + Resources: types.Resources{ + Limits: &types.Resource{ + NanoCPUs: fmt.Sprintf("%f", r.CPULimit), + MemoryBytes: types.UnitBytes(r.MemLimit.Value()), + }, + }, + }, + }, + }, + Volumes: projectVolumes, + } + return project, nil +} + +func toComposeEnvs(opts []string) types.MappingWithEquals { + result := map[string]*string{} + for _, env := range opts { + tokens := strings.Split(env, "=") + if len(tokens) > 1 { + result[tokens[0]] = &tokens[1] + } else { + result[env] = nil + } + } + return result +} diff --git a/azure/convert/container_test.go b/azure/convert/container_test.go new file mode 100644 index 000000000..e8220ffd6 --- /dev/null +++ b/azure/convert/container_test.go @@ -0,0 +1,54 @@ +/* + Copyright 2020 Docker, Inc. + + 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. +*/ + +package convert + +import ( + "testing" + + "github.com/Azure/go-autorest/autorest/to" + "github.com/compose-spec/compose-go/types" + + "github.com/docker/api/containers" + + . "github.com/onsi/gomega" + "github.com/stretchr/testify/suite" +) + +type ContainerConvertTestSuite struct { + suite.Suite +} + +func (suite *ContainerConvertTestSuite) TestConvertContainerEnvironment() { + container := containers.ContainerConfig{ + ID: "container1", + Environment: []string{"key1=value1", "key2", "key3=value3"}, + } + project, err := ContainerToComposeProject(container, "ID") + Expect(err).To(BeNil()) + service1 := project.Services[0] + Expect(service1.Name).To(Equal("ID")) + Expect(service1.Environment).To(Equal(types.MappingWithEquals{ + "key1": to.StringPtr("value1"), + "key2": nil, + "key3": to.StringPtr("value3"), + })) +} + +func TestContainerConvertTestSuite(t *testing.T) { + RegisterTestingT(t) + suite.Run(t, new(ContainerConvertTestSuite)) +} diff --git a/azure/convert/convert_test.go b/azure/convert/convert_test.go index 49810a112..ea004b591 100644 --- a/azure/convert/convert_test.go +++ b/azure/convert/convert_test.go @@ -262,7 +262,8 @@ func (suite *ConvertTestSuite) TestComposeContainerGroupToContainerResourceLimit } func (suite *ConvertTestSuite) TestComposeContainerGroupToContainerenvVar() { - os.Setenv("key2", "value2") + err := os.Setenv("key2", "value2") + Expect(err).To(BeNil()) project := types.Project{ Services: []types.ServiceConfig{ { diff --git a/cli/cmd/run/run.go b/cli/cmd/run/run.go index 5bb6b0568..0991dbd8c 100644 --- a/cli/cmd/run/run.go +++ b/cli/cmd/run/run.go @@ -51,6 +51,7 @@ func Command() *cobra.Command { cmd.Flags().BoolVarP(&opts.Detach, "detach", "d", false, "Run container in background and print container ID") cmd.Flags().Float64Var(&opts.Cpus, "cpus", 1., "Number of CPUs") cmd.Flags().VarP(&opts.Memory, "memory", "m", "Memory limit") + cmd.Flags().StringArrayVarP(&opts.Environment, "env", "e", []string{}, "Set environment variables") return cmd } diff --git a/cli/cmd/run/testdata/run-help.golden b/cli/cmd/run/testdata/run-help.golden index c69e05c58..f759be025 100644 --- a/cli/cmd/run/testdata/run-help.golden +++ b/cli/cmd/run/testdata/run-help.golden @@ -6,6 +6,7 @@ Usage: Flags: --cpus float Number of CPUs (default 1) -d, --detach Run container in background and print container ID + -e, --env stringArray Set environment variables -l, --label stringArray Set meta data on a container -m, --memory bytes Memory limit --name string Assign a name to the container diff --git a/cli/options/run/opts.go b/cli/options/run/opts.go index f1188e1c1..6db435269 100644 --- a/cli/options/run/opts.go +++ b/cli/options/run/opts.go @@ -30,13 +30,14 @@ import ( // Opts contain run command options type Opts struct { - Name string - Publish []string - Labels []string - Volumes []string - Cpus float64 - Memory formatter.MemBytes - Detach bool + Name string + Publish []string + Labels []string + Volumes []string + Cpus float64 + Memory formatter.MemBytes + Detach bool + Environment []string } // ToContainerConfig convert run options to a container configuration @@ -56,13 +57,14 @@ func (r *Opts) ToContainerConfig(image string) (containers.ContainerConfig, erro } return containers.ContainerConfig{ - ID: r.Name, - Image: image, - Ports: publish, - Labels: labels, - Volumes: r.Volumes, - MemLimit: r.Memory, - CPULimit: r.Cpus, + ID: r.Name, + Image: image, + Ports: publish, + Labels: labels, + Volumes: r.Volumes, + MemLimit: r.Memory, + CPULimit: r.Cpus, + Environment: r.Environment, }, nil } diff --git a/containers/api.go b/containers/api.go index 3697e484f..bacfc8367 100644 --- a/containers/api.go +++ b/containers/api.go @@ -68,6 +68,8 @@ type ContainerConfig struct { MemLimit formatter.MemBytes // CPUlimit CPULimit float64 + // Environment variables + Environment []string } // LogsRequest contains configuration about a log request