Skip to content
This repository has been archived by the owner on Dec 8, 2023. It is now read-only.

Init xk6 Docker plugin #1

Merged
merged 21 commits into from
Mar 11, 2021
Merged

Init xk6 Docker plugin #1

merged 21 commits into from
Mar 11, 2021

Conversation

lxkuz
Copy link
Collaborator

@lxkuz lxkuz commented Mar 2, 2021

The update includes the next docker methods:

  • docker ps
  • docker start
  • docker stop
  • docker pause
  • docker unpause
  • docker inspect
  • docker logs
  • docker exec

@lxkuz lxkuz requested a review from simskij March 2, 2021 21:00
Readme.md Outdated
/ __________ \ |__| \__\ \_____/ .io

execution: local
script: ../xk6-docker/test.js
Copy link
Member

Choose a reason for hiding this comment

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

Nit: The script name it's not the same (test.js->script.js).

docker.go Outdated
func (d *Docker) Ps() []string {
cli, err := client.NewClientWithOpts(client.FromEnv)
if err != nil {
panic(err)
Copy link
Member

@dgzlopes dgzlopes Mar 5, 2021

Choose a reason for hiding this comment

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

I think it's better to return the error. But probably @simskij knows better :)

Copy link

Choose a reason for hiding this comment

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

Yes, I agree that we should return the error here rather than panicking. The docker client erroring should probably not be considered fatal for the whole VU goroutine. 🤔

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

docker.go Outdated
result := make([]string, 0, len(containers))

for _, container := range containers {
result = append(result, fmt.Sprintf("%s %s %s", container.ID[:10], container.Image, container.Command))
Copy link
Member

@dgzlopes dgzlopes Mar 5, 2021

Choose a reason for hiding this comment

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

Perhaps it's better to return a struct, instead of a string with the concatenated values.

From my POV it's easier to consume from JS.

Copy link

@simskij simskij Mar 5, 2021

Choose a reason for hiding this comment

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

I agree with @dgzlopes on this one. If the struct itself is too big/contains a lot of properties we don't care about, I'd suggest remapping it to a bespoke struct with reasonable properties and proper json tags.

Consider a scenario where I want to list my containers and get the command used to launch one named whatever-nginx. I'd then. have to regex parse the string from the JS runtime, which likely won't be that fast. If I got a json object on the other hand, it wouldn't be a big deal if I had to console.log the object myself.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@simskij please take a look at rework commit. I tried to keep the initial interface that came from docker go lib. Example js file shows how to get normal docker ps output now. IMO the benefits of this approach are transparent support of https://github.com/moby/moby/tree/master/client functionality. WDYT?

Copy link

Choose a reason for hiding this comment

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

I definitely agree!

If anything, I think it could end up being too much. I don't want to assume too much docker knowledge from the user. But let's start from here. If this extension takes off and people seem to be confused with how to use the props for some reason, we could always limit it in a future version.

Copy link
Member

@dgzlopes dgzlopes left a comment

Choose a reason for hiding this comment

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

I'm excited about this extension :)

Left some comments (for @simme primarily). Also, I think you should vendor your deps!

@simme
Copy link

simme commented Mar 5, 2021

I'm guessing you meant to mention @simskij :)

@dgzlopes
Copy link
Member

dgzlopes commented Mar 5, 2021

Oh, sorry @simme. I can't trust GH auto-complete 😄

docker.go Outdated Show resolved Hide resolved
docker.go Outdated
func (d *Docker) Ps() []string {
cli, err := client.NewClientWithOpts(client.FromEnv)
if err != nil {
panic(err)
Copy link

Choose a reason for hiding this comment

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

Yes, I agree that we should return the error here rather than panicking. The docker client erroring should probably not be considered fatal for the whole VU goroutine. 🤔

@simskij
Copy link

simskij commented Mar 5, 2021

Great work! 👏🏼   Added a few comments.

@lxkuz lxkuz changed the title [WIP] Add go project with k6 extention blank Add go project with k6 extention blank Mar 7, 2021
@lxkuz
Copy link
Collaborator Author

lxkuz commented Mar 7, 2021

@simskij @dgzlopes Ok I'm removing WIP label as my PR becomes huge and I would like to wrap it up and continue work in tiny PRs. I reworked it since your last review and added the list of docker commands (listed in PR description).
What issues I got:

  1. docker run seems more difficult, will cover it in the next PR.
  2. I did not vendor my deps, please let me know how should I do it properly.
  3. docker logs works weird, see the example. I guess it connected with the buffer somehow, I don't have an exact explanation.

@lxkuz lxkuz requested review from dgzlopes and simskij March 7, 2021 08:46
@dgzlopes
Copy link
Member

dgzlopes commented Mar 7, 2021

Yup! If we've smaller PRs, it's easier to review them.

To vendor the dependencies, usually, I run go mod tidy and then go mod vendor. Once you've done that, you can add and commit the vendor folder.

@lxkuz lxkuz changed the title Add go project with k6 extention blank Init xk6 Docker plugin Mar 9, 2021
Copy link

@simskij simskij left a comment

Choose a reason for hiding this comment

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

Looks superb! That was really fast of you! 🎉 👍🏼
Left some minor suggestions.

Comment on lines +19 to +25
func (d *Containers) SetupClient() {
cli, err := client.NewClientWithOpts(client.FromEnv)
if err != nil {
panic(err)
}
d.Client = cli
}
Copy link

Choose a reason for hiding this comment

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

Maybe we should allow the user to configure this by supplying a config object as well and not strictly enforce config through env vars?

I'm thinking that if an object is supplied as the argument to the function, we should treat that as a client config object, if not - default to FromEnv. If we decide to go for this, it could be good if we also cover it with a unit test for each of the paths.

Copy link
Collaborator Author

@lxkuz lxkuz Mar 11, 2021

Choose a reason for hiding this comment

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

So you mean the usage will look like this?

import containers from 'k6/x/docker/containers';

export default function () {
  const containerId = 'YOUR_CONTAINER_ID';
  const env = {
    ....
  }
  containers.setup(env)
  containers.startContainer(containerId, {attach: true});
}

Copy link

Choose a reason for hiding this comment

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

Yeah, exactly! 👍🏼

Readme.md Outdated

Result output:

```bash
Copy link

Choose a reason for hiding this comment

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

plain might be preferable here given the weird syntax highlighting of the output?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

containers.go Outdated
Comment on lines 28 to 81
func (d *Containers) ListContainers(options types.ContainerListOptions) ([]types.Container, error) {
containers, err := d.Client.ContainerList(context.Background(), options)
return containers, err
}

// StartContainer works as Docker start command
func (d *Containers) StartContainer(containerID string, options types.ContainerStartOptions) error {
return d.Client.ContainerStart(context.Background(), containerID, options)
}

// StopContainer works as Docker stop command
func (d *Containers) StopContainer(containerID string) error {
// TODO: Add timeout option support
timeout := 0 * time.Second

return d.Client.ContainerStop(context.Background(), containerID, &timeout)
}

// PauseContainer works as Docker pause command
func (d *Containers) PauseContainer(containerID string) error {
return d.Client.ContainerPause(context.Background(), containerID)
}

// UnpauseContainer works as Docker unpause command
func (d *Containers) UnpauseContainer(containerID string) error {
return d.Client.ContainerUnpause(context.Background(), containerID)
}

// Logs works as Docker logs command
func (d *Containers) Logs(containerID string, options types.ContainerLogsOptions) (string, error) {
response, err := d.Client.ContainerLogs(context.Background(), containerID, options)
buf := new(bytes.Buffer)

if err != nil {
return "", err
}

buf.ReadFrom(response)
return buf.String(), nil
}

// InspectContainer works as Docker inspect command
func (d *Containers) InspectContainer(containerID string) (types.ContainerJSON, error) {
return d.Client.ContainerInspect(context.Background(), containerID)
}

// ExecContainer works as Docker exec command
func (d *Containers) ExecContainer(containerID string, config types.ExecConfig) error {
response, err := d.Client.ContainerExecCreate(context.Background(), containerID, config)
if err != nil {
return err
}
return d.Client.ContainerExecStart(context.Background(), response.ID, types.ExecStartCheck{})
}
Copy link

Choose a reason for hiding this comment

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

Now that I think about it, and see it in the context of the other commands, maybe we should omit Container from the method names?

containers.listContainers and containers.unpauseContainer looks a bit excessive in comparison to containers.list and containers.unpause, don't you think? Since they are all available from the containers object, it should be quite obvious what they refer to anyhow.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

tail: 20,
// defaults: true
}
const result = containers.logs(containerId, options);
Copy link

Choose a reason for hiding this comment

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

This goes for all of the functions that take an options object: Should we provide some "sane" defaults in this case if a user omits the options object from the call? Or is the docker client already taking care of that?


export default function () {
const containerId = 'YOUR_CONTAINER_ID';
containers.pauseContainer(containerId, {attach: true});
Copy link

Choose a reason for hiding this comment

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

Suggested change
containers.pauseContainer(containerId, {attach: true});
containers.pauseContainer(containerId);

implementation does not take any args

Copy link
Collaborator Author

Choose a reason for hiding this comment

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


export default function () {
const containerId = 'YOUR_CONTAINER_ID';
containers.startContainer(containerId, {attach: true});
Copy link

Choose a reason for hiding this comment

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

What would attaching even mean in this context? That we block until the container exits?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This interface is wrong, I just remove it and keep containerId only

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@simskij
Copy link

simskij commented Mar 10, 2021

As discussed on slack, I'd also suggest having a look at whether we can generate typescript types for the extension using something like https://github.com/OneOfOne/struct2ts or https://github.com/tkrajina/typescriptify-golang-structs. This would definitely improve the developer experience while writing scripts, regardless if the user is using typescript or not.

@simskij
Copy link

simskij commented Mar 11, 2021

Yeah, I'll go ahead and merge this and then we can continue in smaller PR:s. 👍🏼

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants