Commit 1e732f4c authored by Ilya Rassadin's avatar Ilya Rassadin
Browse files

Set up CI/CD pipeline

parent 05ff35b6
Loading
Loading
Loading
Loading

.gitlab-ci.yml

0 → 100644
+20 −0
Original line number Diff line number Diff line
stages:
  - validate

pre-commit:
  stage: validate
  image: python:3.14-slim
  variables:
    PRE_COMMIT_HOME: ${CI_PROJECT_DIR}/.pre-commit-cache
  cache:
    key: pre-commit
    paths:
      - .pre-commit-cache/
  before_script:
    - apt-get update -qq && apt-get install -qqy git
    - pip install pre-commit --quiet
  script:
    - pre-commit run --all-files --hook-stage pre-push
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == "master"
+16 −0
Original line number Diff line number Diff line
repos:
  - repo: https://github.com/adrienverge/yamllint
    rev: v1.35.1
    hooks:
      - id: yamllint
        stages: [pre-push]

  - repo: local
    hooks:
      - id: gitlab-ci-lint
        name: Validate GitLab CI templates via API
        language: script
        entry: scripts/validate-gitlab-ci.py
        stages: [pre-push]
        pass_filenames: false
        always_run: true

.yamllint.yml

0 → 100644
+9 −0
Original line number Diff line number Diff line
extends: default
rules:
  document-start: disable
  line-length:
    max: 160
    level: warning
  truthy:
    allowed-values: ['true', 'false']
    check-keys: false
+101 −0
Original line number Diff line number Diff line
#!/usr/bin/env python3
import json
import os
import re
import subprocess
import sys
import tempfile
import urllib.request

GITLAB_URL = "https://gitlab.cetera.ru"
PROJECT_PATH = "boilerplate/ci"
INCLUDE_PATTERN = re.compile(
    r"https://gitlab\.cetera\.ru/boilerplate/ci/raw/[^/]+/([^\s'\"]+)"
)


def build_content():
    with open("gitlab-ci-template.yml") as f:
        template = f.read()

    parts = []
    for filename in INCLUDE_PATTERN.findall(template):
        with open(filename) as f:
            parts.append(f.read().rstrip())

    job_definitions = strip_include_block(template).strip()
    if job_definitions:
        parts.append(job_definitions)

    return "\n\n".join(parts)


def strip_include_block(content):
    lines = content.splitlines(keepends=True)
    result = []
    in_include = False
    for line in lines:
        if line.startswith("include:"):
            in_include = True
            continue
        if in_include:
            if line and not line[0].isspace() and not line.startswith("#"):
                in_include = False
                result.append(line)
        else:
            result.append(line)
    return "".join(result)


def validate_via_token(content, token):
    encoded_path = urllib.parse.quote(PROJECT_PATH, safe="")
    url = f"{GITLAB_URL}/api/v4/projects/{encoded_path}/ci/lint"
    data = json.dumps({"content": content}).encode()
    req = urllib.request.Request(
        url, data=data,
        headers={
            "Content-Type": "application/json",
            "PRIVATE-TOKEN": token,
        },
    )
    with urllib.request.urlopen(req) as response:
        return json.loads(response.read())


def validate_via_glab(content):
    with tempfile.NamedTemporaryFile(mode='w', suffix='.yml', delete=False) as f:
        f.write(content)
        tmpfile = f.name
    try:
        result = subprocess.run(["glab", "ci", "lint", tmpfile])
        return result.returncode == 0
    finally:
        os.unlink(tmpfile)


def main():
    content = build_content()
    print("Validating GitLab CI configuration from local files")

    gitlab_token = os.environ.get("GITLAB_TOKEN")
    if gitlab_token:
        result = validate_via_token(content, gitlab_token)
        for warning in result.get("warnings", []):
            print(f"Warning: {warning}")
        if result.get("valid"):
            print("GitLab CI configuration is valid")
            sys.exit(0)
        else:
            print("GitLab CI configuration is INVALID:")
            for error in result.get("errors", []):
                print(f"  - {error}")
            sys.exit(1)
    elif os.environ.get("CI_JOB_TOKEN"):
        print("Error: GITLAB_TOKEN is required in CI (CI_JOB_TOKEN lacks lint API permissions)", file=sys.stderr)
        sys.exit(1)
    else:
        sys.exit(0 if validate_via_glab(content) else 1)


if __name__ == "__main__":
    main()