Skip to content

Latest commit

 

History

History
265 lines (190 loc) · 10.3 KB

File metadata and controls

265 lines (190 loc) · 10.3 KB

Docker target

Use Docker target to run/test your docker action. action.yml file should have runs.using key equal docker.

Builds and runs specified docker image. Works with native docker on Linux (including GitHub runners) and Docker Desktop on MacOS and Windows.

Paths in container

Unlike other targets, docker target has fixed path of faked dirs and files inside a container. But relying on them is not recommended. You should use GitHub environment variables values instead.

Environment variable Value Description
RUNNER_TEMP /home/runner/work/_temp Temp dir
GITHUB_WORKSPACE /github/workspace The default working directory on the runner for steps, and the default location of a repository when using the checkout action.
GITHUB_EVENT_PATH /github/workflow/event.json The path of the file with the complete webhook event payload.
GITHUB_ENV /github/file_commands/ENV file for exported vars
GITHUB_PATH /github/file_commands/PATH file for added paths
GITHUB_OUTPUT /github/file_commands/OUTPUT file for setting outputs
GITHUB_STATE /github/file_commands/STATE file for setting state

As in other targets, temporary created dirs used by default to fake these paths.

As usual, you can specify existing host dirs instead (options.setTempDir(), options.setWorkspaceDir()).

In both cases dirs will be mounted as volumes to a fixed paths in container and corresponding env variables will point to these paths.

Create by passing a path to action.yml file

import {RunTarget, RunOptions} from 'github-action-ts-run-api';

// Read the details below
const dockerOptions = {};

// Use Dockerfile from `runs.image` key of action.yml
const target = RunTarget.dockerAction('path/to/action.yml', dockerOptions);

const result = target.run(RunOptions.create());

Create by passing a path to Dockerfile

This approach can be used if you want to perform an integration test run not the whole action, but only a part of it. In this case you can create a separate Dockerfile that will have only needed functionality and run tests against it.

import {RunTarget, RunOptions} from 'github-action-ts-run-api';

// Read the details below
const dockerOptions = {};

// No action config was specified. 
// * Default input values will not be applied
// * Container args will not be passed to a container
const target1 = RunTarget.dockerFile('path/to/Dockerfile', undefined, dockerOptions);

// Read action config from action.yml file. 
// * Default input values will be used from the inputs section.
// * Container args defined in the action config will be passed to a container
const target2 = RunTarget.dockerFile('actionSrc/file.js', 'path/to/action.yml', dockerOptions);

// Pass already parsed action config. 
// * Default input values will be used from the inputs section.
// * Container args defined in the action config will be passed to a container
const target3 = RunTarget.dockerFile('actionSrc/file.js', parsedYmlObject, dockerOptions);

const result = target.run(RunOptions.create());

Docker options

Passing the optional dockerOptions argument is demonstrated in the examples above.

It has 2 fields that you can set:

🔸 runUnderCurrentLinuxUser

In the documentation, GitHub recommends running Docker action under root user in a container.

While testing your action on local Linux machine it can cause troubles if root user in container creates files in volumes mounted to the container. Since you normally operate as a non-root user on your development machine, testing an action you will not be able to access/delete created files.

For this reason Docker target by default runs a container under current user on Linux. On macOS and Windows systems where Docker Desktop is normally used, it will be still run under root by default, because Docker Desktop doesn't map file permissions directly to host files, and they will be still available for a local user.

  • true (default): run a container with uid and gid of a current user (only for Linux).
  • false: run under root user (uid = 0, gid = 0).

🔸 network

A name of the docker network to attach a container to.

  • undefined (default): attach to default bridge network
  • string name: attach to a network by name

This option can be used to mock some external services by containers with stub HTTP servers.

Run result

Fields

🔸 buildSpawnResult

Contains a result of a spawning of child docker build command. After a second and subsequent run() calls can be undefined, because image id has been cached.

🔸 spawnResult

Contains a result of a spawning of child docker run command or undefined if the build command failed first.

🔸 isSuccessBuild

Indicates whether docker build command was successful.

🔹 isSuccess

Indicates whether both docker build and docker run commands were successful.

Examples

Usage examples can be found in DockerTarget.test.ts.

Remarks

🔻 Docker Desktop for Windows and MacOS behaves differently from native docker on Linux. Be aware!

🔻 Windows and MacOS GitHub hosted runners don't have installed docker.

🔻 Faked dirs and command files are mounted as volumes.

Utilities

Returns the host that can be used inside container to access the Docker host machine.

See "Stubbing GitHub API by local NodeJS HTTP server" example below.

The wrapper that starts/stops a docker compose file around a callback. Performs docker compose up, then runs callback function and after its promise fulfilled, runs docker compose down.

See "Stubbing GitHub API by HTTP server container" example below.

Testing techniques

💡 Stubbing GitHub API by local NodeJS HTTP server

This approach relates to stubbing any external service:

  • Read base API URL from environment variable, use default if variable is not set
  • For GitHub API there are dedicated GITHUB_API_URL, GITHUB_SERVER_URL, GITHUB_GRAPHQL_URL variables.
  • Start local HTTP stub server, pass its address to the needed env variable.
Show example code

entrypoint.sh:

echo "::set-output name=out1::$(curl "$GITHUB_API_URL"/repos/owner/repo/releases)"

action.test.ts:

import {RunTarget, RunOptions, getDockerHostName} from 'github-action-ts-run-api';

const port = 8234;
const server = http.createServer((req, res) => {
    if (req.url === '/repos/owner/repo/releases') {
        res.writeHead(200);
        res.end('fake_response');
    } else {
        res.writeHead(404);
        res.end();
    }
}).listen(port);

try {
    const target = RunTarget.dockerAction('path/to/action.yml');
    const res = await target.run(RunOptions.create()
        // Using '172.17.0.1' or 'host.docker.internal' 
        // the action container can access the host  
        .setGithubContext({apiUrl: `http://${getDockerHostName()}:${port}`})
    );
    assert(res.commands.outputs.out1 === 'fake_response');
} finally {
    server.close();
}

You can find actual working code in DockerTarget.test.ts.

💡 Stubbing GitHub API by HTTP server container

It's the similar approach to the described above:

  • Use a docker container defined as a service in docker-compose.yml to run your stub server.
  • Define a named network in docker-compose.yml.
  • Attach an action container to the network and make requests to the stub service by name.
Show example code

entrypoint.sh:

echo "::set-output name=out1::$(curl "$GITHUB_API_URL"/repos/owner/repo/releases)"

docker-compose.yml:

version: "3.5"
services:
  fake-server:
    build:
      # Image with stub GitHub API HTTP server
      context: httpServerDir
    ports:
      - "80:80"
networks:
  default:
    # Assign a name to the network to connect 
    # the tested action container to it
    name: testNet

action.test.ts:

import {RunTarget, RunOptions, withDockerCompose} from 'github-action-ts-run-api';

await withDockerCompose(
    'path/to/docker-compose.yml',
    async () => {
        const target = RunTarget.dockerAction(
            'path/to/action.yml', 
            // network name defined in docker-compose.yml    
            {network: 'testNet'}
        );
        const res = await target.run(RunOptions.create()
            // service name defined in docker-compose.yml
            .setGithubContext({apiUrl: `http://fake-server:80`})
        );
        assert(res.commands.outputs.out1 === 'fake_response');
});

You can find actual working code in DockerTarget.test.ts.