diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml new file mode 100644 index 00000000..14ef7f25 --- /dev/null +++ b/.github/workflows/docker-publish.yml @@ -0,0 +1,55 @@ +name: Docker + +on: + workflow_dispatch: + inputs: + tagInput: + description: 'Tag' + required: true + + release: + types: [created] + tags: + - 'v*' + +jobs: + build-and-push-image: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - + name: Checkout + uses: actions/checkout@v4 + - + name: Set up QEMU + uses: docker/setup-qemu-action@v3 + - + name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - + name: Login to Docker Hub + uses: docker/login-action@v3 + with: + registry: docker.io + username: ${{ secrets.DOCKER_HUB_USER }} + password: ${{ secrets.DOCKER_HUB_TOKEN }} + - name: Determine version tag + id: version-tag + run: | + INPUT_VALUE="${{ github.event.inputs.tagInput }}" + if [ -z "$INPUT_VALUE" ]; then + INPUT_VALUE="${{ github.ref_name }}" + fi + echo "::set-output name=value::$INPUT_VALUE" + - + name: Build and push + uses: docker/build-push-action@v6 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: true + tags: | + tighten/takeout:latest + tighten/takeout:${{ steps.version-tag.outputs.value }} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..258204bd --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +FROM php:8.1-cli-alpine + +ENV TAKEOUT_CONTAINER=1 + +COPY --from=docker/buildx-bin /buildx /usr/libexec/docker/cli-plugins/docker-buildx + +# Install the PHP extensions & Docker +RUN apk add --no-cache --update docker openrc ncurses \ + && docker-php-ext-configure pcntl --enable-pcntl \ + && docker-php-ext-install -j$(nproc) pcntl \ + && rc-update add docker boot + +WORKDIR /takeout + +COPY builds/takeout /usr/local/bin/takeout + +ENTRYPOINT ["takeout"] + diff --git a/README.md b/README.md index bebba6f5..13f540f9 100644 --- a/README.md +++ b/README.md @@ -18,18 +18,46 @@ But you can also easily enable ElasticSearch, PostgreSQL, MSSQL, Mongo, Redis, a ## Requirements - macOS, Linux, Windows 10 or WSL2 -- [Composer](https://getcomposer.org/) installed - Docker installed (macOS: [Docker for Mac](https://docs.docker.com/docker-for-mac/), Windows: [Docker for Windows](https://docs.docker.com/docker-for-windows/)) +If you opt for the PHP/Composer installation (not recommended), you also need: + +- PHP installed (latest major version) +- Composer installed + ## Installation -Install Takeout with Composer by running: +The recommended way to install Takeout is the dockerized version via an alias (add this to your `~/.bashrc`, `~/.zshrc` or equivalent). + +On Linux or macOS, use: + +```bash +alias takeout="docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -it tighten/takeout:latest" +``` + +On Windows 10, if you're using Bash, use: + +```bash +alias takeout="docker run --rm -v //var/run/docker.sock:/var/run/docker.sock -it tighten/takeout:latest" +``` + +On Windows 10, if you're using PowerShell, use: + +```bash +function takeout { docker run --rm -v //var/run/docker.sock:/var/run/docker.sock -it tighten/takeout:latest $args } +``` + +That's it. You may now use Takeout on your terminal. The first time you use this alias, it will pull the Takeout image from Docker Hub. + +To update the image, run `docker pull tighten/takeout` when you want to get the newest release. + +Otherwise, if you have a PHP environment available, you may install Takeout via Composer: ```bash -composer global require "tightenco/takeout:~2.8" +composer global require "tightenco/takeout:~2.9" ``` -Make sure the `~/.composer/vendor/bin` directory is in your system's "PATH". +If you use the PHP/Composer installation, make sure you're on the latest version of PHP. We'll only support the current major version of PHP using this installation approach. ## Usage @@ -274,4 +302,31 @@ If you're working with us and are assigned to push a release, here's the easiest 6. [Draft a new release](https://github.com/tighten/takeout/releases/new) with both the tag version and release title of your tag (e.g. `v1.5.1`) 7. Use the "Generate release notes" button to generate release notes from the merged PRs. 8. Hit `Publish release` -9. Profit 😆 +9. The new tag and release will trigger the [`docker-publish.yml`](.github/workflows/docker-publish.yml) workflow, which should take care of building and pushing the new image of the Docker container (see the "Building The Docker Image Manually" section below) +10. Profit 😆 + +## Building The Docker Image Manually + +The important thing is to remember to build both `linux/amd64` and `linux/arm64` images. We rely on Docker's `buildx` command, which uses Docker's [BuildKit](https://github.com/moby/buildkit) behind the scenes, which allows us to build for multiple platforms, independently of the platform of the machine building the image. + +You may build and publish a new version of the docker image using the following command: + +```bash +docker buildx build --platform=linux/amd64,linux/arm64 -t tighten/takeout:latest --push . +``` + +If it's the first time you're building the image, you may get the following error: + +``` +ERROR: Multiple platforms feature is currently not supported for docker driver. Please switch to a different driver (eg. "docker buildx create --use") +``` + +This means that you first need to create a builder container, which you maydo like so: + +```bash +docker buildx create --use +``` + +After that, retrying the `buildx` command should work. + +Please, note that building the container will simply copy the current version of the Takeout `phar` file at [builds/takeout](./builds/takeout) to inside the container and publish that, so make sure you have to most recent version built locally. If you don't, follow the release process to build the new version before building the Docker image. diff --git a/app/Shell/DockerTags.php b/app/Shell/DockerTags.php index 0c8bc015..c4dfa45d 100644 --- a/app/Shell/DockerTags.php +++ b/app/Shell/DockerTags.php @@ -44,22 +44,18 @@ public function getLatestTag(): string public function getTags(): Collection { $response = json_decode($this->getTagsResponse()->getContents(), true); + $platform = $this->platform(); return collect($response['results']) - ->when(in_array($platform, $this->armArchitectures, true), $this->armSupportedImagesOnlyFilter()) - ->when(! in_array($platform, $this->armArchitectures, true), $this->nonArmOnlySupportImagesFilter()) + ->when(in_array($platform, $this->armArchitectures, true), $this->onlyArmImagesFilter()) + ->when(! in_array($platform, $this->armArchitectures, true), $this->onlyNonArmImagesFilter()) ->pluck('name') ->sort(new VersionComparator) ->values(); } - /** - * Return a function intended to filter tags, ensuring images that do not support arm architecture are filtered out. - * - * @return callable - */ - protected function armSupportedImagesOnlyFilter() + protected function onlyArmImagesFilter() { return function ($tags) { return $tags->filter(function ($tag) { @@ -76,12 +72,7 @@ protected function armSupportedImagesOnlyFilter() }; } - /** - * Return a function intended to filter tags, that ensures are arm-only images are filtered out. - * - * @return callable - */ - protected function nonArmOnlySupportImagesFilter() + protected function onlyNonArmImagesFilter() { return function ($tags) { return $tags->filter(function ($tag) { diff --git a/tests/Feature/DockerTagsTest.php b/tests/Feature/DockerTagsTest.php index 4d976b39..2d0804e4 100644 --- a/tests/Feature/DockerTagsTest.php +++ b/tests/Feature/DockerTagsTest.php @@ -10,12 +10,19 @@ use GuzzleHttp\HandlerStack; use GuzzleHttp\Psr7\Response; use Mockery as M; -use Tests\Support\IntelDockerTags; -use Tests\Support\M1DockerTags; +use Tests\Support\FakePlatformDockerTags; use Tests\TestCase; class DockerTagsTest extends TestCase { + public static function armPlatforms(): array + { + return [ + [FakePlatformDockerTags::M1_ARM_PLATFORM], + [FakePlatformDockerTags::LINUX_ARM_PLATFORM], + ]; + } + /** @test */ function it_gets_the_latest_tag_not_named_latest() { @@ -45,14 +52,17 @@ function it_sorts_the_versions_naturally() $this->assertEquals('17.2', $tags->shift()); } - /** @test */ - function it_detects_arm64_based_images_when_running_on_arm64_based_host() + /** + * @test + * + * @dataProvider armPlatforms + */ + function it_detects_arm_based_images_when_running_on_arm64_based_host($platform) { $handlerStack = HandlerStack::create($this->mockImagesResponseHandler()); $client = new Client(['handler' => $handlerStack]); - /** @var DockerTags $dockerTags */ - $dockerTags = M::mock(M1DockerTags::class, [$client, app(MySql::class)])->makePartial(); + $dockerTags = (new FakePlatformDockerTags($client, app(MySql::class)))->withFakePlatform($platform); $this->assertEquals('1.0.0-arm64', $dockerTags->getLatestTag()); } @@ -63,8 +73,7 @@ function it_gets_latest_tag_on_intel_platform() $handlerStack = HandlerStack::create($this->mockImagesResponseHandler()); $client = new Client(['handler' => $handlerStack]); - /** @var DockerTags $dockerTags */ - $dockerTags = M::mock(IntelDockerTags::class, [$client, app(MySql::class)])->makePartial(); + $dockerTags = (new FakePlatformDockerTags($client, app(MySql::class)))->withFakePlatform(FakePlatformDockerTags::INTEL_ARM_PLATFORM); $this->assertEquals('1.0.0', $dockerTags->getLatestTag()); } @@ -77,13 +86,14 @@ private function mockImagesResponseHandler() [ 'name' => 'latest', 'images' => [ + ['architecture' => 'x86_64'], ['architecture' => 'amd64'], - ['architecture' => 'arm64'], ], ], [ 'name' => '1.0.0', 'images' => [ + ['architecture' => 'x86_64'], ['architecture' => 'amd64'], ], ], @@ -91,6 +101,7 @@ private function mockImagesResponseHandler() 'name' => '1.0.0-arm64', 'images' => [ ['architecture' => 'arm64'], + ['architecture' => 'aarch64'], ], ], ], diff --git a/tests/Support/FakePlatformDockerTags.php b/tests/Support/FakePlatformDockerTags.php new file mode 100644 index 00000000..dc4b7dc4 --- /dev/null +++ b/tests/Support/FakePlatformDockerTags.php @@ -0,0 +1,26 @@ +fakePlatform = $platform; + + return $this; + } + + protected function platform(): string + { + return $this->fakePlatform; + } +} diff --git a/tests/Support/IntelDockerTags.php b/tests/Support/IntelDockerTags.php deleted file mode 100644 index 423c0003..00000000 --- a/tests/Support/IntelDockerTags.php +++ /dev/null @@ -1,13 +0,0 @@ -