diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..f7414b7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +hack/tilt/scripts/prepare_values.py linguist-detectable=false diff --git a/.github/ISSUE_TEMPLATE/1-bug.yml b/.github/ISSUE_TEMPLATE/1-bug.yml new file mode 100644 index 0000000..b03cd59 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/1-bug.yml @@ -0,0 +1,50 @@ +# SPDX-FileCopyrightText: 2026 INDUSTRIA DE DISEÑO TEXTIL S.A. (INDITEX S.A.) +# SPDX-License-Identifier: Apache-2.0 +name: Bug Report +description: Report a reproducible bug +title: '[Bug] ' +labels: ['bug'] +assignees: [] + +body: + - type: textarea + id: description + attributes: + label: Description + description: What's the problem? + validations: + required: true + + - type: textarea + id: steps + attributes: + label: Steps to Reproduce + description: How can we reproduce the bug? + placeholder: | + 1. Go to ... + 2. Run ... + 3. See error + validations: + required: true + + - type: textarea + id: expected + attributes: + label: Expected Behavior + validations: + required: false + + - type: input + id: version + attributes: + label: Version / Environment + placeholder: "e.g. v0.3.2, Node 18, macOS" + validations: + required: false + + - type: textarea + id: notes + attributes: + label: Additional context or logs + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/2-feature.yml b/.github/ISSUE_TEMPLATE/2-feature.yml new file mode 100644 index 0000000..b44bd15 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/2-feature.yml @@ -0,0 +1,33 @@ +# SPDX-FileCopyrightText: 2026 INDUSTRIA DE DISEÑO TEXTIL S.A. (INDITEX S.A.) +# SPDX-License-Identifier: Apache-2.0 +name: Feature Request +description: Suggest a feature or improvement +title: '[Feature] ' +labels: ['enhancement'] +assignees: [] + +body: + - type: textarea + id: proposal + attributes: + label: Feature description + description: What would you like to see added or changed? + validations: + required: true + + - type: textarea + id: motivation + attributes: + label: Use case or motivation + description: Why is this feature useful? + + - type: dropdown + id: contribution + attributes: + label: Would you like to work on this? + options: + - Yes, I'd like to open a PR + - Maybe, I'd need help + - No, I'm just proposing it + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/3-other.yml b/.github/ISSUE_TEMPLATE/3-other.yml new file mode 100644 index 0000000..6968199 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/3-other.yml @@ -0,0 +1,37 @@ +# SPDX-FileCopyrightText: 2026 INDUSTRIA DE DISEÑO TEXTIL S.A. (INDITEX S.A.) +# SPDX-License-Identifier: Apache-2.0 +name: Other +description: Use this for anything that doesn't fit the other templates +title: '[Other] ' +labels: ['discussion'] +assignees: [] + +body: + - type: textarea + id: context + attributes: + label: Context or topic + description: Briefly explain what this is about — question, idea, feedback, etc. + validations: + required: true + + - type: textarea + id: details + attributes: + label: Details or background + description: Add any supporting info, links, logs, or notes that may be useful. + placeholder: | + Example: + - I'm wondering if we should improve X + - This tool might be useful for Y + - We discussed this in meeting Z + + - type: dropdown + id: nextstep + attributes: + label: What are you hoping to do next? + options: + - Just opening this for awareness + - Looking for input before starting work + - Need help or advice + - Happy to open a PR based on feedback diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..30e40d3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,3 @@ +# SPDX-FileCopyrightText: 2026 INDUSTRIA DE DISEÑO TEXTIL S.A. (INDITEX S.A.) +# SPDX-License-Identifier: Apache-2.0 +blank_issues_enabled: false diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 810449c..954cfa2 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,6 +1,5 @@ @@ -19,4 +18,4 @@ Briefly describe the purpose of this PR and what changes it introduces. ## Additional context -Add any screenshots, test output, or notes for reviewers here. \ No newline at end of file +Add any screenshots, test output, or notes for reviewers here. diff --git a/.github/inditextech-ci-go.json b/.github/inditextech-ci-go.json new file mode 100644 index 0000000..616f4fc --- /dev/null +++ b/.github/inditextech-ci-go.json @@ -0,0 +1,7 @@ +{ + "artifacts": "none", + "schema_version": 1, + "sonar": true, + "verify_command": "make build test lint", + "working_directory": "." +} diff --git a/.github/inditextech-ci-sync-manifest.json b/.github/inditextech-ci-sync-manifest.json new file mode 100644 index 0000000..695af03 --- /dev/null +++ b/.github/inditextech-ci-sync-manifest.json @@ -0,0 +1,48 @@ +{ + "configuration": { + "development_flow": "trunk-based", + "outputs": [], + "parameters": { + "artifacts": "none", + "sonar": true, + "verify_command": "make build test lint", + "working_directory": "." + } + }, + "creation_year": 2026, + "integrity": { + "algorithm": "hmac-sha256", + "signature": "b9e6b75852c23cbbed7265826d4808864092fa5f3c232af9e14f617ae71bf96e" + }, + "managed_by": "InditexTech CI governance", + "managed_paths": { + ".github/ISSUE_TEMPLATE/1-bug.yml": "cd4dad3f32fe707e06324790a2fe2c25829c55566de2b459d744e785b36e1205", + ".github/ISSUE_TEMPLATE/2-feature.yml": "ebb92730d6c01641d055f97ac094cbe07f88fac24be9b689ddf26e47e7bda863", + ".github/ISSUE_TEMPLATE/3-other.yml": "5a839b39a98704036210dee498146f51bd5a6c057228685680e02004ff063a9a", + ".github/ISSUE_TEMPLATE/config.yml": "10e5dee4d49aa9e7792ceb4c986160c61f95da1ba00a64bf06e5f1f636cd17ec", + ".github/PULL_REQUEST_TEMPLATE.md": "23a0b0ac79a9020ccac9c013582a01f86e2df2ae7f914673583b9a3368313041", + ".github/inditextech-ci-go.json": "d854041fe302eab86b99a4ee25414027fda743af14f26db7e8883ab90f60c5fd", + ".github/workflows/code-gomod_go-PR_verify.yml": "7145f586962f35425afbce4cdd00c61f0447d25f1c22ea48c8131f42343f893e", + ".github/workflows/code-gomod_go-sonarcloud-analysis.yml": "65b682f75a2fc0d9e928bc4816d79e1ff6aa68830c47148e23db9943f3a79c10", + ".github/workflows/codeql.yml": "a7ff83d8f89a5e02bfd729939a51a0a512a0f2f3fa7ddf9dc688dfdf41937748", + ".github/workflows/pr-verify.yml": "f52295fbfe81578c0a6459d8b384be569933ac78d32fff370932a8dec6943e46", + ".github/workflows/push-verify.yml": "217097070d0fdef329eae1a7187806a4623d692ce467ca8f1f9a7f56dddb29ed", + "repolinter.json": "0efb305c47a63f03c488a34906fc2d5471c84145c2ff553f2aacf3a41b92e1a1" + }, + "managed_variables": { + "DEVELOPMENT_FLOW": "trunk-based-development", + "WORKING_DIRECTORY": "." + }, + "profile": "go", + "repository_id": 1011107741, + "retained_paths": { + "CODE_OF_CONDUCT.md": "97188ec333ef1e7e4b8811a41e714a922460bf1a07dabf8d9542dd2def6e0af2", + "CONTRIBUTING.md": "f9c34489e076248247476a0dc9b7ab07fba014dc2c5d82b9ef246546f6c46dab", + "SECURITY.md": "fcaf4cc7669737e9025dafe551a39190dbc3d43b3b5b58e803f5a2a49f39d8f9" + }, + "schema_version": 2, + "source_digests": { + "base": "09dc9b8d0dcb29288bdb6adeb49cea193b4b79f8de78e55eed00c1c669b5ab5a", + "go": "963229e52884cccc1de8b2251ce7745a8fa5e36970553eeb513a9d1f914d4410" + } +} diff --git a/.github/workflows/PR-verify.yml b/.github/workflows/PR-verify.yml deleted file mode 100644 index 6e3e5b2..0000000 --- a/.github/workflows/PR-verify.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: Pull Request verification -permissions: - contents: read - -on: - pull_request: - workflow_dispatch: - -env: - NODE_VERSION: "20" - -jobs: - repo-linter: - name: Repo Linter - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - with: - fetch-depth: 0 - ref: ${{ github.event.pull_request.head.sha }} - - - name: Setup Node version - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4 - with: - node-version: ${{ env.NODE_VERSION }} - - - name: Execute Repo Linter - run: | - echo "Installing Repo Linter" - npm install -g repolinter@v0.11.2 - - echo "Executing Repo Linter" - repolinter --rulesetFile repolinter.json --dryRun . - - echo "Repo Linter execution completed" - - - reuse-compliance: - name: REUSE Compliance - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - with: - fetch-depth: 0 - ref: ${{ github.event.pull_request.head.sha }} - - - name: REUSE Compliance Check - uses: fsfe/reuse-action@bb774aa972c2a89ff34781233d275075cbddf542 # v5 diff --git a/.github/workflows/code-gomod_go-PR_verify.yml b/.github/workflows/code-gomod_go-PR_verify.yml new file mode 100644 index 0000000..dbd442a --- /dev/null +++ b/.github/workflows/code-gomod_go-PR_verify.yml @@ -0,0 +1,120 @@ +# SPDX-FileCopyrightText: 2026 INDUSTRIA DE DISEÑO TEXTIL S.A. (INDITEX S.A.) +# SPDX-License-Identifier: Apache-2.0 + +name: code-gomod-go-PR-verify + +on: + pull_request: + types: [opened, synchronize, ready_for_review, reopened] + +permissions: + contents: read + +concurrency: + group: code-gomod-go-pr-verify-${{ github.repository }}-${{ github.event.pull_request.number }} + cancel-in-progress: true + +env: + WORKING_DIRECTORY: ${{ vars.WORKING_DIRECTORY }} + # The project-owned verify command travels in the managed component descriptor, + # never in an Actions variable: the repo owns its build, governance owns only the + # envelope that invokes it. + COMPONENT_DESCRIPTOR: .github/inditextech-ci-go.json + ASDF_BRANCH_VERSION: '0.18.0' + +jobs: + verify: + name: Code / Verify + runs-on: ubuntu-24.04 + timeout-minutes: 30 + steps: + - name: Check out pull request head + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + persist-credentials: false + ref: ${{ github.event.pull_request.head.sha }} + + - name: Read governed tool versions + id: tool-versions + working-directory: ${{ env.WORKING_DIRECTORY }} + shell: bash + run: | + set -euo pipefail + resolved="" + if [[ -f .tool-versions ]]; then + # Validate only real " " lines; asdf allows # comments (SPDX/REUSE) and blank lines. + if grep -vE '^[[:space:]]*(#|$)' .tool-versions | grep -qvE '^[a-zA-Z0-9_-]+ [a-zA-Z0-9._+-]+$'; then + echo "::error title=Invalid tool-versions::${WORKING_DIRECTORY}/.tool-versions is malformed." + exit 1 + fi + # Emit only the governed golang pin so asdf ignores project extra pins (e.g. kind). + resolved="$(grep -E '^golang ' .tool-versions || true)" + fi + if [[ -z "$resolved" ]]; then + # No committed golang pin: derive it from the module's own go directive so the + # governed lane follows the product's declared toolchain instead of failing or + # clamping it. A committed pin always wins over the derived one. + if [[ ! -f go.mod ]]; then + echo "::error title=Missing go toolchain::${WORKING_DIRECTORY} has no golang pin in .tool-versions and no go.mod to derive one from." + exit 1 + fi + version="$(sed -nE 's/^toolchain[[:space:]]+go([0-9]+\.[0-9]+(\.[0-9]+)?)[[:space:]]*$/\1/p' go.mod | head -n 1)" + if [[ -z "$version" ]]; then + version="$(sed -nE 's/^go[[:space:]]+([0-9]+\.[0-9]+(\.[0-9]+)?)[[:space:]]*$/\1/p' go.mod | head -n 1)" + fi + if [[ -z "$version" ]]; then + echo "::error title=Missing go toolchain::${WORKING_DIRECTORY}/go.mod declares no usable go version." + exit 1 + fi + # asdf installs an exact patch release; a two-part directive (go 1.21) means .0. + if [[ "$version" != *.*.* ]]; then + version="${version}.0" + fi + resolved="golang ${version}" + echo "::notice title=Derived go toolchain::Using ${resolved} from ${WORKING_DIRECTORY}/go.mod." + fi + { + echo "tool_versions<> "$GITHUB_OUTPUT" + + - name: Set up asdf-managed Go + uses: asdf-vm/actions/install@b7bcd026f18772e44fe1026d729e1611cc435d47 # v4.0.1 + with: + tool_versions: ${{ steps.tool-versions.outputs.tool_versions }} + asdf_version: ${{ env.ASDF_BRANCH_VERSION }} + + - name: Resolve governed verify command + id: descriptor + shell: bash + run: | + set -euo pipefail + if [[ ! -f "$COMPONENT_DESCRIPTOR" ]]; then + echo "::error title=Missing descriptor::${COMPONENT_DESCRIPTOR} not found." + exit 1 + fi + # The descriptor is governance-owned and schema-validated; verify_command + # always resolves, but fail closed if it is somehow blank. + verify_command="$(jq -er '.verify_command // "make verify"' "$COMPONENT_DESCRIPTOR")" + if [[ -z "$verify_command" ]]; then + echo "::error title=Empty verify command::descriptor verify_command must not be blank." + exit 1 + fi + { + echo "verify_command<> "$GITHUB_OUTPUT" + + - name: Verify and test + working-directory: ${{ env.WORKING_DIRECTORY }} + shell: bash + env: + VERIFY_COMMAND: ${{ steps.descriptor.outputs.verify_command }} + run: | + set -euo pipefail + # The project owns its build; governance only invokes the governed + # verify_command inside the pinned working directory. + bash -c "$VERIFY_COMMAND" diff --git a/.github/workflows/code-gomod_go-sonarcloud-analysis.yml b/.github/workflows/code-gomod_go-sonarcloud-analysis.yml new file mode 100644 index 0000000..b4710d4 --- /dev/null +++ b/.github/workflows/code-gomod_go-sonarcloud-analysis.yml @@ -0,0 +1,155 @@ +# SPDX-FileCopyrightText: 2026 INDUSTRIA DE DISEÑO TEXTIL S.A. (INDITEX S.A.) +# SPDX-License-Identifier: Apache-2.0 + +name: code-gomod-go-sonarcloud-analysis +run-name: SonarCloud analysis on ${{ github.head_ref || github.ref_name }} + +on: + workflow_dispatch: + pull_request: + types: [opened, synchronize, reopened] + branches: [main, main-*, develop, develop-*] + push: + branches: [main, main-*, develop, develop-*] + +permissions: + contents: read + +concurrency: + group: code-gomod-go-sonarcloud-${{ github.repository }}-${{ github.ref }} + cancel-in-progress: true + +env: + WORKING_DIRECTORY: ${{ vars.WORKING_DIRECTORY }} + ASDF_BRANCH_VERSION: '0.18.0' + +jobs: + analyze: + name: SonarCloud / Analyze + if: >- + vars.SONARCLOUD_ENABLED == 'true' && + (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && + ( + ( + vars.DEVELOPMENT_FLOW == 'trunk-based-development' && + ( + (github.event_name == 'workflow_dispatch' && (github.ref_name == 'main' || startsWith(github.ref_name, 'main-'))) || + (github.event_name == 'pull_request' && (github.event.pull_request.base.ref == 'main' || startsWith(github.event.pull_request.base.ref, 'main-'))) || + (github.event_name == 'push' && (github.ref_name == 'main' || startsWith(github.ref_name, 'main-'))) + ) + ) || + ( + vars.DEVELOPMENT_FLOW == 'git-flow' && + ( + (github.event_name == 'workflow_dispatch' && (github.ref_name == 'develop' || startsWith(github.ref_name, 'develop-'))) || + (github.event_name == 'pull_request' && (github.event.pull_request.base.ref == 'develop' || startsWith(github.event.pull_request.base.ref, 'develop-'))) || + (github.event_name == 'push' && (github.ref_name == 'develop' || startsWith(github.ref_name, 'develop-'))) + ) + ) + ) + runs-on: ubuntu-24.04 + timeout-minutes: 30 + steps: + - name: Resolve SonarCloud availability + id: sonar + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + shell: bash + run: | + set -euo pipefail + # Advisory + graceful-skip: opt-in via SONARCLOUD_ENABLED, but a repo whose + # SONAR_TOKEN is not provisioned simply skips analysis instead of failing. + if [[ -z "${SONAR_TOKEN:-}" ]]; then + echo "::notice title=SonarCloud skipped::SONAR_TOKEN is not configured; skipping advisory analysis." + echo "enabled=false" >> "$GITHUB_OUTPUT" + else + echo "enabled=true" >> "$GITHUB_OUTPUT" + fi + + - name: Check out source + if: steps.sonar.outputs.enabled == 'true' + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Read governed tool versions + id: tool-versions + if: steps.sonar.outputs.enabled == 'true' + working-directory: ${{ env.WORKING_DIRECTORY }} + shell: bash + run: | + set -euo pipefail + resolved="" + if [[ -f .tool-versions ]]; then + # Validate only real " " lines; asdf allows # comments (SPDX/REUSE) and blank lines. + if grep -vE '^[[:space:]]*(#|$)' .tool-versions | grep -qvE '^[a-zA-Z0-9_-]+ [a-zA-Z0-9._+-]+$'; then + echo "::error title=Invalid tool-versions::${WORKING_DIRECTORY}/.tool-versions is malformed." + exit 1 + fi + # Emit only the governed golang pin so asdf ignores project extra pins (e.g. kind). + resolved="$(grep -E '^golang ' .tool-versions || true)" + fi + if [[ -z "$resolved" ]]; then + # No committed golang pin: derive it from the module's own go directive so the + # governed lane follows the product's declared toolchain instead of failing or + # clamping it. A committed pin always wins over the derived one. + if [[ ! -f go.mod ]]; then + echo "::error title=Missing go toolchain::${WORKING_DIRECTORY} has no golang pin in .tool-versions and no go.mod to derive one from." + exit 1 + fi + version="$(sed -nE 's/^toolchain[[:space:]]+go([0-9]+\.[0-9]+(\.[0-9]+)?)[[:space:]]*$/\1/p' go.mod | head -n 1)" + if [[ -z "$version" ]]; then + version="$(sed -nE 's/^go[[:space:]]+([0-9]+\.[0-9]+(\.[0-9]+)?)[[:space:]]*$/\1/p' go.mod | head -n 1)" + fi + if [[ -z "$version" ]]; then + echo "::error title=Missing go toolchain::${WORKING_DIRECTORY}/go.mod declares no usable go version." + exit 1 + fi + # asdf installs an exact patch release; a two-part directive (go 1.21) means .0. + if [[ "$version" != *.*.* ]]; then + version="${version}.0" + fi + resolved="golang ${version}" + echo "::notice title=Derived go toolchain::Using ${resolved} from ${WORKING_DIRECTORY}/go.mod." + fi + { + echo "tool_versions<> "$GITHUB_OUTPUT" + + - name: Set up asdf-managed Go + if: steps.sonar.outputs.enabled == 'true' + uses: asdf-vm/actions/install@b7bcd026f18772e44fe1026d729e1611cc435d47 # v4.0.1 + with: + tool_versions: ${{ steps.tool-versions.outputs.tool_versions }} + asdf_version: ${{ env.ASDF_BRANCH_VERSION }} + + - name: Generate Go coverage profile + if: steps.sonar.outputs.enabled == 'true' + working-directory: ${{ env.WORKING_DIRECTORY }} + shell: bash + run: | + set -euo pipefail + # Advisory coverage: emit a standard Go coverage profile for Sonar. A module + # without tests still gets a static analysis, so coverage generation never blocks. + go test -covermode=atomic -coverprofile=coverage.out ./... \ + || echo "::warning title=Coverage unavailable::go test produced no coverage profile; Sonar will analyze without coverage." + + - name: Run SonarCloud analysis + if: steps.sonar.outputs.enabled == 'true' + uses: SonarSource/sonarqube-scan-action@22918119ff8e1ca75a623e15c8296b6ea4fbe28f # v8.2.1 + env: + SONAR_HOST_URL: https://sonarcloud.io + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + with: + args: >- + -Dsonar.organization=inditextech + -Dsonar.projectKey=InditexTech_${{ github.event.repository.name }} + -Dsonar.projectName=${{ github.event.repository.name }} + -Dsonar.sources=${{ env.WORKING_DIRECTORY }} + -Dsonar.tests=${{ env.WORKING_DIRECTORY }} + -Dsonar.test.inclusions=**/*_test.go + -Dsonar.exclusions=**/vendor/**,**/dist/** + -Dsonar.go.coverage.reportPaths=${{ env.WORKING_DIRECTORY }}/coverage.out diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..2bb4dc6 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,170 @@ +# SPDX-FileCopyrightText: 2026 INDUSTRIA DE DISEÑO TEXTIL S.A. (INDITEX S.A.) +# SPDX-License-Identifier: Apache-2.0 + +name: CodeQL + +on: + push: + branches: [main, main-*, develop, develop-*] + pull_request: + branches: [main, main-*, develop, develop-*] + merge_group: + types: [checks_requested] + schedule: + - cron: '17 4 * * 1' + +permissions: + contents: read + +concurrency: + group: codeql-${{ github.repository }}-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + WORKING_DIRECTORY: ${{ vars.WORKING_DIRECTORY }} + ASDF_BRANCH_VERSION: '0.18.0' + +jobs: + # Resolves code-scanning availability at runtime so the analysis skips cleanly (green) where no Advanced Security seat is licensed, and starts automatically once a repository becomes public or gains a seat. + scanning-availability: + name: Resolve code scanning availability + if: >- + github.event_name == 'schedule' || + ( + vars.DEVELOPMENT_FLOW == 'trunk-based-development' && + ( + (github.event_name == 'push' && (github.ref_name == 'main' || startsWith(github.ref_name, 'main-'))) || + (github.event_name == 'pull_request' && (github.event.pull_request.base.ref == 'main' || startsWith(github.event.pull_request.base.ref, 'main-'))) || + (github.event_name == 'merge_group' && (github.event.merge_group.base_ref == 'main' || startsWith(github.event.merge_group.base_ref, 'main-'))) + ) + ) || + ( + vars.DEVELOPMENT_FLOW == 'git-flow' && + ( + (github.event_name == 'push' && (github.ref_name == 'develop' || startsWith(github.ref_name, 'develop-'))) || + (github.event_name == 'pull_request' && (github.event.pull_request.base.ref == 'develop' || startsWith(github.event.pull_request.base.ref, 'develop-'))) || + (github.event_name == 'merge_group' && (github.event.merge_group.base_ref == 'develop' || startsWith(github.event.merge_group.base_ref, 'develop-'))) + ) + ) + runs-on: ubuntu-24.04 + permissions: + contents: read # Reads repository metadata to resolve availability. + outputs: + enabled: ${{ steps.resolve.outputs.enabled }} + steps: + - name: Resolve availability + id: resolve + env: + GH_TOKEN: ${{ github.token }} + # Manual opt-in (CODE_SCANNING_ENABLED='true') for a seated private/internal repository whose seat status the workflow token cannot read. + FORCE_ENABLED: ${{ vars.CODE_SCANNING_ENABLED }} + run: | + set -euo pipefail + # Visibility is always readable; the security-and-analysis block is admin-token-only, so treat a missing value as unknown. + visibility="$(gh api "/repos/${GITHUB_REPOSITORY}" --jq '.visibility' 2>/dev/null || echo unknown)" + seat="$(gh api "/repos/${GITHUB_REPOSITORY}" \ + --jq '.security_and_analysis.advanced_security.status // "unknown"' 2>/dev/null || echo unknown)" + enabled=false + if [ "${visibility}" = "public" ]; then + enabled=true # Code scanning is always free and available on public repositories. + elif [ "${seat}" = "enabled" ]; then + enabled=true # A Code Security seat is attached, so analysis is licensed. + elif [ "${FORCE_ENABLED:-}" = "true" ]; then + enabled=true # Operator opted this repository in explicitly. + fi + echo "Code scanning availability: visibility=${visibility} seat=${seat} enabled=${enabled}" + echo "enabled=${enabled}" >> "${GITHUB_OUTPUT}" + + analyze: + name: Analyze (${{ matrix.language }}) + needs: scanning-availability + if: needs.scanning-availability.outputs.enabled == 'true' + runs-on: ubuntu-24.04 + permissions: + actions: read # Lets CodeQL inspect workflow metadata. + contents: read + packages: read # Lets CodeQL resolve package metadata during analysis. + security-events: write # Uploads CodeQL results to Code Scanning. + strategy: + fail-fast: false + matrix: + include: + - language: actions + build-mode: none + # Go has no buildless extractor: CodeQL supports build-mode 'none' only for + # C/C++, C#, Java, and Rust. Go is analyzed with build-mode 'autobuild', which + # compiles the module during init and therefore needs the governed toolchain on + # PATH (set up conditionally below before Initialize CodeQL). + - language: go + build-mode: autobuild + steps: + - name: Check out repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Read governed tool versions + id: tool-versions + if: matrix.language == 'go' + working-directory: ${{ env.WORKING_DIRECTORY }} + shell: bash + run: | + set -euo pipefail + resolved="" + if [[ -f .tool-versions ]]; then + # Validate only real " " lines; asdf allows # comments (SPDX/REUSE) and blank lines. + if grep -vE '^[[:space:]]*(#|$)' .tool-versions | grep -qvE '^[a-zA-Z0-9_-]+ [a-zA-Z0-9._+-]+$'; then + echo "::error title=Invalid tool-versions::${WORKING_DIRECTORY}/.tool-versions is malformed." + exit 1 + fi + # Emit only the governed golang pin so asdf ignores project extra pins (e.g. kind). + resolved="$(grep -E '^golang ' .tool-versions || true)" + fi + if [[ -z "$resolved" ]]; then + # No committed golang pin: derive it from the module's own go directive so the + # governed lane follows the product's declared toolchain instead of failing or + # clamping it. A committed pin always wins over the derived one. + if [[ ! -f go.mod ]]; then + echo "::error title=Missing go toolchain::${WORKING_DIRECTORY} has no golang pin in .tool-versions and no go.mod to derive one from." + exit 1 + fi + version="$(sed -nE 's/^toolchain[[:space:]]+go([0-9]+\.[0-9]+(\.[0-9]+)?)[[:space:]]*$/\1/p' go.mod | head -n 1)" + if [[ -z "$version" ]]; then + version="$(sed -nE 's/^go[[:space:]]+([0-9]+\.[0-9]+(\.[0-9]+)?)[[:space:]]*$/\1/p' go.mod | head -n 1)" + fi + if [[ -z "$version" ]]; then + echo "::error title=Missing go toolchain::${WORKING_DIRECTORY}/go.mod declares no usable go version." + exit 1 + fi + # asdf installs an exact patch release; a two-part directive (go 1.21) means .0. + if [[ "$version" != *.*.* ]]; then + version="${version}.0" + fi + resolved="golang ${version}" + echo "::notice title=Derived go toolchain::Using ${resolved} from ${WORKING_DIRECTORY}/go.mod." + fi + { + echo "tool_versions<> "$GITHUB_OUTPUT" + + - name: Set up asdf-managed Go + # autobuild compiles the module during Initialize CodeQL, so the governed Go + # toolchain must already be on PATH; the actions language needs no toolchain. + if: matrix.language == 'go' + uses: asdf-vm/actions/install@b7bcd026f18772e44fe1026d729e1611cc435d47 # v4.0.1 + with: + tool_versions: ${{ steps.tool-versions.outputs.tool_versions }} + asdf_version: ${{ env.ASDF_BRANCH_VERSION }} + + - name: Initialize CodeQL + uses: github/codeql-action/init@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4 + with: + build-mode: ${{ matrix.build-mode }} + languages: ${{ matrix.language }} + + - name: Perform CodeQL analysis + uses: github/codeql-action/analyze@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4 + with: + category: /language:${{ matrix.language }} diff --git a/.github/workflows/go-test.yml b/.github/workflows/go-test.yml deleted file mode 100644 index 1b0d89e..0000000 --- a/.github/workflows/go-test.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: ⚒️ Go tests and linting for the webhook -on: - pull_request: -permissions: - contents: read -jobs: - test: - name: Test 🔨 - runs-on: ubuntu-latest - steps: - - name: Checkout 🧲 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - - - name: Restore go build cache ⏬ - uses: actions/cache@v4 - with: - path: ~/.cache/go-build - key: ${{ runner.os }}-go-build-v1 - - - name: Setup Go ⬇️ - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5 - with: - go-version: 1.26.8 - - - name: Build and test 🏗️ - run: | - make build - make test - - lint: - name: Lint 🔍 - runs-on: ubuntu-latest - steps: - - name: Checkout 🧲 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - - - name: Restore go build cache ⏬ - uses: actions/cache@v4 - with: - path: ~/.cache/go-build - key: ${{ runner.os }}-go-build-v1 - - - name: Setup Go ⬇️ - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5 - with: - go-version: 1.26.8 - - - name: Linter 🔍 - run: | - make lint diff --git a/.github/workflows/pr-verify.yml b/.github/workflows/pr-verify.yml new file mode 100644 index 0000000..66c196d --- /dev/null +++ b/.github/workflows/pr-verify.yml @@ -0,0 +1,79 @@ +# SPDX-FileCopyrightText: 2026 INDUSTRIA DE DISEÑO TEXTIL S.A. (INDITEX S.A.) +# SPDX-License-Identifier: Apache-2.0 + +name: Pull Request Verification +permissions: + contents: read + +on: + pull_request: + +concurrency: + group: pr-verify-${{ github.workflow }}-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + workflow-quality: + name: Workflow and Template Quality + runs-on: ubuntu-24.04 + timeout-minutes: 10 + permissions: + contents: read + checks: write # Lets reviewdog publish actionlint check results. + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + persist-credentials: false + ref: ${{ github.event.pull_request.head.sha }} + + - name: Lint GitHub Actions workflows + uses: reviewdog/action-actionlint@dbe5299849118fd6f099ba563d263d770955a64a # v1.73.2 + with: + reporter: github-pr-check + fail_level: error + + repo-linter: + name: Repo Linter + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + persist-credentials: false + ref: ${{ github.event.pull_request.head.sha }} + + - name: 'Run Repolinter' + uses: newrelic/repolinter-action@3f4448f855c351e9695b24524a4111c7847b84cb # v1.7.0 + + reuse-compliance: + name: REUSE Compliance + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + persist-credentials: false + ref: ${{ github.event.pull_request.head.sha }} + + - name: REUSE Compliance Check + uses: fsfe/reuse-action@676e2d560c9a403aa252096d99fcab3e1132b0f5 # v6.0.0 + + conventional-commits: + name: Conventional Commits + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + persist-credentials: false + ref: ${{ github.event.pull_request.head.sha }} + + - uses: webiny/action-conventional-commits@7f91b1595ca1951cdb671ddc9f07a49081ec5b69 # v1.4.2 diff --git a/.github/workflows/push-verify.yml b/.github/workflows/push-verify.yml new file mode 100644 index 0000000..6f0c7b0 --- /dev/null +++ b/.github/workflows/push-verify.yml @@ -0,0 +1,115 @@ +# SPDX-FileCopyrightText: 2026 INDUSTRIA DE DISEÑO TEXTIL S.A. (INDITEX S.A.) +# SPDX-License-Identifier: Apache-2.0 + +name: Push Verification +permissions: + contents: read + +on: + push: + +concurrency: + group: push-verify-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + workflow-quality: + if: >- + github.ref_type == 'branch' && + github.ref_name == github.event.repository.default_branch + name: Workflow and Template Quality + runs-on: ubuntu-24.04 + timeout-minutes: 10 + permissions: + contents: read + checks: write # Lets reviewdog publish actionlint check results. + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Lint GitHub Actions workflows + uses: reviewdog/action-actionlint@dbe5299849118fd6f099ba563d263d770955a64a # v1.73.2 + with: + reporter: github-check + fail_level: error + + scorecard-analysis: + if: >- + github.ref_type == 'branch' && + github.event.repository.visibility == 'public' && + github.ref_name == github.event.repository.default_branch + name: Scorecard analysis + runs-on: ubuntu-24.04 + timeout-minutes: 20 + permissions: + contents: read + id-token: write # Lets Scorecard prove the published result's provenance. + security-events: write # Uploads the generated Scorecard SARIF to Code Scanning. + steps: + - name: "Checkout code" + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + persist-credentials: false + + - name: "Run analysis" + uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1 + with: + results_file: results.sarif + results_format: sarif + # Scorecard team runs a weekly scan of public GitHub repos, + # see https://github.com/ossf/scorecard#public-data. + # Setting `publish_results: true` helps us scale by leveraging your workflow to + # extract the results instead of relying on our own infrastructure to run scans. + # And it's free for you! + publish_results: true + + # Upload the results as artifacts (optional). Commenting out will disable + # uploads of run results in SARIF format to the repository Actions tab. + # https://docs.github.com/en/actions/advanced-guides/storing-workflow-data-as-artifacts + - name: "Upload artifact" + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + + - name: Upload SARIF to Code Scanning + uses: github/codeql-action/upload-sarif@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8 + with: + sarif_file: results.sarif + category: scorecard + + repo-linter: + if: >- + github.ref_type == 'branch' && + github.ref_name == github.event.repository.default_branch + name: Repo Linter + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: 'Run Repolinter' + uses: newrelic/repolinter-action@3f4448f855c351e9695b24524a4111c7847b84cb # v1.7.0 + + reuse-compliance: + if: >- + github.ref_type == 'branch' && + github.ref_name == github.event.repository.default_branch + name: REUSE Compliance + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: REUSE Compliance Check + uses: fsfe/reuse-action@676e2d560c9a403aa252096d99fcab3e1132b0f5 # v6.0.0 diff --git a/.sync-exclude b/.sync-exclude new file mode 100644 index 0000000..ec7f7ac --- /dev/null +++ b/.sync-exclude @@ -0,0 +1,4 @@ +# One normalized relative path per line. Lines in this file are not reconciled by InditexTech CI governance. +CODE_OF_CONDUCT.md +CONTRIBUTING.md +SECURITY.md diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..de881c3 --- /dev/null +++ b/NOTICE @@ -0,0 +1,13 @@ +Copyright 2025 INDUSTRIA DE DISEÑO TEXTIL S.A. (INDITEX S.A.) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/REUSE.toml b/REUSE.toml index df8521e..0d25d34 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -30,12 +30,15 @@ SPDX-FileComment = "Documentation" [[annotations]] path= [ ".github/**", + ".gitattributes", ".golangci.yml", ".*ignore", + ".sync-exclude", "chart/.helmignore", "chart/templates/_*.tpl", "hack/**", "internal/**", + "NOTICE", "PROJECT", "repolinter.json", "Tiltfile", diff --git a/repolinter.json b/repolinter.json index 3dd01f0..4e64643 100644 --- a/repolinter.json +++ b/repolinter.json @@ -61,7 +61,7 @@ } }, "readme-references-license": { - "level": "off", + "level": "error", "rule": { "type": "file-contents", "options": { @@ -76,17 +76,7 @@ "rule": { "type": "file-type-exclusion", "options": { - "type": ["/*.exe", "/.dll", "!node_modules/", "/*.bin"] - } - } - }, - "test-directory-exists": { - "level": "off", - "rule": { - "type": "directory-existence", - "options": { - "globsAny": ["/test", "/specs"], - "nocase": true + "type": ["/*.exe", "/.dll", "!node_modules/"] } } }, @@ -96,22 +86,13 @@ "type": "file-existence", "options": { "globsAny": [ - ".gitlab-ci.yml", - ".travis.yml", - "appveyor.yml", - ".appveyor.yml", - "circle.yml", - ".circleci/config.yml", - "Jenkinsfile", - ".drone.yml", - ".github/workflows/", - "azure-pipelines.yml" + ".github/workflows/" ] } } }, "code-of-conduct-file-contains-email": { - "level": "off", + "level": "error", "rule": { "type": "file-contents", "options": { @@ -129,18 +110,6 @@ } } }, - "source-license-headers-exist": { - "level": "warning", - "rule": { - "type": "file-starts-with", - "options": { - "globsAll": ["./**/*.py"], - "lineCount": 5, - "patterns": ["Copyright", "License"], - "flags": "i" - } - } - }, "github-issue-template-exists": { "level": "error", "rule": { @@ -152,7 +121,7 @@ } }, "github-pull-request-template-exists": { - "level": "off", + "level": "error", "rule": { "type": "file-existence", "options": { @@ -164,22 +133,111 @@ } } }, + "javascript-package-metadata-exists": { + "level": "error", + "where": ["language=javascript"], + "rule": { + "type": "file-existence", + "options": { + "globsAny": ["{,**/}package.json"] + } + } + }, + "ruby-package-metadata-exists": { + "level": "error", + "where": ["language=ruby"], + "rule": { + "type": "file-existence", + "options": { + "globsAny": ["Gemfile"] + } + } + }, + "java-package-metadata-exists": { + "level": "error", + "where": ["language=java"], + "rule": { + "type": "file-existence", + "options": { + "globsAny": ["{,**/}pom.xml", "{,**/}build.xml", "{,**/}build.gradle"] + } + } + }, + "python-package-metadata-exists": { + "level": "error", + "where": ["language=python"], + "rule": { + "type": "file-existence", + "options": { + "globsAny": [ + "{,**/}setup.py", + "{,**/}requirements.txt", + "{,**/}pyproject.toml" + ] + } + } + }, + "objective-c-package-metadata-exists": { + "level": "error", + "where": ["language=objective-c"], + "rule": { + "type": "file-existence", + "options": { + "globsAny": ["Cartfile", "Podfile", ".podspec"] + } + } + }, + "swift-package-metadata-exists": { + "level": "error", + "where": ["language=swift"], + "rule": { + "type": "file-existence", + "options": { + "globsAny": ["Package.swift"] + } + } + }, + "erlang-package-metadata-exists": { + "level": "error", + "where": ["language=erlang"], + "rule": { + "type": "file-existence", + "options": { + "globsAny": ["rebar.config"] + } + } + }, + "elixir-package-metadata-exists": { + "level": "error", + "where": ["language=elixir"], + "rule": { + "type": "file-existence", + "options": { + "globsAny": ["mix.exs"] + } + } + }, "license-detectable-by-licensee": { - "level": "off", + "level": "error", "where": ["license="], "rule": { "type": "license-detectable-by-licensee", "options": {} } }, - "best-practices-badge-present": { - "level": "off", + "notice-file-exists": { + "level": "error", + "where": ["license=Apache-2.0"], "rule": { - "type": "best-practices-badge-present" + "type": "file-existence", + "options": { + "globsAny": ["NOTICE*"], + "fail-message": "The NOTICE file is described in section 4.4 of the Apache License version 2.0. Its presence is not mandated by the license itself, but by ASF policy." + } } }, "internal-file-not-exists": { - "level": "off", + "level": "error", "rule": { "type": "file-not-exists", "options": {