зеркало из https://github.com/docker/compose-cli.git
Add checks for unsupported flags
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
This commit is contained in:
Родитель
53f692747b
Коммит
fa7e0632ba
|
@ -22,9 +22,9 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/docker/compose-cli/utils"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/progress"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/docker/compose-cli/aci/convert"
|
||||
|
@ -86,11 +86,36 @@ func (cs *aciComposeService) Copy(ctx context.Context, project *types.Project, o
|
|||
}
|
||||
|
||||
func (cs *aciComposeService) Up(ctx context.Context, project *types.Project, options api.UpOptions) error {
|
||||
if err := checkUnsupportedUpOptions(options); err != nil {
|
||||
return err
|
||||
}
|
||||
return progress.Run(ctx, func(ctx context.Context) error {
|
||||
return cs.up(ctx, project)
|
||||
})
|
||||
}
|
||||
|
||||
func checkUnsupportedUpOptions(o api.UpOptions) error {
|
||||
var errs error
|
||||
checks := []struct {
|
||||
toCheck, expected interface{}
|
||||
option string
|
||||
}{
|
||||
{o.Start.CascadeStop, false, "abort-on-container-exit"},
|
||||
{o.Create.RecreateDependencies, "", "always-recreate-deps"},
|
||||
{len(o.Start.AttachTo), 0, "attach-dependencies"},
|
||||
{len(o.Start.ExitCodeFrom), 0, "exit-code-from"},
|
||||
{o.Create.Recreate, "", "force-recreate"},
|
||||
{o.Create.QuietPull, false, "quiet-pull"},
|
||||
{o.Create.RemoveOrphans, false, "remove-orphans"},
|
||||
{o.Create.Inherit, true, "renew-anon-volumes"},
|
||||
{o.Create.Timeout, nil, "timeout"},
|
||||
}
|
||||
for _, c := range checks {
|
||||
errs = utils.CheckUnsupported(errs, c.toCheck, c.expected, "up", c.option)
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
func (cs *aciComposeService) up(ctx context.Context, project *types.Project) error {
|
||||
logrus.Debugf("Up on project with name %q", project.Name)
|
||||
|
||||
|
@ -130,11 +155,8 @@ func (cs aciComposeService) warnKeepVolumeOnDown(ctx context.Context, projectNam
|
|||
}
|
||||
|
||||
func (cs *aciComposeService) Down(ctx context.Context, projectName string, options api.DownOptions) error {
|
||||
if options.Volumes {
|
||||
return errors.Wrap(api.ErrNotImplemented, "--volumes option is not supported on ACI")
|
||||
}
|
||||
if options.Images != "" {
|
||||
return errors.Wrap(api.ErrNotImplemented, "--rmi option is not supported on ACI")
|
||||
if err := checkUnsupportedDownOptions(options); err != nil {
|
||||
return err
|
||||
}
|
||||
return progress.Run(ctx, func(ctx context.Context) error {
|
||||
logrus.Debugf("Down on project with name %q", projectName)
|
||||
|
@ -155,7 +177,17 @@ func (cs *aciComposeService) Down(ctx context.Context, projectName string, optio
|
|||
})
|
||||
}
|
||||
|
||||
func checkUnsupportedDownOptions(o api.DownOptions) error {
|
||||
var errs error
|
||||
errs = utils.CheckUnsupported(errs, o.Volumes, false, "down", "volumes")
|
||||
errs = utils.CheckUnsupported(errs, o.Images, "", "down", "images")
|
||||
return errs
|
||||
}
|
||||
|
||||
func (cs *aciComposeService) Ps(ctx context.Context, projectName string, options api.PsOptions) ([]api.ContainerSummary, error) {
|
||||
if err := checkUnsupportedPsOptions(options); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
groupsClient, err := login.NewContainerGroupsClient(cs.ctx.SubscriptionID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -198,7 +230,14 @@ func (cs *aciComposeService) Ps(ctx context.Context, projectName string, options
|
|||
return res, nil
|
||||
}
|
||||
|
||||
func checkUnsupportedPsOptions(o api.PsOptions) error {
|
||||
return utils.CheckUnsupported(nil, o.All, false, "ps", "all")
|
||||
}
|
||||
|
||||
func (cs *aciComposeService) List(ctx context.Context, opts api.ListOptions) ([]api.Stack, error) {
|
||||
if err := checkUnsupportedListOptions(opts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
containerGroups, err := getACIContainerGroups(ctx, cs.ctx.SubscriptionID, cs.ctx.ResourceGroup)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -226,6 +265,10 @@ func (cs *aciComposeService) List(ctx context.Context, opts api.ListOptions) ([]
|
|||
return stacks, nil
|
||||
}
|
||||
|
||||
func checkUnsupportedListOptions(o api.ListOptions) error {
|
||||
return utils.CheckUnsupported(nil, o.All, false, "ls", "all")
|
||||
}
|
||||
|
||||
func (cs *aciComposeService) Logs(ctx context.Context, projectName string, consumer api.LogConsumer, options api.LogOptions) error {
|
||||
return api.ErrNotImplemented
|
||||
}
|
||||
|
|
|
@ -35,10 +35,10 @@ type ConfigFlags struct {
|
|||
|
||||
// AddConfigFlags adds persistent (global) flags
|
||||
func (c *ConfigFlags) AddConfigFlags(flags *pflag.FlagSet) {
|
||||
flags.StringVar(&c.Config, config.ConfigFlagName, confDir(), "Location of the client config files `DIRECTORY`")
|
||||
flags.StringVar(&c.Config, config.ConfigFlagName, ConfDir(), "Location of the client config files `DIRECTORY`")
|
||||
}
|
||||
|
||||
func confDir() string {
|
||||
func ConfDir() string {
|
||||
env := os.Getenv("DOCKER_CONFIG")
|
||||
if env != "" {
|
||||
return env
|
||||
|
|
|
@ -324,7 +324,7 @@ func exit(ctx string, err error, ctype string) {
|
|||
|
||||
if errors.Is(err, api.ErrNotImplemented) {
|
||||
name := metrics.GetCommand(os.Args[1:])
|
||||
fmt.Fprintf(os.Stderr, "Command %q not available in current context (%s)\n", name, ctx)
|
||||
fmt.Fprintf(os.Stderr, "Command %q not available in current context (%s). %q\n", name, ctx, err)
|
||||
|
||||
os.Exit(1)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
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 compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/compose-cli/pkg/api"
|
||||
)
|
||||
|
||||
type downOptions struct {
|
||||
*projectOptions
|
||||
removeOrphans bool
|
||||
timeChanged bool
|
||||
timeout int
|
||||
volumes bool
|
||||
images string
|
||||
}
|
||||
|
||||
func downCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
opts := downOptions{
|
||||
projectOptions: p,
|
||||
}
|
||||
downCmd := &cobra.Command{
|
||||
Use: "down",
|
||||
Short: "Stop and remove containers, networks",
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
opts.timeChanged = cmd.Flags().Changed("timeout")
|
||||
},
|
||||
PreRunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
if opts.images != "" {
|
||||
if opts.images != "all" && opts.images != "local" {
|
||||
return fmt.Errorf("invalid value for --rmi: %q", opts.images)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runDown(ctx, backend, opts)
|
||||
}),
|
||||
ValidArgsFunction: noCompletion(),
|
||||
}
|
||||
flags := downCmd.Flags()
|
||||
flags.BoolVar(&opts.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file.")
|
||||
flags.IntVarP(&opts.timeout, "timeout", "t", 10, "Specify a shutdown timeout in seconds")
|
||||
flags.BoolVarP(&opts.volumes, "volumes", "v", false, "Remove named volumes declared in the `volumes` section of the Compose file and anonymous volumes attached to containers.")
|
||||
flags.StringVar(&opts.images, "rmi", "", `Remove images used by services. "local" remove only images that don't have a custom tag ("local"|"all")`)
|
||||
return downCmd
|
||||
}
|
||||
|
||||
func runDown(ctx context.Context, backend api.Service, opts downOptions) error {
|
||||
name := opts.ProjectName
|
||||
var project *types.Project
|
||||
if opts.ProjectName == "" {
|
||||
p, err := opts.toProject(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
project = p
|
||||
name = p.Name
|
||||
}
|
||||
|
||||
var timeout *time.Duration
|
||||
if opts.timeChanged {
|
||||
timeoutValue := time.Duration(opts.timeout) * time.Second
|
||||
timeout = &timeoutValue
|
||||
}
|
||||
return backend.Down(ctx, name, api.DownOptions{
|
||||
RemoveOrphans: opts.removeOrphans,
|
||||
Project: project,
|
||||
Timeout: timeout,
|
||||
Images: opts.images,
|
||||
Volumes: opts.volumes,
|
||||
})
|
||||
}
|
|
@ -38,6 +38,7 @@ import (
|
|||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/distribution/distribution/v3/reference"
|
||||
cliconfig "github.com/docker/cli/cli/config"
|
||||
"github.com/docker/compose-cli/utils"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
|
@ -46,11 +47,10 @@ import (
|
|||
"github.com/docker/compose-cli/api/config"
|
||||
)
|
||||
|
||||
func (b *ecsAPIService) Kill(ctx context.Context, project *types.Project, options api.KillOptions) error {
|
||||
return api.ErrNotImplemented
|
||||
}
|
||||
|
||||
func (b *ecsAPIService) Convert(ctx context.Context, project *types.Project, options api.ConvertOptions) ([]byte, error) {
|
||||
if err := checkUnsupportedConvertOptions(options); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err := b.resolveServiceImagesDigests(ctx, project)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -102,6 +102,10 @@ func (b *ecsAPIService) Convert(ctx context.Context, project *types.Project, opt
|
|||
return bytes, err
|
||||
}
|
||||
|
||||
func checkUnsupportedConvertOptions(o api.ConvertOptions) error {
|
||||
return utils.CheckUnsupported(nil, o.Output, "", "convert", "output")
|
||||
}
|
||||
|
||||
func (b *ecsAPIService) resolveServiceImagesDigests(ctx context.Context, project *types.Project) error {
|
||||
configFile, err := cliconfig.Load(config.Dir())
|
||||
if err != nil {
|
||||
|
@ -368,8 +372,8 @@ func (b *ecsAPIService) createListener(service types.ServiceConfig, port types.S
|
|||
strings.ToUpper(port.Protocol),
|
||||
port.Target,
|
||||
)
|
||||
//add listener to dependsOn
|
||||
//https://stackoverflow.com/questions/53971873/the-target-group-does-not-have-an-associated-load-balancer
|
||||
// add listener to dependsOn
|
||||
// https://stackoverflow.com/questions/53971873/the-target-group-does-not-have-an-associated-load-balancer
|
||||
template.Resources[listenerName] = &elasticloadbalancingv2.Listener{
|
||||
DefaultActions: []elasticloadbalancingv2.Listener_Action{
|
||||
{
|
||||
|
|
27
ecs/down.go
27
ecs/down.go
|
@ -19,17 +19,14 @@ package ecs
|
|||
import (
|
||||
"context"
|
||||
|
||||
"github.com/docker/compose-cli/utils"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/progress"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (b *ecsAPIService) Down(ctx context.Context, projectName string, options api.DownOptions) error {
|
||||
if options.Volumes {
|
||||
return errors.Wrap(api.ErrNotImplemented, "--volumes option is not supported on ECS")
|
||||
}
|
||||
if options.Images != "" {
|
||||
return errors.Wrap(api.ErrNotImplemented, "--rmi option is not supported on ECS")
|
||||
if err := checkUnsupportedDownOptions(options); err != nil {
|
||||
return err
|
||||
}
|
||||
return progress.Run(ctx, func(ctx context.Context) error {
|
||||
return b.down(ctx, projectName)
|
||||
|
@ -89,3 +86,21 @@ func doDelete(ctx context.Context, delete func(ctx context.Context, arn string)
|
|||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func checkUnsupportedDownOptions(o api.DownOptions) error {
|
||||
var errs error
|
||||
checks := []struct {
|
||||
toCheck, expected interface{}
|
||||
option string
|
||||
}{
|
||||
{o.Volumes, false, "volumes"},
|
||||
{o.Images, "", "images"},
|
||||
{o.RemoveOrphans, false, "remove-orphans"},
|
||||
{o.Timeout, nil, "timeout"},
|
||||
}
|
||||
for _, c := range checks {
|
||||
errs = utils.CheckUnsupported(errs, c.toCheck, c.expected, "down", c.option)
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
|
28
ecs/exec.go
28
ecs/exec.go
|
@ -1,28 +0,0 @@
|
|||
/*
|
||||
Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
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 ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
)
|
||||
|
||||
func (b *ecsAPIService) Exec(ctx context.Context, project *types.Project, opts api.RunOptions) (int, error) {
|
||||
return 0, api.ErrNotImplemented
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
/*
|
||||
Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
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 ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
)
|
||||
|
||||
func (b *ecsAPIService) Images(ctx context.Context, projectName string, options api.ImagesOptions) ([]api.ImageSummary, error) {
|
||||
return nil, api.ErrNotImplemented
|
||||
}
|
|
@ -20,10 +20,14 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/compose-cli/utils"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
)
|
||||
|
||||
func (b *ecsAPIService) List(ctx context.Context, opts api.ListOptions) ([]api.Stack, error) {
|
||||
if err := checkUnsupportedListOptions(opts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stacks, err := b.aws.ListStacks(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -41,6 +45,10 @@ func (b *ecsAPIService) List(ctx context.Context, opts api.ListOptions) ([]api.S
|
|||
|
||||
}
|
||||
|
||||
func checkUnsupportedListOptions(o api.ListOptions) error {
|
||||
return utils.CheckUnsupported(nil, o.All, false, "ls", "all")
|
||||
}
|
||||
|
||||
func (b *ecsAPIService) checkStackState(ctx context.Context, name string) error {
|
||||
resources, err := b.aws.ListStackResources(ctx, name)
|
||||
if err != nil {
|
||||
|
|
20
ecs/logs.go
20
ecs/logs.go
|
@ -25,9 +25,29 @@ import (
|
|||
)
|
||||
|
||||
func (b *ecsAPIService) Logs(ctx context.Context, projectName string, consumer api.LogConsumer, options api.LogOptions) error {
|
||||
if err := checkUnsupportedLogOptions(options); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(options.Services) > 0 {
|
||||
consumer = utils.FilteredLogConsumer(consumer, options.Services)
|
||||
}
|
||||
err := b.aws.GetLogs(ctx, projectName, consumer.Log, options.Follow)
|
||||
return err
|
||||
}
|
||||
|
||||
func checkUnsupportedLogOptions(o api.LogOptions) error {
|
||||
var errs error
|
||||
checks := []struct {
|
||||
toCheck, expected interface{}
|
||||
option string
|
||||
}{
|
||||
{o.Since, "", "since"},
|
||||
{o.Tail, "", "tail"},
|
||||
{o.Timestamps, false, "timestamps"},
|
||||
{o.Until, "", "until"},
|
||||
}
|
||||
for _, c := range checks {
|
||||
errs = utils.CheckUnsupported(errs, c.toCheck, c.expected, "logs", c.option)
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
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 ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
)
|
||||
|
||||
func (b *ecsAPIService) Build(ctx context.Context, project *types.Project, options api.BuildOptions) error {
|
||||
return api.ErrNotImplemented
|
||||
}
|
||||
|
||||
func (b *ecsAPIService) Push(ctx context.Context, project *types.Project, options api.PushOptions) error {
|
||||
return api.ErrNotImplemented
|
||||
}
|
||||
|
||||
func (b *ecsAPIService) Pull(ctx context.Context, project *types.Project, options api.PullOptions) error {
|
||||
return api.ErrNotImplemented
|
||||
}
|
||||
|
||||
func (b *ecsAPIService) Create(ctx context.Context, project *types.Project, opts api.CreateOptions) error {
|
||||
return api.ErrNotImplemented
|
||||
}
|
||||
|
||||
func (b *ecsAPIService) Start(ctx context.Context, project *types.Project, options api.StartOptions) error {
|
||||
return api.ErrNotImplemented
|
||||
}
|
||||
|
||||
func (b *ecsAPIService) Restart(ctx context.Context, project *types.Project, options api.RestartOptions) error {
|
||||
return api.ErrNotImplemented
|
||||
}
|
||||
|
||||
func (b *ecsAPIService) Stop(ctx context.Context, project *types.Project, options api.StopOptions) error {
|
||||
return api.ErrNotImplemented
|
||||
}
|
||||
|
||||
func (b *ecsAPIService) Pause(ctx context.Context, project string, options api.PauseOptions) error {
|
||||
return api.ErrNotImplemented
|
||||
}
|
||||
|
||||
func (b *ecsAPIService) UnPause(ctx context.Context, project string, options api.PauseOptions) error {
|
||||
return api.ErrNotImplemented
|
||||
}
|
||||
|
||||
func (b *ecsAPIService) Events(ctx context.Context, project string, options api.EventsOptions) error {
|
||||
return api.ErrNotImplemented
|
||||
}
|
||||
|
||||
func (b *ecsAPIService) Port(ctx context.Context, project string, service string, port int, options api.PortOptions) (string, int, error) {
|
||||
return "", 0, api.ErrNotImplemented
|
||||
}
|
||||
|
||||
func (b *ecsAPIService) Copy(ctx context.Context, project *types.Project, options api.CopyOptions) error {
|
||||
return api.ErrNotImplemented
|
||||
}
|
||||
|
||||
func (b *ecsAPIService) RunOneOffContainer(ctx context.Context, project *types.Project, opts api.RunOptions) (int, error) {
|
||||
return 0, api.ErrNotImplemented
|
||||
}
|
||||
|
||||
func (b *ecsAPIService) Remove(ctx context.Context, project *types.Project, options api.RemoveOptions) error {
|
||||
return api.ErrNotImplemented
|
||||
}
|
||||
|
||||
func (b *ecsAPIService) Images(ctx context.Context, projectName string, options api.ImagesOptions) ([]api.ImageSummary, error) {
|
||||
return nil, api.ErrNotImplemented
|
||||
}
|
||||
|
||||
func (b *ecsAPIService) Top(ctx context.Context, projectName string, services []string) ([]api.ContainerProcSummary, error) {
|
||||
return nil, api.ErrNotImplemented
|
||||
}
|
||||
|
||||
func (b *ecsAPIService) Exec(ctx context.Context, project *types.Project, opts api.RunOptions) (int, error) {
|
||||
return 0, api.ErrNotImplemented
|
||||
}
|
||||
|
||||
func (b *ecsAPIService) Kill(ctx context.Context, project *types.Project, options api.KillOptions) error {
|
||||
return api.ErrNotImplemented
|
||||
}
|
11
ecs/ps.go
11
ecs/ps.go
|
@ -19,10 +19,15 @@ package ecs
|
|||
import (
|
||||
"context"
|
||||
|
||||
"github.com/docker/compose-cli/utils"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
)
|
||||
|
||||
func (b *ecsAPIService) Ps(ctx context.Context, projectName string, options api.PsOptions) ([]api.ContainerSummary, error) {
|
||||
if err := checkUnsupportedPsOptions(options); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cluster, err := b.aws.GetStackClusterID(ctx, projectName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -56,3 +61,9 @@ func (b *ecsAPIService) Ps(ctx context.Context, projectName string, options api.
|
|||
}
|
||||
return summary, nil
|
||||
}
|
||||
|
||||
func checkUnsupportedPsOptions(o api.PsOptions) error {
|
||||
var errs error
|
||||
errs = utils.CheckUnsupported(errs, o.All, false, "ps", "all")
|
||||
return errs
|
||||
}
|
||||
|
|
33
ecs/run.go
33
ecs/run.go
|
@ -1,33 +0,0 @@
|
|||
/*
|
||||
Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
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 ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
)
|
||||
|
||||
func (b *ecsAPIService) RunOneOffContainer(ctx context.Context, project *types.Project, opts api.RunOptions) (int, error) {
|
||||
return 0, api.ErrNotImplemented
|
||||
}
|
||||
|
||||
func (b *ecsAPIService) Remove(ctx context.Context, project *types.Project, options api.RemoveOptions) error {
|
||||
return api.ErrNotImplemented
|
||||
}
|
27
ecs/top.go
27
ecs/top.go
|
@ -1,27 +0,0 @@
|
|||
/*
|
||||
Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
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 ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
)
|
||||
|
||||
func (b *ecsAPIService) Top(ctx context.Context, projectName string, services []string) ([]api.ContainerProcSummary, error) {
|
||||
return nil, api.ErrNotImplemented
|
||||
}
|
73
ecs/up.go
73
ecs/up.go
|
@ -24,60 +24,16 @@ import (
|
|||
"syscall"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/docker/compose-cli/utils"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/progress"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (b *ecsAPIService) Build(ctx context.Context, project *types.Project, options api.BuildOptions) error {
|
||||
return api.ErrNotImplemented
|
||||
}
|
||||
|
||||
func (b *ecsAPIService) Push(ctx context.Context, project *types.Project, options api.PushOptions) error {
|
||||
return api.ErrNotImplemented
|
||||
}
|
||||
|
||||
func (b *ecsAPIService) Pull(ctx context.Context, project *types.Project, options api.PullOptions) error {
|
||||
return api.ErrNotImplemented
|
||||
}
|
||||
|
||||
func (b *ecsAPIService) Create(ctx context.Context, project *types.Project, opts api.CreateOptions) error {
|
||||
return api.ErrNotImplemented
|
||||
}
|
||||
|
||||
func (b *ecsAPIService) Start(ctx context.Context, project *types.Project, options api.StartOptions) error {
|
||||
return api.ErrNotImplemented
|
||||
}
|
||||
|
||||
func (b *ecsAPIService) Restart(ctx context.Context, project *types.Project, options api.RestartOptions) error {
|
||||
return api.ErrNotImplemented
|
||||
}
|
||||
|
||||
func (b *ecsAPIService) Stop(ctx context.Context, project *types.Project, options api.StopOptions) error {
|
||||
return api.ErrNotImplemented
|
||||
}
|
||||
|
||||
func (b *ecsAPIService) Pause(ctx context.Context, project string, options api.PauseOptions) error {
|
||||
return api.ErrNotImplemented
|
||||
}
|
||||
|
||||
func (b *ecsAPIService) UnPause(ctx context.Context, project string, options api.PauseOptions) error {
|
||||
return api.ErrNotImplemented
|
||||
}
|
||||
|
||||
func (b *ecsAPIService) Events(ctx context.Context, project string, options api.EventsOptions) error {
|
||||
return api.ErrNotImplemented
|
||||
}
|
||||
|
||||
func (b *ecsAPIService) Port(ctx context.Context, project string, service string, port int, options api.PortOptions) (string, int, error) {
|
||||
return "", 0, api.ErrNotImplemented
|
||||
}
|
||||
|
||||
func (b *ecsAPIService) Copy(ctx context.Context, project *types.Project, options api.CopyOptions) error {
|
||||
return api.ErrNotImplemented
|
||||
}
|
||||
|
||||
func (b *ecsAPIService) Up(ctx context.Context, project *types.Project, options api.UpOptions) error {
|
||||
if err := checkUnsupportedUpOptions(options); err != nil {
|
||||
return err
|
||||
}
|
||||
return progress.Run(ctx, func(ctx context.Context) error {
|
||||
return b.up(ctx, project, options)
|
||||
})
|
||||
|
@ -142,3 +98,24 @@ func (b *ecsAPIService) up(ctx context.Context, project *types.Project, options
|
|||
err = b.WaitStackCompletion(ctx, project.Name, operation, previousEvents...)
|
||||
return err
|
||||
}
|
||||
|
||||
func checkUnsupportedUpOptions(o api.UpOptions) error {
|
||||
var errs error
|
||||
checks := []struct {
|
||||
toCheck, expected interface{}
|
||||
option string
|
||||
}{
|
||||
{o.Create.Inherit, true, "renew-anon-volumes"},
|
||||
{o.Create.RemoveOrphans, false, "remove-orphans"},
|
||||
{o.Create.QuietPull, false, "quiet-pull"},
|
||||
{o.Create.Recreate, api.RecreateDiverged, "force-recreate"},
|
||||
{o.Create.RecreateDependencies, api.RecreateDiverged, "always-recreate-deps"},
|
||||
{len(o.Start.AttachTo), 0, "attach-dependencies"},
|
||||
{len(o.Start.ExitCodeFrom), 0, "exit-code-from"},
|
||||
{o.Create.Timeout, nil, "timeout"},
|
||||
}
|
||||
for _, c := range checks {
|
||||
errs = utils.CheckUnsupported(errs, c.toCheck, c.expected, "up", c.option)
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
|
4
go.mod
4
go.mod
|
@ -19,12 +19,12 @@ require (
|
|||
github.com/awslabs/goformation/v4 v4.15.6
|
||||
github.com/buger/goterm v1.0.0
|
||||
github.com/cnabio/cnab-to-oci v0.3.1-beta1
|
||||
github.com/compose-spec/compose-go v0.0.0-20210901090333-feb401cda7f7
|
||||
github.com/compose-spec/compose-go v0.0.0-20210906143156-938b039f7805
|
||||
github.com/containerd/console v1.0.2
|
||||
github.com/containerd/containerd v1.5.4
|
||||
github.com/distribution/distribution/v3 v3.0.0-20210316161203-a01c71e2477e
|
||||
github.com/docker/cli v20.10.7+incompatible
|
||||
github.com/docker/compose/v2 v2.0.0-20210902070652-5b30accf351f
|
||||
github.com/docker/compose/v2 v2.0.0-rc.3.0.20210916150857-15cd03448553
|
||||
github.com/docker/docker v20.10.7+incompatible
|
||||
github.com/docker/go-connections v0.4.0
|
||||
github.com/docker/go-units v0.4.0
|
||||
|
|
9
go.sum
9
go.sum
|
@ -255,8 +255,8 @@ github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnht
|
|||
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
|
||||
github.com/codahale/hdrhistogram v0.0.0-20160425231609-f8ad88b59a58/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
|
||||
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
|
||||
github.com/compose-spec/compose-go v0.0.0-20210901090333-feb401cda7f7 h1:wI9VC+EgX61YYB/xyQVb9BBfsFOklZ/BinipIpFcWn4=
|
||||
github.com/compose-spec/compose-go v0.0.0-20210901090333-feb401cda7f7/go.mod h1:Hnmn5ZCVA3sSBN2urjCZNNIyNqCPayRGH7PmMSaV2Q0=
|
||||
github.com/compose-spec/compose-go v0.0.0-20210906143156-938b039f7805 h1:iFShT4oPik+NnXdzksa4yLvJYDDZBPMjHPN3qtJaXPA=
|
||||
github.com/compose-spec/compose-go v0.0.0-20210906143156-938b039f7805/go.mod h1:Hnmn5ZCVA3sSBN2urjCZNNIyNqCPayRGH7PmMSaV2Q0=
|
||||
github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE=
|
||||
github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU=
|
||||
github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU=
|
||||
|
@ -412,9 +412,10 @@ github.com/docker/cli v20.10.5+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHv
|
|||
github.com/docker/cli v20.10.6+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/cli v20.10.7+incompatible h1:pv/3NqibQKphWZiAskMzdz8w0PRbtTaEB+f6NwdU7Is=
|
||||
github.com/docker/cli v20.10.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/cli-docs-tool v0.1.1/go.mod h1:oMzPNt1wC3TcxuY22GMnOODNOxkwGH51gV3AhqAjFQ4=
|
||||
github.com/docker/compose-on-kubernetes v0.4.19-0.20190128150448-356b2919c496/go.mod h1:iT2pYfi580XlpaV4KmK0T6+4/9+XoKmk/fhoDod1emE=
|
||||
github.com/docker/compose/v2 v2.0.0-20210902070652-5b30accf351f h1:YjG63wEdSZGdZYt/EOEclCQldN5Bg0jPNWDx//psKEw=
|
||||
github.com/docker/compose/v2 v2.0.0-20210902070652-5b30accf351f/go.mod h1:9t3b24Terzo7Dx37p5Vtb7JEMPekAsnZUf5JmRblivg=
|
||||
github.com/docker/compose/v2 v2.0.0-rc.3.0.20210916150857-15cd03448553 h1:uv/kMiCOXbCaFR8vn6pISh50pPWo01rCeSHVxBXGVes=
|
||||
github.com/docker/compose/v2 v2.0.0-rc.3.0.20210916150857-15cd03448553/go.mod h1:1ElTlmGxnapGk1DF8EscG9uozzScMMBn3wkDeiQCE5k=
|
||||
github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY=
|
||||
github.com/docker/distribution v2.6.0-rc.1.0.20180327202408-83389a148052+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/distribution v2.7.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
|
|
|
@ -27,7 +27,6 @@ import (
|
|||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/progress"
|
||||
utils2 "github.com/docker/compose/v2/pkg/utils"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
apicontext "github.com/docker/compose-cli/api/context"
|
||||
"github.com/docker/compose-cli/api/context/store"
|
||||
|
@ -72,11 +71,35 @@ func NewComposeService() (api.Service, error) {
|
|||
|
||||
// Up executes the equivalent to a `compose up`
|
||||
func (s *composeService) Up(ctx context.Context, project *types.Project, options api.UpOptions) error {
|
||||
if err := checkUnsupportedUpOptions(options); err != nil {
|
||||
return err
|
||||
}
|
||||
return progress.Run(ctx, func(ctx context.Context) error {
|
||||
return s.up(ctx, project)
|
||||
})
|
||||
}
|
||||
|
||||
func checkUnsupportedUpOptions(o api.UpOptions) error {
|
||||
var errs error
|
||||
checks := []struct {
|
||||
toCheck, expected interface{}
|
||||
option string
|
||||
}{
|
||||
{o.Create.Inherit, true, "renew-anon-volumes"},
|
||||
{o.Create.RemoveOrphans, false, "remove-orphans"},
|
||||
{o.Create.QuietPull, false, "quiet-pull"},
|
||||
{o.Create.Recreate, api.RecreateDiverged, "force-recreate"},
|
||||
{o.Create.RecreateDependencies, api.RecreateDiverged, "always-recreate-deps"},
|
||||
{len(o.Start.AttachTo), 0, "attach-dependencies"},
|
||||
{len(o.Start.ExitCodeFrom), 0, "exit-code-from"},
|
||||
{o.Create.Timeout, nil, "timeout"},
|
||||
}
|
||||
for _, c := range checks {
|
||||
errs = utils.CheckUnsupported(errs, c.toCheck, c.expected, "up", c.option)
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
func (s *composeService) up(ctx context.Context, project *types.Project) error {
|
||||
w := progress.ContextWriter(ctx)
|
||||
|
||||
|
@ -132,17 +155,30 @@ func (s *composeService) up(ctx context.Context, project *types.Project) error {
|
|||
|
||||
// Down executes the equivalent to a `compose down`
|
||||
func (s *composeService) Down(ctx context.Context, projectName string, options api.DownOptions) error {
|
||||
if options.Volumes {
|
||||
return errors.Wrap(api.ErrNotImplemented, "--volumes option is not supported on Kubernetes")
|
||||
}
|
||||
if options.Images != "" {
|
||||
return errors.Wrap(api.ErrNotImplemented, "--rmi option is not supported on Kubernetes")
|
||||
if err := checkUnsupportedDownOptions(options); err != nil {
|
||||
return err
|
||||
}
|
||||
return progress.Run(ctx, func(ctx context.Context) error {
|
||||
return s.down(ctx, projectName, options)
|
||||
})
|
||||
}
|
||||
|
||||
func checkUnsupportedDownOptions(o api.DownOptions) error {
|
||||
var errs error
|
||||
checks := []struct {
|
||||
toCheck, expected interface{}
|
||||
option string
|
||||
}{
|
||||
{o.Volumes, false, "volumes"},
|
||||
{o.Images, "", "images"},
|
||||
{o.RemoveOrphans, false, "remove-orphans"},
|
||||
}
|
||||
for _, c := range checks {
|
||||
errs = utils.CheckUnsupported(errs, c.toCheck, c.expected, "down", c.option)
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
func (s *composeService) down(ctx context.Context, projectName string, options api.DownOptions) error {
|
||||
w := progress.ContextWriter(ctx)
|
||||
eventName := fmt.Sprintf("Remove %s", projectName)
|
||||
|
@ -193,9 +229,16 @@ func (s *composeService) down(ctx context.Context, projectName string, options a
|
|||
|
||||
// List executes the equivalent to a `docker stack ls`
|
||||
func (s *composeService) List(ctx context.Context, opts api.ListOptions) ([]api.Stack, error) {
|
||||
if err := checkUnsupportedListOptions(opts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.sdk.ListReleases()
|
||||
}
|
||||
|
||||
func checkUnsupportedListOptions(o api.ListOptions) error {
|
||||
return utils.CheckUnsupported(nil, o.All, false, "ls", "all")
|
||||
}
|
||||
|
||||
// Build executes the equivalent to a `compose build`
|
||||
func (s *composeService) Build(ctx context.Context, project *types.Project, options api.BuildOptions) error {
|
||||
return api.ErrNotImplemented
|
||||
|
@ -238,12 +281,31 @@ func (s *composeService) Copy(ctx context.Context, project *types.Project, optio
|
|||
|
||||
// Logs executes the equivalent to a `compose logs`
|
||||
func (s *composeService) Logs(ctx context.Context, projectName string, consumer api.LogConsumer, options api.LogOptions) error {
|
||||
if err := checkUnsupportedLogOptions(options); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(options.Services) > 0 {
|
||||
consumer = utils.FilteredLogConsumer(consumer, options.Services)
|
||||
}
|
||||
return s.client.GetLogs(ctx, projectName, consumer, options.Follow)
|
||||
}
|
||||
|
||||
func checkUnsupportedLogOptions(o api.LogOptions) error {
|
||||
var errs error
|
||||
checks := []struct {
|
||||
toCheck, expected interface{}
|
||||
option string
|
||||
}{
|
||||
{o.Since, "", "since"},
|
||||
{o.Timestamps, false, "timestamps"},
|
||||
{o.Until, "", "until"},
|
||||
}
|
||||
for _, c := range checks {
|
||||
errs = utils.CheckUnsupported(errs, c.toCheck, c.expected, "logs", c.option)
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
// Ps executes the equivalent to a `compose ps`
|
||||
func (s *composeService) Ps(ctx context.Context, projectName string, options api.PsOptions) ([]api.ContainerSummary, error) {
|
||||
return s.client.GetContainers(ctx, projectName, options.All)
|
||||
|
@ -251,7 +313,6 @@ func (s *composeService) Ps(ctx context.Context, projectName string, options api
|
|||
|
||||
// Convert translate compose model into backend's native format
|
||||
func (s *composeService) Convert(ctx context.Context, project *types.Project, options api.ConvertOptions) ([]byte, error) {
|
||||
|
||||
chart, err := helm.GetChartInMemory(project)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -287,9 +348,28 @@ func (s *composeService) Remove(ctx context.Context, project *types.Project, opt
|
|||
|
||||
// Exec executes a command in a running service container
|
||||
func (s *composeService) Exec(ctx context.Context, project *types.Project, opts api.RunOptions) (int, error) {
|
||||
if err := checkUnsupportedExecOptions(opts); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return 0, s.client.Exec(ctx, project.Name, opts)
|
||||
}
|
||||
|
||||
func checkUnsupportedExecOptions(o api.RunOptions) error {
|
||||
var errs error
|
||||
checks := []struct {
|
||||
toCheck, expected interface{}
|
||||
option string
|
||||
}{
|
||||
{o.Index, 0, "index"},
|
||||
{o.Privileged, false, "privileged"},
|
||||
{o.WorkingDir, "", "workdir"},
|
||||
}
|
||||
for _, c := range checks {
|
||||
errs = utils.CheckUnsupported(errs, c.toCheck, c.expected, "exec", c.option)
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
func (s *composeService) Pause(ctx context.Context, project string, options api.PauseOptions) error {
|
||||
return api.ErrNotImplemented
|
||||
}
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
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 utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/compose-cli/api/context/store"
|
||||
"github.com/docker/compose-cli/cli/config"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var ErrUnsupportedFlag = errors.New("unsupported flag")
|
||||
|
||||
func CheckUnsupported(errs error, toCheck, expectedValue interface{}, commandName, msg string) error {
|
||||
ctype := store.DefaultContextType
|
||||
currentContext := config.GetCurrentContext("", config.ConfDir(), nil)
|
||||
s, err := store.New(config.ConfDir())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cc, err := s.Get(currentContext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cc != nil {
|
||||
ctype = cc.Type()
|
||||
}
|
||||
// Fixes type for posterior comparison
|
||||
switch toCheck.(type) {
|
||||
case *time.Duration:
|
||||
if expectedValue == nil {
|
||||
var nilDurationPtr *time.Duration
|
||||
expectedValue = nilDurationPtr
|
||||
}
|
||||
}
|
||||
if toCheck != expectedValue {
|
||||
return multierror.Append(errs, errors.Wrap(ErrUnsupportedFlag,
|
||||
fmt.Sprintf(`option "%s --%s" on context type %s.`,
|
||||
commandName, msg, strings.ToUpper(ctype))))
|
||||
}
|
||||
return errs
|
||||
}
|
Загрузка…
Ссылка в новой задаче