From 17e434dd9226e7ef5b0daf43b564f913b7f4c98d Mon Sep 17 00:00:00 2001 From: BAK BYEONG JUN Date: Fri, 25 Apr 2025 19:21:50 +0000 Subject: [PATCH] init --- tasks/git-replace/task.yaml | 90 ++++++++++++++++++ tasks/openapi-generate/task.yaml | 77 ++++++++++++++++ tasks/openapi-version/task.yaml | 45 +++++++++ tasks/rust-nx-merge/task.yaml | 119 ++++++++++++++++++++++++ tasks/sonarqube-analysis/taks.yaml | 141 +++++++++++++++++++++++++---- 5 files changed, 455 insertions(+), 17 deletions(-) create mode 100644 tasks/git-replace/task.yaml create mode 100644 tasks/openapi-generate/task.yaml create mode 100644 tasks/openapi-version/task.yaml create mode 100644 tasks/rust-nx-merge/task.yaml diff --git a/tasks/git-replace/task.yaml b/tasks/git-replace/task.yaml new file mode 100644 index 0000000..e959185 --- /dev/null +++ b/tasks/git-replace/task.yaml @@ -0,0 +1,90 @@ +apiVersion: tekton.dev/v1 +kind: Task +metadata: + name: git-replace-multi + annotations: + tekton.dev/pipelines.minVersion: "0.19.0" + tekton.dev/categories: GitOps + tekton.dev/tags: git, devops + tekton.dev/displayName: "replace multiple files/dirs in git repository" + tekton.dev/platforms: "linux/amd64" +spec: + description: | + Replaces multiple files or directories in a Git repository, committing and pushing the changes. + + params: + - name: repositoryUrl + type: string + description: Source repository URL + + - name: branch + type: string + default: main + description: Git branch to push to + + - name: targetPaths + type: array + description: List of target paths in the repo (file or directory, e.g. dir/ or file) + + - name: sourcePaths + type: array + description: List of source paths in the workspace (file or directory, e.g. dir/ or file) + + - name: commitMessage + type: string + default: "chore(gitops): replace multiple files or dirs" + description: Commit message + + workspaces: + - name: base + - name: tmp + + steps: + - name: clone-repository + image: alpine/git:latest + script: | + #!/bin/sh + set -e + REPO_URL="$(params.repositoryUrl)" + BRANCH="$(params.branch)" + WORKSPACE_PATH="$(workspaces.tmp.path)" + git clone --branch "${BRANCH}" "${REPO_URL}" "${WORKSPACE_PATH}/repo" + cd "${WORKSPACE_PATH}/repo" + + - name: sync-files + image: alpine:3.18 + script: | + #!/bin/sh + set -e + TARGET_PATHS=($(params.targetPaths[*])) + SOURCE_PATHS=($(params.sourcePaths[*])) + BASE_PATH="$(workspaces.base.path)/source" + REPO_PATH="$(workspaces.tmp.path)/repo" + COUNT=${#TARGET_PATHS[@]} + if [ $COUNT -ne ${#SOURCE_PATHS[@]} ]; then + echo "targetPaths와 sourcePaths의 길이가 다릅니다." + exit 1 + fi + for i in $(seq 0 $(($COUNT - 1))); do + TARGET="${REPO_PATH}/${TARGET_PATHS[$i]}" + SOURCE="${BASE_PATH}/${SOURCE_PATHS[$i]}" + [ -e "${TARGET}" ] && rm -rf "${TARGET}" || true + mkdir -p $(dirname "${TARGET}") + cp -r "${SOURCE}" "${TARGET}" + done + + - name: commit-and-push + image: alpine/git:latest + script: | + #!/bin/sh + set -e + cd "$(workspaces.tmp.path)/repo" + git config --global user.name "tekton-bot" + git config --global user.email "tekton@example.com" + git add . + git commit -m "$(params.commitMessage)" || exit 0 + git push origin HEAD:"$(params.branch)" + + volumes: + - name: tmp + emptyDir: {} diff --git a/tasks/openapi-generate/task.yaml b/tasks/openapi-generate/task.yaml new file mode 100644 index 0000000..442c0bb --- /dev/null +++ b/tasks/openapi-generate/task.yaml @@ -0,0 +1,77 @@ +apiVersion: tekton.dev/v1 +kind: Task +metadata: + name: openapi-generate +spec: + params: + - name: context + type: string + description: context directory + default: "" + + - name: packageNamePrefix + description: Rust crate name prefix + type: string + default: "" + - name: specDomain + type: string + default: "" + - name: specVersion + description: Rust crate version + type: string + default: "0.0.0" + - name: generator + description: specify the generator + type: string + default: "" + + # 배열 타입으로 변경 + - name: generatorOptions + type: array + description: | + openapi-generator-cli options + ex) ['--additional-properties=key=value', '--enable-post-process-file'] + default: [] + + workspaces: + - name: base + description: Git-cloned source code + + results: + - name: output + + steps: + # split-arguments 단계 제거 + + - name: generate-code + image: openapitools/openapi-generator-cli:v7.4.0 + workingDir: /workspace/base/$(params.context)/source + env: + - name: HOME + value: /workspace/base/$(params.context)/home + script: | + #!/usr/bin/env bash + set -e + + OPENAPI_FILE="specs/$(params.specDomain)/openapi.yaml" + PACKAGE_NAME="$(params.packageNamePrefix)$(params.context)" + OUTPUT="/workspace/base/$(params.context)/output/${PACKAGE_NAME}-$(date +%s)-$(head /dev/urandom | tr -dc a-z0-9 | head -c 6)" + + if [ ! -d "$OUTPUT" ]; then + mkdir -p "$OUTPUT" + fi + + # 배열 파라미터 직접 참조 + GENERATOR_OPTIONS=("${params.generatorOptions[@]}") + + openapi-generator-cli generate \ + -i ${OPENAPI_FILE} \ + -g $(params.generator) \ + -o $OUTPUT \ + --additional-properties=packageName="${PACKAGE_NAME}" \ + --additional-properties=packageVersion=$(params.specVersion) \ + --additional-properties=publish=true \ + --additional-properties=disableValidator=false \ + "${GENERATOR_OPTIONS[@]}" # 배열 확장 + + echo -n "${OUTPUT}" > $(results.output.path) diff --git a/tasks/openapi-version/task.yaml b/tasks/openapi-version/task.yaml new file mode 100644 index 0000000..0af9c59 --- /dev/null +++ b/tasks/openapi-version/task.yaml @@ -0,0 +1,45 @@ +apiVersion: tekton.dev/v1 +kind: Task +metadata: + name: nx-nodejs-version +spec: + params: + - name: context + type: string + description: context directory + default: "" + - name: specDomain + type: string + default: "" + - name: specVersion + description: Rust crate version + type: string + default: "0.0.0" + workspaces: + - name: base + description: Git-cloned source code + results: + - name: version + description: Extracted project version (e.g. 0.2.0) + steps: + - name: verify-version + image: mikefarah/yq:4.24.2 + workingDir: /workspace/base/$(params.context)/source + env: + - name: HOME + value: /workspace/base/$(params.context)/home + script: | + set -e + OPENAPI_FILE="specs/$(params.specDomain)/openapi.yaml" + EXPECTED_VERSION="$(params.specVersion)" + ACTUAL_VERSION=$(yq '.info.version' "$OPENAPI_FILE") + + echo "Expected: $EXPECTED_VERSION" + echo "Actual: $ACTUAL_VERSION" + + if [ "$ACTUAL_VERSION" != "$EXPECTED_VERSION" ]; then + echo "❌ Version mismatch! Expected: $EXPECTED_VERSION, Actual: $ACTUAL_VERSION" + exit 1 + fi + echo "✅ Tag and version match: $ACTUAL_VERSION" + echo -n "$ACTUAL_VERSION" > /tekton/results/version \ No newline at end of file diff --git a/tasks/rust-nx-merge/task.yaml b/tasks/rust-nx-merge/task.yaml new file mode 100644 index 0000000..e64298b --- /dev/null +++ b/tasks/rust-nx-merge/task.yaml @@ -0,0 +1,119 @@ +apiVersion: tekton.dev/v1 +kind: Task +metadata: + name: rust-nx-merge + annotations: + tekton.dev/pipelines.minVersion: "0.19.0" + tekton.dev/categories: GitOps + tekton.dev/tags: git, devops, nx, rust + tekton.dev/displayName: "Merge Rust projects with nx import" + tekton.dev/platforms: "linux/amd64" +spec: + description: | + Clones a git repository, merges Rust projects using nx import, + updates target project versions to match source versions. + + params: + - name: context + type: string + description: context directory + default: "" + + - name: repositoryUrl + type: string + description: Target repository URL + + - name: branch + type: string + default: "main" + description: Git branch to operate on + + - name: workspaceName + type: string + description: Nx workspace project name to lint and test + + - name: targetProjects + type: array + description: List of target project paths in the repo (e.g., ["libs/project1"]) + + - name: sourceProjects + type: array + description: List of source project paths in workspace (e.g., ["generated/project1"]) + + - name: commitMessage + type: string + default: "chore: merge projects and sync versions" + description: Commit message + + workspaces: + - name: base + - name: tmp + + steps: + - name: clone-repository + image: alpine/git:latest + workingDir: /workspace/tmp + env: + - name: HOME + value: /workspace/base/$(params.context)/home + script: | + #!/bin/sh + set -e + git clone -b $(params.branch) $(params.repositoryUrl) repo + cd repo + + - name: merge-projects + image: node:18 + workingDir: /workspace/tmp + script: | + #!/bin/sh + set -e + + # Install nx if not present + command -v nx >/dev/null || npm install -g nx + + # Get project pairs + TARGETS=($(params.targetProjects[*])) + SOURCES=($(params.sourceProjects[*])) + + if [ ${#TARGETS[@]} -ne ${#SOURCES[@]} ]; then + echo "Error: targetProjects and sourceProjects length mismatch" + exit 1 + fi + + # Process each project pair + cd repo/$(params.workspaceName) + + for i in $(seq 0 $((${#TARGETS[@]}-1))); do + SOURCE_PATH="${SOURCES[$i]}" + TARGET_PROJECT="${TARGETS[$i]}" + TARGET_PATH="repo/$(params.workspaceName)/${TARGET_PROJECT}" + + # Import project + echo "Importing ${SOURCE_PATH} to ${TARGET_PROJECT}" + npx nx import "${SOURCE_PATH}" "${TARGET_PROJECT}" + + # Version sync + SRC_VERSION=$(grep '^version =' "${SOURCE_PATH}/Cargo.toml" | cut -d'"' -f2) + sed -i.bak "s/^version = .*/version = \"${SRC_VERSION}\"/" "${TARGET_PATH}/Cargo.toml" + rm -f "${TARGET_PATH}/Cargo.toml.bak" + done + + - name: commit-and-push + image: alpine/git:latest + workingDir: /workspace/tmp + env: + - name: HOME + value: /workspace/base/$(params.context)/home + script: | + #!/bin/sh + set -e + cd repo" + + git add . + git commit -m "$(params.commitMessage)" || exit 0 + git push origin HEAD:"$(params.branch)" + + volumes: + - name: tmp + emptyDir: {} diff --git a/tasks/sonarqube-analysis/taks.yaml b/tasks/sonarqube-analysis/taks.yaml index 64cec4c..bcc39d0 100644 --- a/tasks/sonarqube-analysis/taks.yaml +++ b/tasks/sonarqube-analysis/taks.yaml @@ -7,38 +7,145 @@ spec: - name: context type: string default: "" - description: context directory + description: "소스코드가 있는 하위 디렉토리 (없을 경우 '')" - - name: sonarHostUrl + - name: sonarqubeUrl type: string default: "https://sonarqube.unbox-x.net" - description: SonarQube server URL + description: SonarQube 서버 URL - name: projectKey type: string - description: SonarQube project key + description: SonarQube 프로젝트 키 + + - name: architecture + type: string + description: 프로젝트 언어: python | nodejs | typescript | rust + + - name: coverageEnabled + type: string + default: "true" + description: "커버리지 수집 여부 (true | false)" + + - name: qualityGateEnabled + type: string + default: "false" + description: "Quality Gate 후속 처리 활성화 여부 (예: Slack 알림 등)" workspaces: - name: base - description: Workspace with shared code (e.g. from git-clone) + description: 소스코드가 위치한 Workspace (보통 git-clone 결과) - - name: sonar-auth - description: | - Workspace containing authentication token (file: `token`) + - name: sonarqube-credentials + description: SonarQube 인증용 토큰이 포함된 Workspace (파일명: token) steps: - - name: sonar-scan - image: sonarsource/sonar-scanner-cli:5 - workingDir: /workspace/base/$(params.context)/source + - name: prepare-and-analyze + image: ubuntu:22.04 + workingDir: /workspace/base/$(params.context) + env: + - name: DEBIAN_FRONTEND + value: noninteractive script: | - #!/bin/sh + #!/bin/bash set -e - SONAR_TOKEN=$(cat /workspace/sonar-auth/token) + PROJECT_KEY=$(params.projectKey) + ARCHITECTURE=$(params.architecture) + SONARQUBE_URL=$(params.sonarqubeUrl) + SONAR_TOKEN=$(cat /workspace/sonarqube-credentials/token) + COVERAGE_ENABLED=$(params.coverageEnabled) + QUALITY_GATE_ENABLED=$(params.qualityGateEnabled) - echo "📡 Running SonarQube analysis on project $(params.projectKey)..." + echo "📦 Preparing for architecture: $ARCHITECTURE" + echo "🛡️ Coverage enabled? $COVERAGE_ENABLED" + echo "🎯 Quality Gate enabled? $QUALITY_GATE_ENABLED" + + COVERAGE_OPTION="" + + case "$ARCHITECTURE" in + python) + apt update && apt install -y python3-pip curl unzip python3-venv + pip install --upgrade pip + + # 설치 방식 결정: pyproject.toml + poetry.lock → poetry / requirements.txt → pip + if [ -f "pyproject.toml" ] && [ -f "poetry.lock" ]; then + # Poetry 설치 (선택적) + pip install poetry --root-user-action=ignore + + echo "📦 Using Poetry for dependency management" + poetry lock + poetry install --with dev + + if [ "$COVERAGE_ENABLED" = "true" ]; then + echo "🧪 Running pytest with coverage (Poetry)" + poetry run pytest --cov=. --cov-report=xml + COVERAGE_OPTION="-Dsonar.python.coverage.reportPaths=coverage.xml" + fi + + elif [ -f "requirements.txt" ]; then + echo "📦 Using pip + venv for dependency management" + python3 -m venv venv + source venv/bin/activate + pip install -r requirements.txt --root-user-action=ignore + pip install pytest pytest-cov + + if [ "$COVERAGE_ENABLED" = "true" ]; then + echo "🧪 Running pytest with coverage (pip)" + pytest --cov=. --cov-report=xml + COVERAGE_OPTION="-Dsonar.python.coverage.reportPaths=coverage.xml" + fi + + else + echo "❌ Python project must contain either pyproject.toml+poetry.lock or requirements.txt" + exit 1 + fi + ;; + + nodejs|typescript) + curl -fsSL https://deb.nodesource.com/setup_20.x | bash - + apt install -y nodejs curl unzip + npm install + if [ "$COVERAGE_ENABLED" = "true" ]; then + echo "🧪 Running npm test with coverage" + npm run test -- --coverage + COVERAGE_OPTION="-Dsonar.javascript.lcov.reportPaths=coverage/lcov.info" + fi + ;; + rust) + apt update && apt install -y curl unzip pkg-config libssl-dev + curl https://sh.rustup.rs -sSf | bash -s -- -y + source $HOME/.cargo/env + cargo install cargo-tarpaulin + if [ "$COVERAGE_ENABLED" = "true" ]; then + echo "🧪 Running cargo tarpaulin" + cargo tarpaulin --out Xml + # Rust는 coverage 연동이 공식적으로 어려워 생략 + COVERAGE_OPTION="" + fi + ;; + *) + echo "❌ 지원하지 않는 아키텍처입니다: $ARCHITECTURE" + exit 1 + ;; + esac + + echo "⬇️ Installing SonarScanner" + curl -sSLo sonar-scanner.zip https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-7.1.0.4889-linux-x64.zip + unzip sonar-scanner.zip + export PATH="$PWD/sonar-scanner-cli-7.1.0.4889-linux-x64/bin:$PATH" + + echo "📡 Running SonarQube analysis on project: $PROJECT_KEY" sonar-scanner \ - -Dsonar.projectKey=$(params.projectKey) \ - -Dsonar.host.url=$(params.sonarHostUrl) \ - -Dsonar.login=$SONAR_TOKEN + -Dsonar.projectKey=$PROJECT_KEY \ + -Dsonar.projectName=$PROJECT_KEY \ + -Dsonar.sources=. \ + -Dsonar.host.url=$SONARQUBE_URL \ + -Dsonar.login=$SONAR_TOKEN \ + $COVERAGE_OPTION + + if [ "$QUALITY_GATE_ENABLED" = "true" ]; then + echo "🔍 Quality Gate 후속 처리를 위한 Hook 실행 가능 (Slack, Webhook 등)" + # 여기에 Slack 연동, ArgoCD 알림, 등 후속 로직 연동 가능 + fi