diff --git a/.commitlintrc b/.commitlintrc new file mode 100644 index 0000000..f210891 --- /dev/null +++ b/.commitlintrc @@ -0,0 +1,92 @@ +{ + "defaultIgnores": true, + "extends": "@commitlint/config-conventional", + "rules": { + "scope-enum": [ + 2, + "always", + [ + "symfony", + "doctrine", + "github", + "ci", + "deps", + "docker-image", + "github-action", + "docker", + "pihole" + ] + ], + "scope-empty": [ + 0 + ], + "body-leading-blank": [ + 1, + "always" + ], + "body-max-line-length": [ + 2, + "always", + 100 + ], + "footer-leading-blank": [ + 1, + "always" + ], + "footer-max-line-length": [ + 2, + "always", + 100 + ], + "header-max-length": [ + 2, + "always", + 100 + ], + "subject-case": [ + 2, + "never", + [ + "sentence-case", + "start-case", + "pascal-case", + "upper-case" + ] + ], + "subject-empty": [ + 2, + "never" + ], + "subject-full-stop": [ + 2, + "never", + "." + ], + "type-case": [ + 2, + "always", + "lower-case" + ], + "type-empty": [ + 2, + "never" + ], + "type-enum": [ + 2, + "always", + [ + "build", + "chore", + "ci", + "docs", + "feat", + "fix", + "perf", + "refactor", + "revert", + "style", + "test" + ] + ] + } +} \ No newline at end of file diff --git a/.github/renovate.json b/.github/renovate.json new file mode 100644 index 0000000..37136ff --- /dev/null +++ b/.github/renovate.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["local>kilip/renovate-config"], + "semanticCommits": "enabled" +} diff --git a/.github/workflows/build-image.yaml b/.github/workflows/build-image.yaml index 587fd37..3275922 100644 --- a/.github/workflows/build-image.yaml +++ b/.github/workflows/build-image.yaml @@ -2,14 +2,15 @@ name: "Build Image" on: + workflow_call: workflow_dispatch: - push: - branches: ["main"] jobs: build-and-test: name: Build and test runs-on: ubuntu-latest + env: + DOCKER_IMAGE: ghcr.io/${{ github.repository }} steps: - uses: actions/checkout@v4 with: @@ -21,9 +22,16 @@ jobs: run: |- echo "build_date=$(date --rfc-3339=seconds --utc)" >> $GITHUB_OUTPUT echo "goss_args=tail -f /dev/null" >> $GITHUB_OUTPUT - echo "tag_version=docker-dns:latest" >> $GITHUB_OUTPUT - echo "tag_testing=docker-dns:testingz" >> $GITHUB_OUTPUT - echo "tag_rolling=docker-dns:rolling" >> $GITHUB_OUTPUT + echo "tag_testing=testingz" >> $GITHUB_OUTPUT + echo "tag_rolling=rolling" >> $GITHUB_OUTPUT + + date=$(date +'%Y%m%d%H%M%S') + ref="${{ github.ref }}" + if [[ $ref == ref/tags/v* ]]; then + echo "tag_version=${{ github.ref_name }}" >> $GITHUB_OUTPUT + else + echo "tag_version=daily${date}" >> $GITHUB_OUTPUT + fi - name: Setup Goss uses: e1himself/goss-installation-action@v1.2.1 @@ -50,9 +58,9 @@ jobs: build-args: |- VERSION=latest context: . - platforms: linux/amd64 # load does not support muti-arch https://github.com/docker/buildx/issues/290 + platforms: linux/amd64, linux/arm64 load: true - tags: ghcr.io/${{ github.repository_owner }}/${{ steps.vars.outputs.tag_testing }} + tags: ${{ env.DOCKER_IMAGE }}:testingz cache-from: type=gha cache-to: type=gha,mode=max @@ -66,29 +74,33 @@ jobs: GOSS_SLEEP: 2 GOSS_FILES_STRATEGY: cp CONTAINER_LOG_OUTPUT: goss_container_log_output - run: dgoss run ghcr.io/${{ github.repository_owner }}/${{ steps.vars.outputs.tag_testing }} ${{ steps.vars.outputs.goss_args }} + run: dgoss run ${{ env.DOCKER_IMAGE }} ${{ steps.vars.outputs.goss_args }} - name: Build all platforms id: release uses: docker/build-push-action@v6 with: - build-args: |- - VERSION=${{ steps.vars.outputs.tag_version }} - CHANNEL=stable labels: |- - ${{ steps.vars.outputs.chan_label_type }}.created="${{ steps.vars.outputs.build_date }}" - ${{ steps.vars.outputs.chan_label_type }}.title="docker-dns (stable)" - ${{ steps.vars.outputs.chan_label_type }}.version="${{ steps.vars.outputs.tag_version }}" - ${{ steps.vars.outputs.chan_label_type }}.authors="Anthonius Munthi " - ${{ steps.vars.outputs.chan_label_type }}.url="https://github.com/kilip/docker-dns" - ${{ steps.vars.outputs.chan_label_type }}.build.url="https://github.com/kilip/docker-dns/actions/runs/${{ github.run_id }}" - ${{ steps.vars.outputs.chan_label_type }}.documentation="https://github.com/kilip/docker-dns/README.md" - ${{ steps.vars.outputs.chan_label_type }}.revision="${{ github.sha }}" + org.opencontainers.image.created="${{ steps.vars.outputs.build_date }}" + org.opencontainers.image.title="docker-dns (stable)" + org.opencontainers.image.version="${{ steps.vars.outputs.tag_version }}" + org.opencontainers.image.authors="Anthonius Munthi " + org.opencontainers.image.url="https://github.com/kilip/docker-dns" + org.opencontainers.image.build.url="https://github.com/kilip/docker-dns/actions/runs/${{ github.run_id }}" + org.opencontainers.image.documentation="https://github.com/kilip/docker-dns/README.md" + org.opencontainers.image.revision="${{ github.sha }}" context: . - platforms: linux/amd64, linux/arm64 + platforms: linux/amd64 push: true - tags: |- - ghcr.io/${{ github.repository_owner }}/${{ steps.vars.outputs.tag_rolling }} - ghcr.io/${{ github.repository_owner }}/${{ steps.vars.outputs.tag_version }} + tags: ${{ env.DOCKER_IMAGE }}:${{ steps.vars.outputs.tag_version }} cache-from: type=gha cache-to: type=gha,mode=max + + - name: Push latest tags + if: startsWith(github.ref, 'refs/tags/v') + run: | + docker tag ${{ env.DOCKER_IMAGE }}:${{ steps.vars.outputs.tag_version }} ${{ env.DOCKER_IMAGE }}:latest + docker push ${{ env.DOCKER_IMAGE }}:latest + + docker tag ${{ env.DOCKER_IMAGE }}:${{ steps.vars.outputs.tag_version }} ${{ env.DOCKER_IMAGE }}:rolling + docker push ${{ env.DOCKER_IMAGE }}:rolling diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml new file mode 100644 index 0000000..dc70ff9 --- /dev/null +++ b/.github/workflows/check.yaml @@ -0,0 +1,116 @@ +--- +name: Static Check + +on: + workflow_call: + +env: + COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +jobs: + commitlint: + if: github.event_name == 'pull_request' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Run commitlint + run: | + commit=$(gh api \ + /repos/${{ github.repository }}/pulls/${{github.event.number}}/commits \ + | jq -r '.[0].commit.message' \ + | head -n 1) + # we can't use npx see https://github.com/conventional-changelog/commitlint/issues/613 + echo '{}' > package.json + npm install --no-fund --no-audit @commitlint/config-conventional @commitlint/cli + echo $commit | ./node_modules/.bin/commitlint -g .commitlintrc + + php-cs-fixer: + name: PHP CS Fixer (PHP ${{ matrix.php }}) + runs-on: ubuntu-latest + timeout-minutes: 20 + strategy: + matrix: + php: + - "8.3" + fail-fast: false + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: intl, bcmath, curl, openssl, mbstring, mongodb + ini-values: memory_limit=-1 + tools: pecl, composer, php-cs-fixer + coverage: none + - name: Run PHP-CS-Fixer fix + run: php-cs-fixer fix --dry-run --diff --ansi + + phpstan: + name: PHPStan (PHP ${{ matrix.php }}) + runs-on: ubuntu-latest + timeout-minutes: 20 + strategy: + matrix: + php: + - "8.3" + fail-fast: false + env: + APP_DEBUG: "1" # https://github.com/phpstan/phpstan-symfony/issues/37 + SYMFONY_PHPUNIT_VERSION: "9.6" + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + tools: pecl, composer + extensions: intl, bcmath, curl, openssl, mbstring, mongodb + coverage: none + ini-values: memory_limit=-1 + - name: Get composer cache directory + id: composercache + run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + - name: Cache dependencies + uses: actions/cache@v4 + with: + path: ${{ steps.composercache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-composer- + - name: Update project dependencies + run: | + composer update --no-interaction --no-progress --ansi + - name: Cache PHPStan results + uses: actions/cache@v4 + with: + path: /tmp/phpstan + key: phpstan-php${{ matrix.php }}-${{ github.sha }} + restore-keys: | + phpstan-php${{ matrix.php }}- + phpstan- + continue-on-error: true + - name: Clear test app cache + run: | + bin/console cache:clear + - name: Run PHPStan analysis + run: | + ./vendor/bin/phpstan --version + ./vendor/bin/phpstan analyse --no-interaction --no-progress --ansi + + docker: + name: Lint Docker Files + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Lint Dockerfiles + uses: hadolint/hadolint-action@v3.1.0 + with: + recursive: true diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..8510fd3 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,41 @@ +--- +name: CI + +on: + push: + branches: ["main"] + tags: + - v[0-9]+.[0-9]+.[0-9]+ + pull_request: + branches: ["main"] + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +env: + COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +jobs: + check: + name: Static Check + uses: kilip/docker-dns/.github/workflows/check.yaml@main + secrets: inherit + + test: + name: Testing + needs: ["check"] + if: ${{ needs.check.result != 'failure' }} + uses: kilip/docker-dns/.github/workflows/test.yaml@main + secrets: inherit + + build-images: + name: Build + needs: ["test"] + if: ${{ needs.test.result != 'failure' && github.event_name != 'pull_request' }} + uses: "kilip/docker-dns/.github/workflows/build-image.yaml@main" + secrets: inherit + permissions: + contents: write + packages: write diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..ccecec9 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,36 @@ +--- +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json +name: "Release" + +on: + push: + tags: + - v[0-9]+.[0-9]+.[0-9]+ + +jobs: + release: + name: Release + runs-on: ubuntu-latest + permissions: + contents: write + packages: write + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Update CHANGELOG + id: changelog + uses: requarks/changelog-action@v1 + with: + token: ${{ github.token }} + tag: ${{ github.ref_name }} + + - name: Create Release + uses: ncipollo/release-action@v1.14.0 + with: + allowUpdates: true + draft: false + makeLatest: true + name: ${{ github.ref_name }} + body: ${{ steps.changelog.outputs.changes }} + token: ${{ github.token }} diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..05fbcf1 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,74 @@ +--- +name: "Testing" + +on: + workflow_call: + +env: + COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} + DATABASE_URL: postgres://crank:crank@localhost/crank + PGPASSWORD: crank + +jobs: + phpunit: + name: PHPUnit (PHP ${{ matrix.php }}) + runs-on: ubuntu-latest + timeout-minutes: 20 + strategy: + matrix: + php: + - "8.3" + include: + - php: "8.3" + coverage: true + fail-fast: false + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + tools: pecl, composer + extensions: intl, bcmath, curl, openssl, mbstring, pdo_pgsql + coverage: pcov + ini-values: memory_limit=-1 + - name: Setup postgres + run: | + sudo systemctl start postgresql + sudo -u postgres psql -d template1 -c "CREATE USER crank WITH PASSWORD 'crank' CREATEDB" + createdb -h localhost -p 5432 -U crank crank_test + pg_isready -d crank_test -h localhost -p 5432 -U crank + - name: Get composer cache directory + id: composercache + run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + - name: Cache dependencies + uses: actions/cache@v4 + with: + path: ${{ steps.composercache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-composer- + - name: Update project dependencies + run: composer update --no-interaction --no-progress --ansi + - name: Create logs dir + run: mkdir -p build/logs/phpunit + - name: Clear test app cache + run: ./bin/console cache:clear --ansi --env=test + - name: Run PHPUnit tests + run: ./vendor/bin/phpunit --log-junit build/logs/phpunit/junit.xml ${{ matrix.coverage && '--coverage-clover build/logs/phpunit/clover.xml' || '' }} + - name: Upload test artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: phpunit-logs-php${{ matrix.php }} + path: build/logs/phpunit/ + continue-on-error: true + - name: Upload coverage results to Codecov + if: matrix.coverage + uses: codecov/codecov-action@v4 + with: + name: phpunit-php${{ matrix.php }} + flags: phpunit + fail_ci_if_error: false + token: ${{ secrets.CODECOV_TOKEN }} + continue-on-error: true diff --git a/.gitignore b/.gitignore index a2776af..d4fb249 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,7 @@ /phpunit.xml .phpunit.result.cache ###< phpunit/phpunit ### + +###> phpstan/phpstan ### +phpstan.neon +###< phpstan/phpstan ### diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..02159ea --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "files.associations": { + "*.json5": "jsonc" + } +} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index cba6196..5a977d0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,6 @@ FROM php:8.3-fpm-alpine +# hadolint ignore=DL3018 RUN apk add --no-cache --virtual \ acl \ file \ diff --git a/Taskfile.yml b/Taskfile.yml index 5f54e61..6f02b95 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -46,3 +46,5 @@ tasks: GOSS_FILES_STRATEGY: cp GOSS_OPTS: --sleep 5s --retry-timeout 60s --color --format documentation GOSS_SLEEP: 2 + DOCKERDNS_PIHOLE_URL: http://localhost + DOCKERDNS_PIHOLE_TOKEN: 998ed4d621742d0c2d85ed84173db569afa194d4597686cae947324aa58ab4bb diff --git a/composer.json b/composer.json index c953740..8af408a 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,8 @@ "allow-plugins": { "php-http/discovery": true, "symfony/flex": true, - "symfony/runtime": true + "symfony/runtime": true, + "phpstan/extension-installer": true }, "sort-packages": true }, @@ -75,6 +76,10 @@ } }, "require-dev": { + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^1.11", + "phpstan/phpstan-phpunit": "^1.4", + "phpstan/phpstan-symfony": "^1.4", "phpunit/phpunit": "^9.5", "symfony/browser-kit": "7.1.*", "symfony/css-selector": "7.1.*", diff --git a/composer.lock b/composer.lock index 28ecbc9..7f1c1af 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "5dbc892754e883c127441bd3cb01e283", + "content-hash": "c00086f4a7b4904febd1189e9f806907", "packages": [ { "name": "doctrine/cache", @@ -5514,6 +5514,232 @@ }, "time": "2022-02-21T01:04:05+00:00" }, + { + "name": "phpstan/extension-installer", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "f6b87faf9fc7978eab2f7919a8760bc9f58f9203" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/f6b87faf9fc7978eab2f7919a8760bc9f58f9203", + "reference": "f6b87faf9fc7978eab2f7919a8760bc9f58f9203", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.0", + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.9.0" + }, + "require-dev": { + "composer/composer": "^2.0", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12 || ^1.0" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.4.1" + }, + "time": "2024-06-10T08:20:49+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.11.10", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "640410b32995914bde3eed26fa89552f9c2c082f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/640410b32995914bde3eed26fa89552f9c2c082f", + "reference": "640410b32995914bde3eed26fa89552f9c2c082f", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "time": "2024-08-08T09:02:50+00:00" + }, + { + "name": "phpstan/phpstan-phpunit", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-phpunit.git", + "reference": "f3ea021866f4263f07ca3636bf22c64be9610c11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/f3ea021866f4263f07ca3636bf22c64be9610c11", + "reference": "f3ea021866f4263f07ca3636bf22c64be9610c11", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.11" + }, + "conflict": { + "phpunit/phpunit": "<7.0" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-strict-rules": "^1.5.1", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "extension.neon", + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPUnit extensions and rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-phpunit/issues", + "source": "https://github.com/phpstan/phpstan-phpunit/tree/1.4.0" + }, + "time": "2024-04-20T06:39:00+00:00" + }, + { + "name": "phpstan/phpstan-symfony", + "version": "1.4.8", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-symfony.git", + "reference": "14eec8c011b856eee4d744a2a3f709db1e1858bd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/14eec8c011b856eee4d744a2a3f709db1e1858bd", + "reference": "14eec8c011b856eee4d744a2a3f709db1e1858bd", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.11.7" + }, + "conflict": { + "symfony/framework-bundle": "<3.0" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.3.11", + "phpstan/phpstan-strict-rules": "^1.5.1", + "phpunit/phpunit": "^8.5.29 || ^9.5", + "psr/container": "1.0 || 1.1.1", + "symfony/config": "^5.4 || ^6.1", + "symfony/console": "^5.4 || ^6.1", + "symfony/dependency-injection": "^5.4 || ^6.1", + "symfony/form": "^5.4 || ^6.1", + "symfony/framework-bundle": "^5.4 || ^6.1", + "symfony/http-foundation": "^5.4 || ^6.1", + "symfony/messenger": "^5.4", + "symfony/polyfill-php80": "^1.24", + "symfony/serializer": "^5.4", + "symfony/service-contracts": "^2.2.0" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "extension.neon", + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Lukáš Unger", + "email": "looky.msc@gmail.com", + "homepage": "https://lookyman.net" + } + ], + "description": "Symfony Framework extensions and rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-symfony/issues", + "source": "https://github.com/phpstan/phpstan-symfony/tree/1.4.8" + }, + "time": "2024-08-13T19:43:40+00:00" + }, { "name": "phpunit/php-code-coverage", "version": "9.2.31", diff --git a/docker/docker-entrypoint.sh b/docker/docker-entrypoint.sh index 78cb642..9e9ea56 100755 --- a/docker/docker-entrypoint.sh +++ b/docker/docker-entrypoint.sh @@ -1,6 +1,10 @@ #!/bin/sh set -e -composer install --prefer-dist --no-progress --no-interaction +if [ -z "$(ls -A 'vendor/' 2>/dev/null)" ]; then + composer install --prefer-dist --no-progress --no-interaction +fi + +php bin/console doctrine:schema:update --force exec docker-php-entrypoint "$@" diff --git a/phpstan.dist.neon b/phpstan.dist.neon new file mode 100644 index 0000000..800e9d1 --- /dev/null +++ b/phpstan.dist.neon @@ -0,0 +1,16 @@ +parameters: + level: 6 + paths: + - bin/ + - config/ + - public/ + - src/ + - tests/ + symfony: + containerXmlPath: var/cache/dev/DockerDNS_KernelDevDebugContainer.xml + constantHassers: false + scanDirectories: + - var/cache/dev/Symfony/Config + bootstrapFiles: + - vendor/autoload.php + - src/Kernel.php \ No newline at end of file diff --git a/src/Bridge/Docker/Client.php b/src/Bridge/Docker/Client.php index ac66af6..259e655 100644 --- a/src/Bridge/Docker/Client.php +++ b/src/Bridge/Docker/Client.php @@ -68,7 +68,7 @@ public function getContainers(): array $serializer = new Serializer($normalizers, $encoders); /** @var array $containers */ - $containers = $serializer->deserialize($json, Container::class . '[]', 'json'); + $containers = $serializer->deserialize($json, Container::class.'[]', 'json'); $mapped = []; foreach ($containers as $container) { diff --git a/src/Bridge/Docker/DTO/Container.php b/src/Bridge/Docker/DTO/Container.php index b4f0ee8..a9ae087 100644 --- a/src/Bridge/Docker/DTO/Container.php +++ b/src/Bridge/Docker/DTO/Container.php @@ -14,8 +14,9 @@ class Container { /** - * @param array $labels - * @param array $names + * @param array $labels + * @param array $names + * @param array $ports */ public function __construct( public string $id, @@ -43,7 +44,7 @@ public function hasLabel(string $label): bool /** * @return string|int|bool|float */ - public function getLabelValue(string $label, $default = null): mixed + public function getLabelValue(string $label, mixed $default = null): mixed { if ($this->hasLabel($label)) { return $this->labels[$label]; diff --git a/src/Bridge/Docker/Entity/Container.php b/src/Bridge/Docker/Entity/Container.php index a7d3903..dd9d087 100644 --- a/src/Bridge/Docker/Entity/Container.php +++ b/src/Bridge/Docker/Entity/Container.php @@ -28,6 +28,9 @@ class Container #[ORM\Column(type: 'string')] public string $name; + /** + * @var array + */ #[ORM\Column(type: 'array')] public array $labels; } diff --git a/src/Bridge/Docker/Listener/CleanUpListener.php b/src/Bridge/Docker/Listener/CleanUpListener.php index af5f920..b534e6c 100644 --- a/src/Bridge/Docker/Listener/CleanUpListener.php +++ b/src/Bridge/Docker/Listener/CleanUpListener.php @@ -13,7 +13,7 @@ use DockerDNS\Bridge\Docker\Docker; use DockerDNS\Bridge\Docker\Entity\Container; -use DockerDNS\Bridge\Docker\Event\CleanupEvent; +use DockerDNS\Bridge\Docker\Event\CleanUpEvent; use DockerDNS\Bridge\Docker\Event\ContainerRemovedEvent; use DockerDNS\Bridge\Docker\Repository\ContainerRepository; use Monolog\Attribute\WithMonologChannel; @@ -29,9 +29,10 @@ public function __construct( private ContainerRepository $repository, private EventDispatcherInterface $dispatcher, private LoggerInterface $logger - ) {} + ) { + } - public function __invoke(CleanupEvent $event) + public function __invoke(CleanUpEvent $event): void { $repository = $this->repository; $dispatcher = $this->dispatcher; diff --git a/src/Bridge/Docker/Listener/StartListener.php b/src/Bridge/Docker/Listener/StartListener.php index 7405453..7375490 100644 --- a/src/Bridge/Docker/Listener/StartListener.php +++ b/src/Bridge/Docker/Listener/StartListener.php @@ -31,9 +31,10 @@ public function __construct( private EventDispatcherInterface $dispatcher, private LoggerInterface $logger, private DockerClient $docker - ) {} + ) { + } - public function __invoke() + public function __invoke(): void { $dispatcher = $this->dispatcher; $containers = $this->docker->getContainers(); diff --git a/src/Bridge/Docker/Repository/ContainerRepository.php b/src/Bridge/Docker/Repository/ContainerRepository.php index c823903..8600dd9 100644 --- a/src/Bridge/Docker/Repository/ContainerRepository.php +++ b/src/Bridge/Docker/Repository/ContainerRepository.php @@ -39,7 +39,7 @@ public function create(ContainerDTO $dto): void $container->labels = $dto->labels; $this->getEntityManager()->persist($container); - $this->getEntityManager()->flush($container); + $this->getEntityManager()->flush(); } public function remove(Container $container): void diff --git a/src/Bridge/Pihole/Client.php b/src/Bridge/Pihole/Client.php index edb58c8..ef555a4 100644 --- a/src/Bridge/Pihole/Client.php +++ b/src/Bridge/Pihole/Client.php @@ -43,6 +43,9 @@ private function createGuzzle(): GuzzleClient return $guzzle; } + /** + * @return array> + */ public function getCustomDNS(): array { $guzzle = $this->guzzle; diff --git a/src/Bridge/Pihole/DTO/CNameCollection.php b/src/Bridge/Pihole/DTO/CNameCollection.php index ae4b54e..1b844a8 100644 --- a/src/Bridge/Pihole/DTO/CNameCollection.php +++ b/src/Bridge/Pihole/DTO/CNameCollection.php @@ -23,7 +23,7 @@ public function __construct( ) { } - public static function fromJson(string $json): static + public static function fromJson(string $json): CNameCollection { $data = json_decode($json, true)['data']; @@ -37,7 +37,7 @@ public static function fromJson(string $json): static ); } - return new static($cnames); + return new CNameCollection($cnames); } public function hasDomain(string $domain): bool diff --git a/src/Bridge/Pihole/DTO/Server.php b/src/Bridge/Pihole/DTO/Server.php index 2af3905..bfc8f8b 100644 --- a/src/Bridge/Pihole/DTO/Server.php +++ b/src/Bridge/Pihole/DTO/Server.php @@ -25,7 +25,7 @@ public function __construct( ) { if (is_null($this->guzzle)) { $this->guzzle = new GuzzleClient([ - 'base_uri' => $url . '/admin/api.php', + 'base_uri' => $url.'/admin/api.php', ]); } diff --git a/src/Bridge/Pihole/Entity/CName.php b/src/Bridge/Pihole/Entity/CName.php index 705fc0e..f36c4c1 100644 --- a/src/Bridge/Pihole/Entity/CName.php +++ b/src/Bridge/Pihole/Entity/CName.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace DockerDNS\Bridge\Pihole\Entity; use Doctrine\ORM\Mapping as ORM; diff --git a/src/Bridge/Pihole/Listener/ContainerRemovedListener.php b/src/Bridge/Pihole/Listener/ContainerRemovedListener.php index e061b60..cd212b6 100644 --- a/src/Bridge/Pihole/Listener/ContainerRemovedListener.php +++ b/src/Bridge/Pihole/Listener/ContainerRemovedListener.php @@ -1,11 +1,20 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace DockerDNS\Bridge\Pihole\Listener; use DockerDNS\Bridge\Docker\Docker; use DockerDNS\Bridge\Docker\Event\ContainerRemovedEvent; -use DockerDNS\Bridge\Pihole\Entity\CName; use DockerDNS\Bridge\Pihole\DTO\CName as CNameDTO; +use DockerDNS\Bridge\Pihole\Entity\CName; use DockerDNS\Bridge\Pihole\Repository\CNameRepository; use DockerDNS\Bridge\Pihole\ServerRegistry; use Monolog\Attribute\WithMonologChannel; @@ -16,12 +25,12 @@ #[WithMonologChannel('pihole')] class ContainerRemovedListener { - public function __construct( private ServerRegistry $registry, private CNameRepository $repository, private LoggerInterface $logger - ) {} + ) { + } public function __invoke(ContainerRemovedEvent $event): void { @@ -45,6 +54,6 @@ public function __invoke(ContainerRemovedEvent $event): void } } - $repository->remove($container->id); + $repository->remove($container->containerId); } } diff --git a/src/Bridge/Pihole/Listener/ProcessContainerListener.php b/src/Bridge/Pihole/Listener/ProcessContainerListener.php index 46552ee..f873935 100644 --- a/src/Bridge/Pihole/Listener/ProcessContainerListener.php +++ b/src/Bridge/Pihole/Listener/ProcessContainerListener.php @@ -25,19 +25,12 @@ #[WithMonologChannel('pihole')] class ProcessContainerListener { - /** - * @var array - */ - private array $servers = []; - - /** - * @param array $servers - */ public function __construct( private ServerRegistry $registry, private CNameRepository $repository, private LoggerInterface $logger - ) {} + ) { + } public function __invoke(Container $container): void { @@ -54,7 +47,7 @@ private function process(Container $container): void if ($container->hasLabel(Pihole::LABEL_CNAME_DOMAIN) && $container->hasLabel(Pihole::LABEL_CNAME_TARGET)) { $targets[] = [ $container->getLabelValue(Pihole::LABEL_CNAME_DOMAIN), - $container->getLabelValue(Pihole::LABEL_CNAME_TARGET) + $container->getLabelValue(Pihole::LABEL_CNAME_TARGET), ]; } @@ -66,9 +59,9 @@ private function process(Container $container): void if ($container->hasLabel($labelDomain) && $container->hasLabel($labelTarget)) { $targets[] = [ $container->getLabelValue($labelDomain), - $container->getLabelValue($labelTarget) + $container->getLabelValue($labelTarget), ]; - $index++; + ++$index; } else { $process = false; } @@ -82,7 +75,7 @@ private function process(Container $container): void } } - private function processServer(Server $server, Container $container, $domain, $target): void + private function processServer(Server $server, Container $container, string $domain, string $target): void { $logger = $this->logger; $cnames = $server->getCNames(); @@ -109,7 +102,7 @@ private function processServer(Server $server, Container $container, $domain, $t $logger->notice('{0}: added cname domain: {1} target: {2}', [ $server->name, $domain, - $target + $target, ]); } $repository->update($container->id, $domain, $target); diff --git a/src/Bridge/Pihole/PiholeExtension.php b/src/Bridge/Pihole/PiholeExtension.php index 42127b0..43324bb 100644 --- a/src/Bridge/Pihole/PiholeExtension.php +++ b/src/Bridge/Pihole/PiholeExtension.php @@ -51,6 +51,9 @@ public function configure(DefinitionConfigurator $definition): void { } + /** + * @param array $config + */ public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void { $this->configureServers($container); diff --git a/src/Bridge/Pihole/Repository/CNameRepository.php b/src/Bridge/Pihole/Repository/CNameRepository.php index 9dfaa70..e703fdc 100644 --- a/src/Bridge/Pihole/Repository/CNameRepository.php +++ b/src/Bridge/Pihole/Repository/CNameRepository.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace DockerDNS\Bridge\Pihole\Repository; use DockerDNS\Bridge\Pihole\Entity\CName; @@ -24,15 +33,15 @@ public function findByContainer(string $containerId): array return $this->findBy(['containerId' => $containerId]); } - public function findByDomainAndTarget($domain, $target): ?CName + public function findByDomainAndTarget(string $domain, string $target): ?CName { return $this->findOneBy([ 'domain' => $domain, - 'target' => $target + 'target' => $target, ]); } - public function update(string $containerId, $domain, $target): void + public function update(string $containerId, string $domain, string $target): void { $cname = $this->findByDomainAndTarget($domain, $target); if (is_null($cname)) { @@ -46,7 +55,7 @@ public function update(string $containerId, $domain, $target): void $this->getEntityManager()->flush(); } - public function remove(string $containerId, string $domain = null, string $target = null): void + public function remove(string $containerId, ?string $domain = null, ?string $target = null): void { $filters['containerId'] = $containerId; if (!is_null($domain)) { diff --git a/src/Bridge/Pihole/ServerRegistry.php b/src/Bridge/Pihole/ServerRegistry.php index bc5bf71..b8b991e 100644 --- a/src/Bridge/Pihole/ServerRegistry.php +++ b/src/Bridge/Pihole/ServerRegistry.php @@ -1,9 +1,17 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace DockerDNS\Bridge\Pihole; use DockerDNS\Bridge\Pihole\DTO\Server; -use Psr\Log\LoggerInterface; use Symfony\Component\DependencyInjection\Attribute\Autowire; class ServerRegistry @@ -11,10 +19,10 @@ class ServerRegistry /** * @var array */ - public array $servers; + public array $servers = []; /** - * @param array $servers + * @param array> $servers */ public function __construct( #[Autowire(param: 'dockerdns.pihole.servers')] diff --git a/src/Command/StartCommand.php b/src/Command/StartCommand.php index 0e4e5dd..0eb35d3 100644 --- a/src/Command/StartCommand.php +++ b/src/Command/StartCommand.php @@ -31,13 +31,12 @@ public function __construct( protected function execute(InputInterface $input, OutputInterface $output): int { pcntl_async_signals(true); - $needsToRun = true; - pcntl_signal(SIGTERM, function () use ($needsToRun) { - $needsToRun = false; + $event = new UpdateEvent(); + pcntl_signal(SIGTERM, function () use (&$event) { + $event->interrupt = true; }); - $event = new UpdateEvent(); - while ($needsToRun) { + while (!$event->interrupt) { try { $this->dispatcher->dispatch($event, Constants::EVENT_START); } catch (\Exception $e) { diff --git a/src/Event/UpdateEvent.php b/src/Event/UpdateEvent.php index 4edfb9c..a5e8024 100644 --- a/src/Event/UpdateEvent.php +++ b/src/Event/UpdateEvent.php @@ -13,4 +13,8 @@ class UpdateEvent { + public function __construct( + public bool $interrupt = false + ) { + } } diff --git a/symfony.lock b/symfony.lock index 34695be..08f750b 100644 --- a/symfony.lock +++ b/symfony.lock @@ -26,6 +26,18 @@ "migrations/.gitignore" ] }, + "phpstan/phpstan": { + "version": "1.11", + "recipe": { + "repo": "github.com/symfony/recipes-contrib", + "branch": "main", + "version": "1.0", + "ref": "5e490cc197fb6bb1ae22e5abbc531ddc633b6767" + }, + "files": [ + "phpstan.dist.neon" + ] + }, "phpunit/phpunit": { "version": "9.6", "recipe": { diff --git a/tests/Bridge/Docker/ClientTest.php b/tests/Bridge/Docker/ClientTest.php index f5dc8f7..9b09e0d 100644 --- a/tests/Bridge/Docker/ClientTest.php +++ b/tests/Bridge/Docker/ClientTest.php @@ -15,6 +15,11 @@ use DockerDNS\Tests\Fixtures; use PHPUnit\Framework\TestCase; +/** + * @covers \DockerDNS\Bridge\Docker\Client + * @covers \DockerDNS\Bridge\Docker\Serializer\PropertyNameConverter + * @covers \DockerDNS\Bridge\Docker\DTO\Container + */ class ClientTest extends TestCase { public function testConnection(): void diff --git a/tests/Bridge/Docker/Listener/CleanUpListenerTest.php b/tests/Bridge/Docker/Listener/CleanUpListenerTest.php index c103869..ed553f0 100644 --- a/tests/Bridge/Docker/Listener/CleanUpListenerTest.php +++ b/tests/Bridge/Docker/Listener/CleanUpListenerTest.php @@ -18,6 +18,7 @@ use DockerDNS\Bridge\Docker\Listener\CleanUpListener; use DockerDNS\Bridge\Docker\Repository\ContainerRepository; use DockerDNS\Tests\Fixtures; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; use Symfony\Component\EventDispatcher\EventDispatcher; @@ -25,10 +26,10 @@ class CleanUpListenerTest extends TestCase { - private ContainerRepository $repository; - private EventDispatcherInterface $dispatcher; - private LoggerInterface $logger; - private CleanUpListener $listener; + private MockObject|ContainerRepository $repository; + private MockObject|EventDispatcherInterface $dispatcher; + private MockObject|LoggerInterface $logger; + private MockObject|CleanUpListener $listener; protected function setUp(): void { @@ -48,6 +49,7 @@ public function testInvoke(): void $event = new CleanUpEvent($containers); $entity = new Container(); $entity->containerId = 'some-id'; + $entity->name = 'name'; $this->repository->expects($this->once()) ->method('findAll') diff --git a/tests/Bridge/Docker/Listener/StartListenerTest.php b/tests/Bridge/Docker/Listener/StartListenerTest.php index 3c4ce46..5321ac0 100644 --- a/tests/Bridge/Docker/Listener/StartListenerTest.php +++ b/tests/Bridge/Docker/Listener/StartListenerTest.php @@ -14,21 +14,22 @@ use DockerDNS\Bridge\Docker\Client as DockerClient; use DockerDNS\Bridge\Docker\DTO\Container; use DockerDNS\Bridge\Docker\Docker; -use DockerDNS\Bridge\Docker\Event\CleanupEvent; +use DockerDNS\Bridge\Docker\Event\CleanUpEvent; use DockerDNS\Bridge\Docker\Listener\StartListener; use DockerDNS\Bridge\Docker\Repository\ContainerRepository; use DockerDNS\Tests\Fixtures; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; class StartListenerTest extends TestCase { - private ContainerRepository $repository; - private EventDispatcherInterface $dispatcher; - private LoggerInterface $logger; - private DockerClient $docker; - private StartListener $listener; + private MockObject|ContainerRepository $repository; + private MockObject|EventDispatcherInterface $dispatcher; + private MockObject|LoggerInterface $logger; + private MockObject|DockerClient $docker; + private MockObject|StartListener $listener; protected function setUp(): void { @@ -61,7 +62,7 @@ public function testInvoke(): void ->withConsecutive( [$this->isInstanceOf(Container::class), Docker::EVENT_PROCESS], [$this->isInstanceOf(Container::class), Docker::EVENT_PROCESS], - [$this->isInstanceOf(CleanupEvent::class), Docker::EVENT_CLEANUP], + [$this->isInstanceOf(CleanUpEvent::class), Docker::EVENT_CLEANUP], ) ;