Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cp command: copy to all containers of a service as default behaviour #9437

Merged
merged 4 commits into from
May 10, 2022

Conversation

glours
Copy link
Contributor

@glours glours commented May 3, 2022

What I did
Change the default behaviour of the cp command when copying from host to a service. Now it will copy the source to all the containers of a service instead of the first one previously

Deprecation of the --all flag

(not mandatory) A picture of a cute animal, if possible in relation with what you did
image

@glours glours self-assigned this May 3, 2022
@@ -64,8 +64,10 @@ func copyCommand(p *projectOptions, backend api.Service) *cobra.Command {
}

flags := copyCmd.Flags()
flags.IntVar(&opts.index, "index", 1, "Index of the container if there are multiple instances of a service [default: 1].")
flags.IntVar(&opts.index, "index", 0, "Index of the container if there are multiple instances of a service [default: 1 when copying from a container].")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a huge fan of specifying a default value that we change depending of the context of the cp command. if you have better idea to do it, let me know

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps remove the [default: 1 when copying from a container]. altogether? (note that Cobra already prints defaults, so in most cases, there's no need to manually put it in the description)

Suggested change
flags.IntVar(&opts.index, "index", 0, "Index of the container if there are multiple instances of a service [default: 1 when copying from a container].")
flags.IntVar(&opts.index, "index", 0, "Index of the container if there are multiple instances of a service .")

As to the flag itself; are indexes guaranteed to start with 1? (i.e., could a container be removed, and it starts with 2 ?)

The "when copying from a container" is a bit confusing, as compose works with "services", correct? (in other words; I don't think there's an option to specify a container - for that you'd use docker cp or docker container cp.

Overall I'm wondering a bit if we should have this option; my train of thought here is that;

  • by default, we should assume all instances of a service to be identical
  • compose to interact with a compose "service" (so consider all to be identical)
  • it there IS a need to target a specific instance (which should be an exception to the rule); perhaps users should use docker cp / docker container cp for the "I know what I'm doing" situation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need to double check index is aligned wit compose exec --index, and that we didn't duplicated the logic to select a target container here so that we enforce an homogeneous behavior

when copying from a container indeed only make sense with a single container as target. Either we should REQUIRE index to be set when multiple container match, or set "first indexed in the available ones" as default (which might not be 1) - but, again, should behave the same as compose exec

it there IS a need to target a specific instance (which should be an exception to the rule); perhaps users should use docker cp / docker container cp for the "I know what I'm doing" situation.

While it obvious they can just run docker exec <container> as name is predicable with compose, for some reason (?) docker-compose exec was introduced and adopted by many, probably because users prefer to rely on the same tool for everything (when you get a hammer...). So, even with containers as target, a compose command makes sense to them. Or maybe this helps writing scripts that do not depend on te project name (included in container name) set by project dir?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, so it was mostly around "when it's needed to target a specific instance"; I can see a good use for docker compose exec (where it automatically picks the (first) instance of the service). Mostly for --index, because in that case, I would still need to do a docker compose ps to learn about what indexes I can pick.

Taking something like;

services:
  web:
    image: nginx:alpine
docker compose up -d --scale web=3
[+] Running 4/4
 ⠿ Network composescale_default  Created                                    0.1s
 ⠿ Container composescale-web-3  Started                                    0.7s
 ⠿ Container composescale-web-1  Started                                    0.6s
 ⠿ Container composescale-web-2  Started                                    0.7s

"just works":

docker compose exec web echo hello
hello

Exec'ing into a specific instance, my natural approach would be to;

Get a list of what's running;

docker compose ps
NAME                 COMMAND                  SERVICE             STATUS              PORTS
composescale-web-1   "/docker-entrypoint.…"   web                 running             80/tcp
composescale-web-2   "/docker-entrypoint.…"   web                 running             80/tcp
composescale-web-3   "/docker-entrypoint.…"   web                 running             80/tcp

Then, docker compose exec into the one I want to access;

docker compose exec composescale-web-2 sh -c 'echo hello world; echo do more things; echo do other things;'
service "composescale-web-2" is not running container #1

But that doesn't work currently (so then I need to use docker exec), as it will treat it as service-name (also somewhat expected)

So to do the same, I need to look at the output, pick the "index" / instance number, edit my command to pass it as --index;

docker compose exec --index=2 web sh -c 'echo hello world; echo do more things; echo do other things;'
hello world
do more things
do other things

(wondering if we should make the docker compose exec composescale-web-2 work 🤔)

Copy link
Contributor Author

@glours glours May 4, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not, @ndeloof @ulyssessouza WDYT? I can do that in a follow up PR if we all agree

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(let me "unresolve" so that it's not hidden)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docker compose exec <containername> doesn't make sense to me, the whole verbage used by compose is service-centric. Use of --index is unpleasant but I can't imagine a workaround. I'd just expect it's not required for service running 1 replica and user don't have to bother with this BUT when they try to diagnose a distributed-system issue with multiple replicas. But then, obviously, using plain container name with docker exec would be a natural option, and maybe we should deprecate --index support and suggest user to better use docker exec for this purpose. Or maybe this is fine we keep this as legacy/usability, as we - at least - share runExec implementation code!

@glours glours force-pushed the update-remove-cp-all-flag branch from b0b13e1 to 601dae1 Compare May 3, 2022 19:25
Comment on lines +69 to +70
flags.MarkHidden("all") //nolint:errcheck
flags.MarkDeprecated("all", "By default all the containers of the service will get the source file/directory to be copied.") //nolint:errcheck
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of the //nolint, perhaps just "fix" it in code (I know we disabled the errcheck linter in various repositories, because it's often a bit "too eager", and having the //nolint for all of them can be noisy; in this case _ = might be "less noisy", and makes it explicit we want to ignore the error);

Suggested change
flags.MarkHidden("all") //nolint:errcheck
flags.MarkDeprecated("all", "By default all the containers of the service will get the source file/directory to be copied.") //nolint:errcheck
_ = flags.MarkHidden("all")
_ = flags.MarkDeprecated("all", "By default all the containers of the service will get the source file/directory to be copied.")

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer we have noose by end of line with a nolint comment vs start of line, where my mind will focus reading, for useless assignment

@@ -64,8 +64,10 @@ func copyCommand(p *projectOptions, backend api.Service) *cobra.Command {
}

flags := copyCmd.Flags()
flags.IntVar(&opts.index, "index", 1, "Index of the container if there are multiple instances of a service [default: 1].")
flags.IntVar(&opts.index, "index", 0, "Index of the container if there are multiple instances of a service [default: 1 when copying from a container].")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps remove the [default: 1 when copying from a container]. altogether? (note that Cobra already prints defaults, so in most cases, there's no need to manually put it in the description)

Suggested change
flags.IntVar(&opts.index, "index", 0, "Index of the container if there are multiple instances of a service [default: 1 when copying from a container].")
flags.IntVar(&opts.index, "index", 0, "Index of the container if there are multiple instances of a service .")

As to the flag itself; are indexes guaranteed to start with 1? (i.e., could a container be removed, and it starts with 2 ?)

The "when copying from a container" is a bit confusing, as compose works with "services", correct? (in other words; I don't think there's an option to specify a container - for that you'd use docker cp or docker container cp.

Overall I'm wondering a bit if we should have this option; my train of thought here is that;

  • by default, we should assume all instances of a service to be identical
  • compose to interact with a compose "service" (so consider all to be identical)
  • it there IS a need to target a specific instance (which should be an exception to the rule); perhaps users should use docker cp / docker container cp for the "I know what I'm doing" situation.

@glours glours force-pushed the update-remove-cp-all-flag branch from 85001d1 to 2d9937b Compare May 4, 2022 15:21
@glours glours force-pushed the update-remove-cp-all-flag branch 2 times, most recently from 27be8e9 to b9e1f68 Compare May 4, 2022 15:35
@glours glours force-pushed the update-remove-cp-all-flag branch from b9e1f68 to 8b2eca4 Compare May 4, 2022 15:49
pkg/compose/cp.go Outdated Show resolved Hide resolved
pkg/compose/cp.go Outdated Show resolved Hide resolved
@glours glours force-pushed the update-remove-cp-all-flag branch 2 times, most recently from 369791a to 15feb17 Compare May 6, 2022 09:59
pkg/compose/cp.go Outdated Show resolved Hide resolved
}
return append(containers, container), nil
default:
containers, err = s.getContainers(ctx, projectName, oneOffExclude, true, serviceName)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps s.getContainers() should accept an index;

  • if index != 0; return the specified container (slice with one container)
  • otherwise return all containers

That way the code could be simplified / share more;

  • for copyFrom, it always picks the first from that list
  • for copyTo, it always iterates over the list of containers

Copy link
Member

@thaJeztah thaJeztah May 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like the first and last branch are basically "the same", so this could be;

func (s *composeService) listContainersTargetedForCopy(ctx context.Context, projectName string, index int, direction copyDirection, serviceName string) (Containers, error) {
	switch {
	case index > 0:
		container, err := s.getSpecifiedContainer(ctx, projectName, oneOffExclude, true, serviceName, index)
		if err != nil {
			return nil, err
		}
		return Containers{container}, nil
	default:
		containers, err := s.getContainers(ctx, projectName, oneOffExclude, true, serviceName)
		if err != nil {
			return nil, err
		}
		if len(containers) < 1 {
			return nil, fmt.Errorf("no container found for service %q", serviceName)
		}
		if direction == fromService {
			// copy from container defaults to the first container
			return containers[:1], nil
		}
		return containers, nil
	}
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer to keep the s.getContainers() as it because it's wildly used in the rest of the code. Having a bit more complexity in the copyFrom and copyTo seems to be a good tradeoff IHMO.

I updated the switch as you proposed 👍

if !options.All {
containers = containers.filter(indexed(options.Index))
}

g := errgroup.Group{}
for _, container := range containers {
containerID := container.ID
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't comment on the line below (and unrelated to this PR), but wondering why we do the validation of direction in the loop? The direction is defined above, and will always be the same for all containers, so I think we can return early (with an error, if needed) at the start of the function, so that we don't have to first collect the containers, and do the same check for each one of them.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I addressed it 😉

@glours glours force-pushed the update-remove-cp-all-flag branch from 15feb17 to f0774b6 Compare May 6, 2022 14:01
@glours glours force-pushed the update-remove-cp-all-flag branch from f0774b6 to 046d6ca Compare May 6, 2022 17:41
@ndeloof ndeloof merged commit a603e27 into docker:v2 May 10, 2022
@glours glours deleted the update-remove-cp-all-flag branch January 11, 2023 14:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants