diff --git a/.github/ISSUE_TEMPLATE/bug-report.yaml b/.github/ISSUE_TEMPLATE/bug-report.yaml index 4e42c66a0..305d16f2f 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yaml +++ b/.github/ISSUE_TEMPLATE/bug-report.yaml @@ -3,33 +3,33 @@ description: Report a bug of DevStream title: ":bomb: `Bug`: " labels: bug body: -- type: textarea - id: problem - attributes: - label: What Happened? - description: Please provide as much information as possible. - validations: - required: true -- type: textarea - id: repro - attributes: - label: How to Reproduce? - validations: - required: false -- type: textarea - id: else - attributes: - label: Anything else - validations: - required: false -- type: dropdown - id: version - attributes: - label: 'DevStream Version' - description: "To find out the version run: `dtm version`" - options: - - < v0.10.3 - - v0.10.3 - - latest - validations: - required: true + - type: textarea + id: problem + attributes: + label: What Happened? + description: Please provide as much information as possible. + validations: + required: true + - type: textarea + id: repro + attributes: + label: How to Reproduce? + validations: + required: false + - type: textarea + id: else + attributes: + label: Anything else + validations: + required: false + - type: dropdown + id: version + attributes: + label: 'DevStream Version' + description: "To find out the version run: `dtm version`" + options: + - < v0.13.0 + - v0.13.0 + - latest + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yaml similarity index 100% rename from .github/ISSUE_TEMPLATE/config.yml rename to .github/ISSUE_TEMPLATE/config.yaml diff --git a/.github/ISSUE_TEMPLATE/documentation.yaml b/.github/ISSUE_TEMPLATE/documentation.yaml index 9c252042a..0bf694bec 100644 --- a/.github/ISSUE_TEMPLATE/documentation.yaml +++ b/.github/ISSUE_TEMPLATE/documentation.yaml @@ -3,17 +3,17 @@ description: Help to improve our documentation title: ":open_book: `Docs`: " labels: docs body: -- type: textarea - id: problem - attributes: - label: What should be changed? - description: Please provide as much info as possible. - validations: - required: true -- type: checkboxes - id: terms - attributes: - label: Please read the documents below. - options: - - label: I've read the document [Creating a Documentation for DevStream](https://docs.devstream.io/en/latest/development/mkdocs/) and [Document Translation(Chinese only)](https://docs.devstream.io/en/latest/development/translation.zh/). (If you want to submit a document type contribution.) - required: false + - type: textarea + id: problem + attributes: + label: What should be changed? + description: Please provide as much info as possible. + validations: + required: true + - type: checkboxes + id: terms + attributes: + label: Please read the documents below. + options: + - label: I've read the document [Creating a Documentation for DevStream](https://docs.devstream.io/en/latest/development/mkdocs/) and [Document Translation(Chinese only)](https://docs.devstream.io/en/latest/development/translation.zh/). (If you want to submit a document type contribution.) + required: false diff --git a/.github/ISSUE_TEMPLATE/proposal.md b/.github/ISSUE_TEMPLATE/proposal.md index c848bdea0..95afe4941 100644 --- a/.github/ISSUE_TEMPLATE/proposal.md +++ b/.github/ISSUE_TEMPLATE/proposal.md @@ -6,18 +6,17 @@ labels: proposal --- ## What Would You Like to Add? Why Is This Needed? - + --> -## Design + ## Design -## Anything else - - + ## Anything else + diff --git a/.github/sync-staging-repo.yml b/.github/sync-staging-repo.yml deleted file mode 100644 index 265d8156b..000000000 --- a/.github/sync-staging-repo.yml +++ /dev/null @@ -1,24 +0,0 @@ -devstream-io/dtm-jenkins-share-library: - - source: staging/dtm-jenkins-share-library/ - dest: . - deleteOrphaned: true -devstream-io/dtm-pipeline-templates: - - source: staging/dtm-pipeline-templates/ - dest: . - deleteOrphaned: true -devstream-io/dtm-repo-scaffolding-golang-cli: - - source: staging/dtm-repo-scaffolding-golang-cli/ - dest: . - deleteOrphaned: true -devstream-io/dtm-repo-scaffolding-golang-gin: - - source: staging/dtm-repo-scaffolding-golang-gin/ - dest: . - deleteOrphaned: true -devstream-io/dtm-repo-scaffolding-java-springboot: - - source: staging/dtm-repo-scaffolding-java-springboot/ - dest: . - deleteOrphaned: true -devstream-io/dtm-repo-scaffolding-python-flask: - - source: staging/dtm-repo-scaffolding-python-flask/ - dest: . - deleteOrphaned: true diff --git a/.github/workflows/automated-release.yml b/.github/workflows/automated-release.yml deleted file mode 100644 index 96e248350..000000000 --- a/.github/workflows/automated-release.yml +++ /dev/null @@ -1,87 +0,0 @@ -name: automated release -on: - release: - types: [published] -jobs: - # release for linux - build-and-release-on-linux: - env: - TAG: ${{ github.ref_name }} - USER: devstream-io - REPO: devstream - PLUGINDIR: ~/.devstream/plugins - GOOS: linux - GOARCH: amd64 - runs-on: [self-hosted, linux, X64] - steps: - - run: echo "🐧 This job is now running on a ${{ runner.os }}-${{ runner.arch }} server hosted by GitHub!" - - name: Checkout - uses: actions/checkout@v3 - with: - ref: ${{ env.TAG }} - - name: Setup Golang env - uses: actions/setup-go@v3 - with: - go-version: 1.18 - cache: true - - name: Build - run: make build -j8 - - name: Install Github-release - run: go install github.com/github-release/github-release@latest - - name: Install AWS CLI v2 - run: | - curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o /tmp/awscliv2.zip - unzip -q /tmp/awscliv2.zip -d /tmp - rm /tmp/awscliv2.zip - sudo /tmp/aws/install --update - rm -rf /tmp/aws/ - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v1 - with: - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws-region: ap-southeast-1 - - name: check md5 & commit - run: git rev-parse HEAD && md5sum dtm* - - name: upload core - run: bash -e ./hack/release/upload_dtm_core.sh ${{ secrets.GITHUB_TOKEN }} ${{ env.TAG }} ${{ env.GOOS }} ${{ env.GOARCH }} - - name: upload plugin - run: aws s3 cp ~/.devstream/plugins/ s3://download.devstream.io/${{ env.TAG }}/ --recursive --acl public-read - - # release for darwin-amd64 - build-and-release-on-darwin-amd64: - env: - TAG: ${{ github.ref_name }} - USER: devstream-io - REPO: devstream - PLUGINDIR: ~/.devstream/plugins - GOOS: darwin - GOARCH: amd64 - runs-on: macos-latest - steps: - - run: echo "🐧 This job is now running on a ${{ runner.os }}-${{ runner.arch }} server hosted by GitHub!" - - name: Checkout - uses: actions/checkout@v3 - with: - ref: ${{ env.TAG }} - - name: Setup Golang env - uses: actions/setup-go@v3 - with: - go-version: 1.18 - cache: true - - name: Build - run: make build -j8 - - name: Install Github-release - run: go install github.com/github-release/github-release@latest - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v1 - with: - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws-region: ap-southeast-1 - - name: update dtm version on s3 - run: ./hack/release/update_dtm_version_on_s3.sh ${{ env.TAG }} - - name: upload dtm core to github release and s3 - run: bash -e ./hack/release/upload_dtm_core.sh ${{ secrets.GITHUB_TOKEN }} ${{ env.TAG }} ${{ env.GOOS }} ${{ env.GOARCH }} - - name: upload plugins to s3 - run: aws s3 cp ~/.devstream/plugins/ s3://download.devstream.io/${{ env.TAG }}/ --recursive --acl public-read diff --git a/.github/workflows/cf-ip-monitor.yml b/.github/workflows/cf-ip-monitor.yml deleted file mode 100644 index c70c15867..000000000 --- a/.github/workflows/cf-ip-monitor.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Cloudflare IP Monitor - -on: - schedule: - - cron: 0 12 * * * # every day at 12:00 - -jobs: - check: - runs-on: [ self-hosted, linux, X64 ] - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Check IPs - run: | - cd hack/cf-ip-monitor || exit - bash check.sh - shell: bash - - name: Create Issue - uses: JasonEtco/create-an-issue@v2 - if: env.CHANGED == 1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - filename: hack/cf-ip-monitor/ISSUE_TEMPLATE.md diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 000000000..7cb492296 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,23 @@ +name: CI + +on: + push: + branches: + - main + pull_request: + branches: + - main +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: "1.20.3" + cache: true + - name: Run tests + run: go test -v ./... + - name: Build project + run: make build diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml deleted file mode 100644 index c98ff6cb9..000000000 --- a/.github/workflows/e2e-test.yml +++ /dev/null @@ -1,126 +0,0 @@ -name: e2e test - -on: - push: - branches: [ main ] - paths: - - '**.go' - - 'Makefile' - - 'go.mod' - - 'go.sum' - - '.github/workflows/*.yml' - - 'test/**' - - 'hack/e2e/**' - - 'hack/terraform/**' - -env: - # DO NOT use the GITHUB_TOKEN here - # see https://github.com/devstream-io/devstream/pull/414 for more info - GITHUB_TOKEN: ${{ secrets.E2E_GITHUB_TOKEN }} - DOCKERHUB_USERNAME: ${{ secrets.E2E_DOCKERHUB_USERNAME }} - # for github actions - DOCKERHUB_TOKEN: ${{ secrets.E2E_DOCKERHUB_TOKEN }} - # for apps - IMAGE_REPO_PASSWORD: ${{ secrets.E2E_DOCKERHUB_TOKEN }} - # token for gitlab.com (of aFlyBird0 now) - GITLAB_TOKEN: ${{ secrets.GITLAB_TOKEN }} - -concurrency: - group: ${{ github.workflow }} - -jobs: - e2e-test: - # permissions for id and other - permissions: - id-token: write - contents: write - if: github.repository == 'devstream-io/devstream' - strategy: - matrix: - os: [ubuntu-latest] - go: [1.18.x] - runs-on: [self-hosted, linux, X64] - name: e2e-test-${{ matrix.os }} - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Setup Golang env - uses: actions/setup-go@v3 - with: - go-version: ${{ matrix.go }} - cache: true - - name: Build - run: make build -j8 - - name: Install AWS CLI v2 - run: | - curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o /tmp/awscliv2.zip - unzip -q /tmp/awscliv2.zip -d /tmp - rm /tmp/awscliv2.zip - sudo /tmp/aws/install --update - rm -rf /tmp/aws/ - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@master - with: - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws-region: ap-southeast-1 - - name: Configure EKS credentials - run: | - aws eks update-kubeconfig --region ap-southeast-1 --name dtm-test - - name: Install kubectl - run: | - curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.22.0/bin/linux/amd64/kubectl - chmod +x ./kubectl - sudo mv ./kubectl /usr/local/bin/kubectl - - name: copy config files - run: cp ./test/e2e/yaml/e2e-*.yaml ./ - # test 1 is git-ops with tools only - - name: test 1 - apply git-ops (tools only) - run: ./dtm apply -f e2e-tools.yaml -y - - name: test 1 - apply git-ops (tools only) again - run: ./dtm apply -f e2e-tools.yaml -y - - name: test 1 - check if pod is ready - run: while [[ $(kubectl get pods -l app=dtme2epython -o 'jsonpath={..status.conditions[?(@.type=="Ready")].status}') != "True" ]]; do echo "pod not ready yet..."; sleep 3; done - timeout-minutes: 10 - - name: test 1 - verify - run: ./dtm verify -f e2e-tools.yaml - - name: test 1 - clean - run: ./dtm delete -f e2e-tools.yaml -y - # test 2 is git-ops with apps - - name: test 2 - apply (apps) - run: ./dtm apply --debug -f e2e-apps.yaml -y - - name: test 2 - apply (apps) again - run: ./dtm apply -f e2e-apps.yaml -y - - name: test 2 - check if pod is ready - run: while [[ $(kubectl get pods -l app=dtm-e2e-go -o 'jsonpath={..status.conditions[?(@.type=="Ready")].status}') != "True" ]]; do echo "pod not ready yet..."; sleep 3; done - timeout-minutes: 10 - - name: test 2 - check gin app can be reached - run: | - # get cluster ip of the service created by the argocd - clusterIP=$(kubectl get svc dtm-e2e-go -n default -o jsonpath='{.spec.clusterIP}') - # curl gin url and check if it returns content which contains "Blue Train", if not, echo error and exit 1 - curl -s http://$clusterIP:8080/albums/1 | grep "Blue Train" || (echo "failed to access to gin app" && exit 1) - - name: test 2 - clean - run: ./dtm delete -f e2e-apps.yaml -y - # test3 is gitlabci-argocd - - name: test 3 - apply (gitlabci-argocd) - run: ./dtm apply --debug -f e2e-gitlabci-argocd.yaml -y - - name: test 3 - apply (gitlabci-argocd) again - run: ./dtm apply -f e2e-gitlabci-argocd.yaml -y - - name: test 3 - check if pod is ready - run: while [[ $(kubectl get pods -l app=dtme2egitlabciargocd -o 'jsonpath={..status.conditions[?(@.type=="Ready")].status}') != "True" ]]; do echo "pod not ready yet..."; sleep 3; done - timeout-minutes: 10 - - name: test 3 - check flask app can be reached - run: | - # get cluster ip of the service created by the argocd - clusterIP=$(kubectl get svc dtme2egitlabciargocd -n default -o jsonpath='{.spec.clusterIP}') - # curl flask url and check if it returns "Hello, World!", if not, echo error and exit 1 - curl -s http://$clusterIP:8080 | grep "Hello, World!" || (echo "failed to access to flask app" && exit 1) - - name: test 2 - clean - run: ./dtm delete -f e2e-gitlabci-argocd.yaml -y - - name: test e2e success or not - if: failure() - run: | - echo "e2e actions failure" - echo ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - curl -X POST -H "Content-Type: application/json" -d '{"msg_type":"text","content":{"text":"AllAttention: dtm-actions failure: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} "}}' https://open.feishu.cn/open-apis/bot/v2/hook/${{ secrets.FEISHU_BOT_ID }} diff --git a/.github/workflows/license-scan.yml b/.github/workflows/license-scan.yml deleted file mode 100644 index 7a3cea7e7..000000000 --- a/.github/workflows/license-scan.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: license check by FOSS -on: - pull_request - -jobs: - fossa: - runs-on: [self-hosted, linux, X64] - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - uses: fossa-contrib/fossa-action@v1 - with: - # https://docs.fossa.com/docs/api-reference#push-only-api-token - fossa-api-key: 047e53daaa907de4fda24866d0346f1d diff --git a/.github/workflows/link-pr.yml b/.github/workflows/link-pr.yml deleted file mode 100644 index 029878531..000000000 --- a/.github/workflows/link-pr.yml +++ /dev/null @@ -1,57 +0,0 @@ -name: links when pr - -on: - pull_request: - branches: [ main ] - paths: - - '**.md' - - '.lycheeignore' - -jobs: - linkChecker: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - # replace site base url to build MkDocs in local - - name: Replace Base URL - run: | - sed -i 's|https://docs.devstream.io|http://localhost|g' mkdocs.yml - - # disable "edit this page" link - # see https://github.com/devstream-io/devstream/issues/1386 for more details - - name: Disable Edit This Page - run: | - # edit_uri: edit/main/docs -> edit_uri: "" - sed -i 's|edit_uri: edit/main/docs|edit_uri: ""|g' mkdocs.yml - - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: "3.10" - - - name: Install MkDocs and dependencies - run: | - python -m pip install --upgrade pip - pip install -r docs/requirements.txt - - - name: Build MkDocs - run: mkdocs build - - - name: Link Checker - uses: lycheeverse/lychee-action@v1.5.0 - with: - fail: true - # For parameter description, see https://github.com/lycheeverse/lychee#commandline-parameters - # -E, --exclude-all-private Exclude all private IPs from checking. - # -i, --insecure Proceed for server connections considered insecure (invalid TLS) - # -n, --no-progress Do not show progress bar. - # -t, --timeout Website timeout in seconds from connect to response finished [default:20] - # --max-concurrency Maximum number of concurrent network requests [default: 128] - # -a --accept Comma-separated list of accepted status codes for valid links - - # ./site the MkDocs site directory to check - # ./*.md all markdown files in the root directory - args: -E -i -n -t 45 --max-concurrency 64 -a 429,401 -- 'site' '*.md' - env: - GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/.github/workflows/link.yml b/.github/workflows/link.yml deleted file mode 100644 index a4e16fcc7..000000000 --- a/.github/workflows/link.yml +++ /dev/null @@ -1,61 +0,0 @@ -name: links - -on: - repository_dispatch: - workflow_dispatch: - schedule: - - cron: "30 8 * * *" - -jobs: - linkChecker: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: Download Exclude Path - run: | - curl https://raw.githubusercontent.com/devstream-io/devstream/main/.lycheeignore --output .lycheeignore - - # replace site base url to build MkDocs in local - - name: Replace Base URL - run: | - sed -i 's|https://docs.devstream.io|http://localhost|g' mkdocs.yml - - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: "3.10" - - - name: Install MkDocs and dependencies - run: | - python -m pip install --upgrade pip - pip install -r docs/requirements.txt - - - name: Build MkDocs - run: mkdocs build - - - name: Check Links - uses: lycheeverse/lychee-action@v1.5.0 - with: - # For parameter description, see https://github.com/lycheeverse/lychee#commandline-parameters - # -E, --exclude-all-private Exclude all private IPs from checking. - # -v, --verbose Verbose program output - # -i, --insecure Proceed for server connections considered insecure (invalid TLS) - # -n, --no-progress Do not show progress bar. - # -t, --timeout Website timeout in seconds from connect to response finished [default:20] - # --max-concurrency Maximum number of concurrent network requests [default: 128] - # -a --accept Comma-separated list of accepted status codes for valid links - - # ./site the MkDocs site directory to check - # ./*.md all markdown files in the root directory - args: -E -v -i -n -t 45 --max-concurrency 64 -a 429,401 -- 'site' '*.md' - output: out.md - env: - GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} - - - name: Create Issue From File - uses: peter-evans/create-issue-from-file@v3 - with: - title: Broken Link Detected - content-filepath: out.md -# assignees: aFlyBird0 diff --git a/.github/workflows/lint-commit-message.yml b/.github/workflows/lint-commit-message.yml deleted file mode 100644 index ce5583b83..000000000 --- a/.github/workflows/lint-commit-message.yml +++ /dev/null @@ -1,13 +0,0 @@ -name: lint commit message -on: [ push, pull_request ] - -jobs: - lint-commit-message: - runs-on: [self-hosted, linux, X64] - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - uses: wagoid/commitlint-github-action@v4 - with: - configFile: ${{ github.workspace }}/hack/commitlint.config.js diff --git a/.github/workflows/lint-yaml.yml b/.github/workflows/lint-yaml.yml deleted file mode 100644 index 2542f135e..000000000 --- a/.github/workflows/lint-yaml.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: Yaml Lint - -on: - pull_request: - branches: [ main ] - paths: - - '**.yaml' - - '**.yml' - -jobs: - yamlLint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Remove All Strings In Square Brackets - run: | - # remove strings in "[[]]" in .yml or .yaml files, or yaml lint will fail - sed -i "s/\[\[.*\]\]//g" `grep "\[\[.*\]\]" -rl --include="*.yml" --include="*.yaml" .` - - name: Yaml Lint - uses: ibiqlik/action-yamllint@v3 - with: - file_or_dir: . - config_file: .yamllint.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index 68b6a20ba..000000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: main builder - -on: - push: - branches: [ main ] - paths: - - '**.go' - - 'Makefile' - - 'go.mod' - - 'go.sum' - - '.github/workflows/*.yml' - - 'test/**' - - 'hack/e2e/**' - - 'hack/terraform/**' -jobs: - build-and-test: - strategy: - matrix: - os: [macos-latest] - go: [1.18.x] - include: - - os: macos-latest - runs-on: ${{ matrix.os }} - name: build-test-${{ matrix.os }} - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Setup Golang env - uses: actions/setup-go@v3 - with: - go-version: ${{ matrix.go }} - cache: true - - name: Test - run: go test -v ./... - - name: Build - run: make build -j8 - - name: test main actions success or not - if: failure() - run: | - echo "main actions failure" - echo ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - curl -X POST -H "Content-Type: application/json" -d '{"msg_type":"text","content":{"text":"AllAttention: dtm-actions failure: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} "}}' https://open.feishu.cn/open-apis/bot/v2/hook/${{ secrets.FEISHU_BOT_ID }} diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml deleted file mode 100644 index 6d03c8c58..000000000 --- a/.github/workflows/pr.yml +++ /dev/null @@ -1,57 +0,0 @@ -name: pull request builder - -on: - pull_request: - branches: [ main ] - paths: - - '**.go' - - 'Makefile' - - 'go.mod' - - 'go.sum' - - '.github/workflows/*.yml' - - 'test/**' - - 'hack/e2e/**' - - 'hack/terraform/**' - -env: - go-version: 1.18.x -jobs: - golang-lint: - name: Golang lint - runs-on: [self-hosted, linux, X64] - permissions: - contents: read - steps: - - uses: actions/checkout@v3 - - name: Install Go ${{ env.go-version }} - uses: actions/setup-go@v3 - with: - go-version: ${{ env.go-version }} - cache: true - - name: golangci-lint - uses: golangci/golangci-lint-action@v3 - with: - version: v1.46 - args: --timeout 5m0s - build-and-test: - needs: [golang-lint] - strategy: - matrix: - os: [ubuntu-latest] - go: [1.18.x] - include: - - os: ubuntu-latest - runs-on: [self-hosted, linux, X64] - name: build-test-${{ matrix.os }} - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Setup Golang env - uses: actions/setup-go@v3 - with: - go-version: ${{ matrix.go }} - cache: true - - name: Test - run: go test -v ./... - - name: Build - run: make build -j8 diff --git a/.github/workflows/sync-staging-repo.yml b/.github/workflows/sync-staging-repo.yml deleted file mode 100644 index 928212dc9..000000000 --- a/.github/workflows/sync-staging-repo.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Sync Staging Files to Other Repos -on: - push: - branches: [ main ] - paths: - - staging/** - - .github/workflows/sync-staging-repo.yml - - .github/sync-staging-repo.yml - workflow_dispatch: - -jobs: - sync: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Run GitHub File Sync - uses: BetaHuhn/repo-file-sync-action@v1.16.5 - with: - GH_PAT: ${{ secrets.GH_PAT }} - CONFIG_PATH: .github/sync-staging-repo.yml - ORIGINAL_MESSAGE: true - SKIP_PR: true - COMMIT_EACH_FILE: false diff --git a/.github/workflows/update-download-script.yml b/.github/workflows/update-download-script.yml deleted file mode 100644 index 7e57f3e13..000000000 --- a/.github/workflows/update-download-script.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: update download.sh - -on: - push: - branches: [ main ] - paths: - - hack/install.sh - workflow_dispatch: - -jobs: - update-download-script: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v1 - with: - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws-region: ap-southeast-1 - - name: Update download.sh - run: aws s3 cp hack/install/download.sh s3://download.devstream.io/download.sh --acl public-read diff --git a/.github/workflows/update-fig-spec.yml b/.github/workflows/update-fig-spec.yml deleted file mode 100644 index aaf0a2533..000000000 --- a/.github/workflows/update-fig-spec.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: Update Fig Spec -on: - push: - tags: - - "v*" - -env: - go-version: 1.18.x -jobs: - update-fig-spec: - name: Update Fig Spec - runs-on: [self-hosted, linux, X64] - steps: - - uses: actions/checkout@v3 - - name: Install Go ${{ env.go-version }} - uses: actions/setup-go@v3 - with: - go-version: ${{ env.go-version }} - cache: true - - name: Generate the spec - run: | - make build-core - ./dtm completion fig > dtm.ts - - name: Create Autocomplete PR - uses: withfig/push-to-fig-autocomplete-action@v1 - with: - token: ${{ secrets.FIG_TOKEN }} - autocomplete-spec-name: dtm - spec-path: dtm.ts - integration: cobra diff --git a/.gitignore b/.gitignore index c773edc61..a8841ccd4 100644 --- a/.gitignore +++ b/.gitignore @@ -7,14 +7,8 @@ *.so *.dylib -# build +# Build dtm -dtm-darwin* -dtm-linux* -smoke -build/output -build/working_dir -docs/build # Test binary, build with `go test -c` *.test @@ -25,35 +19,6 @@ docs/build # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 .glide/ -# YAML for testings -config*.yaml* -variables*.yaml -variables*.yaml.bak -tools*.yaml -tools*.yaml.bak -e2e-test-local.yaml - # IDE .idea/ .vscode/ - -# .devstream -.devstream/ -*.state -# do not ignore this file -!/test/state/test_drifted.state - -# e2e -testbin/ - -# plugins -.github-* - -# terraform -.terraform - -# test -cov - -# md -README_when_create_plugin.md diff --git a/.lycheeignore b/.lycheeignore deleted file mode 100644 index e62ffe5bd..000000000 --- a/.lycheeignore +++ /dev/null @@ -1,26 +0,0 @@ -https://github.com/.*/pull/[0-9]+.* -https://github.com/.*/issues/[0-9]+.* -https://fonts.gstatic.com/ -.*\$\{.*\}.* -.*ssh.* -.*foo.* -.*bar.* -.*xxx.* -.*changeme.* -.*example.* -.*test.* -.*YOUR_.* -https://id.atlassian.net -https://JIRA_ID.atlassian.net -https://jira_id.atlassian.net -https://vault-0.vault-internal:8201/ -https://kubernetes.default.svc -https://github.com/IronCore864/dtm-test-go.git -https://www.crunchbase.com/organization/merico -https://jp-gouin.github.io/helm-openldap/ -https://pkg.go.dev/github.com/golang-standards/project-layout -https://gitlab.com/-/profile/personal_access_tokens\?name=DevStream\+Access\+token&scopes=api -http://core.harbor.domain/ -https://core.harbor.domain/ -http://jenkins.jenkins:8080/ -http[s]?://((2((5[0-5])|([0-4]\d)))|([0-1]?\d{1,2}))(\.((2((5[0-5])|([0-4]\d)))|([0-1]?\d{1,2}))){3}.* diff --git a/.yamllint.yml b/.yamllint.yml deleted file mode 100644 index e4e3d07c0..000000000 --- a/.yamllint.yml +++ /dev/null @@ -1,35 +0,0 @@ -extends: default -rules: - brackets: disable # do not check brackets - comments: - require-starting-space: true - min-spaces-from-content: 1 # at leaset 1 space between comment and content - document-start: disable # whether the document must start with '---' is optional - indentation: - spaces: 2 - # block sequences should not be indented - # e.g.: - # OK: - # key: - # - value1 - # - value2 - # NOT OK: - # key: - # - value1 - # - value2 - indent-sequences: false - line-length: disable - new-line-at-end-of-file: enable # must have a new line at the end of file - trailing-spaces: disable # do not check trailing spaces - truthy: disable # do not check truthy -ignore: | - *.tpl.yaml - *.tpl.yml - *tmpl.yaml - *tmpl.yml - *template.yml - *template.yaml - **/.github/** - **/githubactions/** - **/workflows/** - **/staging/** diff --git a/CODEOWNERS b/CODEOWNERS deleted file mode 100644 index 8ba17fe3c..000000000 --- a/CODEOWNERS +++ /dev/null @@ -1,13 +0,0 @@ -* @devstream-io/reviewer - -CODEOWNERS @devstream-io/pmc - -/.github/ @daniel-hutao -Makefile @daniel-hutao -/hack/ @daniel-hutao - -*.tf @IronCore864 -/docs/ @IronCore864 -CODE_OF_CONDUCT.md @IronCore864 -CONTRIBUTING @IronCore864 -README.md @IronCore864 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index cad10e823..1ee37e1f5 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -16,7 +16,7 @@ Private harassment is also unacceptable. No matter who you are, if you feel you Any spamming, trolling, flaming, baiting, or other attention-stealing behavior is not welcome. -[Email The Moderation Team](mailto:tiexin.guo@merico.dev) +[Email The Moderation Team](mailto:tao.hu@merico.dev) ## Moderation diff --git a/MAINTAINERS.md b/MAINTAINERS.md index 52234e968..5e266b046 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -4,14 +4,13 @@ Please keep the list sorted alphabetically by GitHub handle. | Github ID | Name | Email | |---------------------------------------------------|------------------------|------------------------------------| -| [@IronCore864](https://github.com/IronCore864/) | Tiexin Guo | tiexin.guo@merico.dev | | [@daniel-hutao](https://github.com/daniel-hutao/) | Daniel Hu | tao.hu@merico.dev | ---- ### Previous Project Maintainers: -| Github ID | Name | -|----------------------------------------------|------------------------| -| [@lfbdev](https://github.com/lfbdev) | Fangbao Li | - +| Github ID | Name | +|---------------------------------------------------|------------------------| +| [@IronCore864](https://github.com/IronCore864/) | Tiexin Guo | +| [@lfbdev](https://github.com/lfbdev) | Fangbao Li | +| [@aFlyBird0](https://github.com/aFlyBird0) | Hepeng Li | diff --git a/Makefile b/Makefile index 6fe8e05d9..3f5bb43a6 100644 --- a/Makefile +++ b/Makefile @@ -1,17 +1,9 @@ +DTM_ROOT=github.com/devstream-io/devstream SELF_DIR=$(dir $(lastword $(MAKEFILE_LIST))) GOOS=$(shell go env GOOS) GOPATH=$(shell go env GOPATH) GOARCH=$(shell go env GOARCH) -GO_PLUGIN_BUILD=go build -buildmode=plugin -trimpath -gcflags="all=-N -l" -PLUGINS=$(notdir $(wildcard $(ROOT_DIR)/cmd/plugin/*)) -PLUGIN_SUFFIX=${GOOS}-${GOARCH}_${VERSION} -SHELL := /bin/bash -DTM_ROOT=github.com/devstream-io/devstream -GO_LDFLAGS += -X '$(DTM_ROOT)/internal/pkg/version.Version=$(VERSION)' \ - -X '$(DTM_ROOT)/cmd/devstream/list.PluginsName=$(PLUGINS)' -FIND := find . -path './cmd/**/*.go' -o -path './test/**/*.go' -o -path './pkg/**/*.go' -o -path './internal/**/*.go' -GITHOOK := $(shell cp -f hack/githooks/* .git/hooks/) # COLORS RED = $(shell printf "\33[31m") @@ -20,71 +12,16 @@ WHITE = $(shell printf "\33[37m") YELLOW = $(shell printf "\33[33m") RESET = $(shell printf "\33[0m") - -ifeq ($(GOOS),linux) - MD5SUM=md5sum -else - MD5SUM=md5 -q -endif - ifeq ($(origin ROOT_DIR),undefined) ROOT_DIR := $(abspath $(shell cd $(SELF_DIR) && pwd -P)) endif -ifeq ($(origin VERSION), undefined) -# the VERSION is a number, like 0.6.0 -# it doesn't contain the prefix v, not v0.6.0, but 0.6.0 -VERSION := $(shell git describe --tags --always --match='v*' | cut -c 2-) -endif - -ifeq ($(origin PLUGINS_DIR),undefined) -PLUGINS_DIR := ${HOME}/.devstream/plugins -$(shell mkdir -p $(PLUGINS_DIR)) -endif - -.PHONY: all -all: build - -.PHONY: help -help: ## Display this help. - @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9.%-]+:.*?##/ { printf " \033[36m%-18s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) - -.PHONY: clean -clean: ## Remove dtm and plugins. It's best to run a "clean" before "build". - -rm -rf $(PLUGINS_DIR) - -rm -f dtm* - -.PHONY: build-core -build-core: generate fmt lint vet mod-tidy ## Build dtm core only, without plugins, locally. - go build -trimpath -gcflags="all=-N -l" -ldflags "$(GO_LDFLAGS)" -o dtm-${GOOS}-${GOARCH} ./cmd/devstream/ - @-rm -f dtm - @cp dtm-${GOOS}-${GOARCH} dtm - @echo "${GREEN}✔'dtm' has been generated in the current directory($(PWD))!${RESET}" - -.PHONY: build-plugin.% -build-plugin.%: generate fmt lint vet mod-tidy ## Build one dtm plugin, like "make build-plugin.argocd". - $(eval plugin_name := $(strip $*)) - @[ -d $(ROOT_DIR)/cmd/plugin/$(plugin_name) ] || { echo -e "\n${RED}✘ Plugin '$(plugin_name)' not found!${RESET} The valid plugin name is as follows (Eg. You can use ${YELLOW}make build-plugin.argocdapp${RESET} to build argocdapp plugin): \n\n$(shell ls ./cmd/plugin/)\n"; exit 1; } - @echo "$(YELLOW)Building plugin '$(plugin_name)'$(RESET)" - ${GO_PLUGIN_BUILD} -o ${PLUGINS_DIR}/${plugin_name}-${PLUGIN_SUFFIX}.so ${ROOT_DIR}/cmd/plugin/${plugin_name} - @$(MAKE) md5-plugin.$(plugin_name) - -.PHONY: build-plugins -build-plugins: generate fmt vet mod-tidy $(addprefix build-plugin.,$(PLUGINS)) ## Build dtm plugins only. Use multi-threaded like "make build-plugins -j8" to speed up. +FIND := find . -path './cmd/*.go' -o -path './internal/pkg/*.go' .PHONY: build -build: build-core build-plugins ## Build everything. Use multi-threaded like "make build -j8" to speed up. - -.PHONY: md5 -md5: md5-plugins ## Create md5 sums for all plugins. - -.PHONY: md5-plugins -md5-plugins: $(addprefix md5-plugin.,$(PLUGINS)) - -.PHONY: md5-plugin.% -md5-plugin.%: - $(eval plugin_name := $(strip $*)) - ${MD5SUM} $(PLUGINS_DIR)/${plugin_name}-${PLUGIN_SUFFIX}.so > $(PLUGINS_DIR)/${plugin_name}-${PLUGIN_SUFFIX}.md5 +build: fmt vet mod-tidy ## Build dtm core only, without plugins, locally. + go build -trimpath -o dtm . + @echo "${GREEN}✔'dtm' has been generated in the current directory($(PWD))!${RESET}" .PHONY: fmt fmt: verify.goimports ## Run 'go fmt' & goimports against code. @@ -93,14 +30,10 @@ fmt: verify.goimports ## Run 'go fmt' & goimports against code. @$(FIND) -type f | xargs ${GOPATH}/bin/goimports -w -local $(DTM_ROOT) @go mod edit -fmt -.PHONY: generate -generate: ## Run "go generate ./...". - go generate ./... - -.PHONY: lint -lint: verify.golangcilint ## Run 'golangci-lint' against code. - @echo "$(YELLOW)Run golangci to lint source codes$(RESET)" - @${GOPATH}/bin/golangci-lint -c $(ROOT_DIR)/.golangci.yml run $(ROOT_DIR)/... +#.PHONY: lint +#lint: verify.golangcilint ## Run 'golangci-lint' against code. +# @echo "$(YELLOW)Run golangci to lint source codes$(RESET)" +# @${GOPATH}/bin/golangci-lint -c $(ROOT_DIR)/.golangci.yml run $(ROOT_DIR)/... .PHONY: vet vet: ## Run "go vet ./...". @@ -110,20 +43,6 @@ vet: ## Run "go vet ./...". mod-tidy: ## Run "go mod tidy". go mod tidy -.PHONY: e2e -e2e: build ## Run e2e tests. - ./dtm apply -f config.yaml - ./dtm verify -f config.yaml - ./dtm delete -f config.yaml - -.PHONY: e2e-up -e2e-up: ## Start kind cluster for e2e tests. - sh hack/e2e/e2e-up.sh - -.PHONY: e2e-down -e2e-down: ## Stop kind cluster for e2e tests. - sh hack/e2e/e2e-down.sh - .PHONY: verify.% verify.%: @if ! command -v $* >/dev/null 2>&1; then $(MAKE) install.$*; fi diff --git a/OWNERS b/OWNERS deleted file mode 100644 index da7c69ebe..000000000 --- a/OWNERS +++ /dev/null @@ -1,17 +0,0 @@ -approvers: -- IronCore864 -- daniel-hutao -- imxw -- aFlyBird0 -- hxcGit -- steinliber -- algobot76 - -reviewers: -- IronCore864 -- daniel-hutao -- imxw -- aFlyBird0 -- hxcGit -- steinliber -- algobot76 diff --git a/README.md b/README.md index 6ca98be72..a301dd815 100644 --- a/README.md +++ b/README.md @@ -1,144 +1,3 @@ -
-
- - - # DevStream -[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat&logo=github&color=2370ff&labelColor=454545)](https://makeapullrequest.com) -![Test](https://github.com/devstream-io/devstream/actions/workflows/main.yml/badge.svg) -[![Go Report Card](https://goreportcard.com/badge/github.com/devstream-io/devstream)](https://goreportcard.com/report/github.com/devstream-io/devstream) -[![Downloads](https://img.shields.io/github/downloads/devstream-io/devstream/total.svg)](https://github.com/devstream-io/devstream/releases) -[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/6202/badge)](https://bestpractices.coreinfrastructure.org/projects/6202) -[![Slack](https://img.shields.io/badge/slack-join_chat-success.svg?logo=slack)](https://cloud-native.slack.com/archives/C03LA2B8K0A) - -| English | [中文](README_zh.md) | -| --- | --- | - -
- -## DevStream, What Is It Anyway? - -TL;DR: DevStream (CLI tool named `dtm`) is an open-source DevOps toolchain manager. - -[v0.6.0 Demo](https://www.youtube.com/watch?v=q7TK3vFr1kg) - -Imagine you are starting a new project or ramping up a new team. Before writing the first line of code, you have to figure out the tools to run an effective SDLC process and from development to deployment. - -Typically, you'd need the following pieces in place to work effectively: - -- Project management software or issue tracking tools (JIRA, etc.) -- Source code management (GitHub, Bitbucket, etc.) -- Continuous integration tools (Jenkins, CircleCI, Travis CI, etc.) -- Continuous delivery/deployment tools (Flux CD/Flux2, Argo CD, etc.) -- A single source of truth for secrets and credentials (secrets manager, e.g., Vault by HashiCorp) -- Some tools for centralized logging and monitoring (for example, ELK, Prometheus/Grafana); - -The list could go on for quite a bit, but you get the idea! - -There are many challenges in creating an effective and personalized workflow: - -- There are too many choices. Which is best? There is no "one-size-fits-all" answer because it totally depends on your needs and preferences. -- Integration between different pieces is challenging, creating silos and fragmentation. -- The software world evolves fast. What's best today might not make sense tomorrow. If you want to switch parts or tools out, it can be challenging and resource intensive to manage. - -To be fair, there are a few integrated products out there that may contain everything you might need, but they might not suit your specific requirements perfectly. So, the chances are, you will still want to go out and do your research, find the best pieces, and integrate them yourself. That being said, to choose, launch, connect, and manage all these pieces take a lot of time and energy. - -You might be seeing where we are going with this... - -We wanted to make it easy to set up these personalized and flexible toolchains, so we built DevStream, an open-source DevOps toolchain manager. - -Think of the Linux kernel V.S. different distributions. Different distros offer different packages so that you can always choose the best for your need. - -Or, think of `yum`, `apt`, or `apk`. You can easily set it up with your favorite packages for any new environment using these package managers. - -DevStream aims to be the package manager for DevOps tools. - -To be more ambitious, DevStream wants to be the Linux kernel, around which different distros can be created with various components so that you can always have the best components for each part of your SDLC workflow. - -## Why `dtm`? - -Q: The CLI tool is named `dtm`, while the tool itself is called DevStream. What the heck?! Where is the consistency? - -A: Inspired by [`git`](https://github.com/git/git#readme), the name is (depending on your mood): - -- a symmetric, scientific acronym of **d**evs**t**rea**m**. -- "devops toolchain manager": you're in a good mood, and it actually works for you. -- "dead to me": when it breaks. - -## Why Use DevStream? - -No more manual curl/wget download, apt install, helm install; no more local experiments and playing around just to get a piece of tool installed correctly. - -Define your desired DevOps tools in a single human-readable YAML config file, and at the press of a button (one single command), you will have your whole DevOps toolchain and SDLC workflow set up. Five Minutes. One Command. - -Want to install another different tool for a try? No problem. - -Want to remove or reinstall a specific piece in the workflow? DevStream has got your back! - -## Quick Start - -If you want to get a quick start, follow our [quick start](https://docs.devstream.io/en/latest/quickstart/) doc now. - -## Best Practices Toolchain Integration - -DevStream supports the management of many tools. You can flexibly combine some tools to meet the DevOps toolchain your need. - -And yes, if you ask me if any recommended practices that can be used out of the box, - -I am happy to tell you that we have, and we are constantly adding more possible combinations, - -so you are more than welcome to tell us what combinations you expect. - -- [GitOps Toolchain](https://docs.devstream.io/en/latest/best-practices/gitops/) -- [GitLab, Jenkins and Harbor On Premise Toolchain (Chinese only for now)](https://docs.devstream.io/en/latest/best-practices/gitlab-jenkins-harbor-java-springboot.zh/) - -## Supported DevOps Tools - -DevStream already supports many tools and it's still growing. For a complete list of supported tools, check out our [list of plugins](https://docs.devstream.io/en/latest/plugins/plugins-list/) document. - -Alternatively, run `dtm list plugins` and it will show you all the available plugins. - -## Dev Info - -### Pre-requisites - -- Git -- Go (1.18+) - -### Development Guide - -- [Development Environment Setup](https://docs.devstream.io/en/latest/development/dev/dev-env-setup) -- [Code linter](https://docs.devstream.io/en/latest/development/dev/lint) -- [Build the source code](https://docs.devstream.io/en/latest/development/dev/build) -- [Test the source code: unit test, e2e test](https://docs.devstream.io/en/latest/development/dev/test) -- [Create a plugin](https://docs.devstream.io/en/latest/development/dev/creating-a-plugin) - -## Contribute - -First of all, thanks for wanting to contribute to DevStream! For more details on how to contribute, contributor growth program, style guide and more, please check out our [CONTRIBUTING](./CONTRIBUTING.md) document. - -## Community - -We will regularly organize `DevStream Community Meeting`, please visit the [wiki](https://github.com/devstream-io/devstream/wiki) page for details. - -Please join our Slack channel. Here's how: - -1. [Invite yourself to CNCF's Slack if you haven't done so](https://slack.cncf.io). - - Input your email address, and click "get my invite." - - Open your inbox, find the invitation email, and click "join now." - - You can join by Email or with your Google account; follow the instructions. -2. Join DevStream channel, there are two ways to do so: - - Use [this link](https://cloud-native.slack.com/messages/devstream) to join the channel. - - In your Slack app, on the left side navigation bar, move your mouse to the "Channels" section, and there should emerge a "plus" sign on the right. Click, and select "browse channels." Input "devstream", and join. -3. For Mandarin-speaking users and contributors, you are also encouraged to join the [devstream-mandarin](https://cloud-native.slack.com/messages/devstream-mandarin) channel, where all discussions will be in Mandarin. - -For WeChat users, you can also join our WeChat group: - -![](docs/images/wechat-group-qr-code.png) - -## Code of Conduct - -[DevStream code of conduct](./CODE_OF_CONDUCT.md) - -As of Jun 2022, we joined CNCF sandbox. We also need to follow the [CNCF Community Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md). +to be updated diff --git a/README_zh.md b/README_zh.md deleted file mode 100644 index 9e112c151..000000000 --- a/README_zh.md +++ /dev/null @@ -1,147 +0,0 @@ -
-
- - - -# DevStream - -[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat&logo=github&color=2370ff&labelColor=454545)](https://makeapullrequest.com) -![Test](https://github.com/devstream-io/devstream/actions/workflows/main.yml/badge.svg) -[![Go Report Card](https://goreportcard.com/badge/github.com/devstream-io/devstream)](https://goreportcard.com/report/github.com/devstream-io/devstream) -[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/6202/badge)](https://bestpractices.coreinfrastructure.org/projects/6202) -[![Downloads](https://img.shields.io/github/downloads/devstream-io/devstream/total.svg)](https://github.com/devstream-io/devstream/releases) -[![Slack](https://img.shields.io/badge/slack-join_chat-success.svg?logo=slack)](https://cloud-native.slack.com/archives/C03LA2B8K0A) - -| [English](README.md) | 中文 | -| --- | --- | - -
- -## DevStream 是什么? -TL;DR: DevStream(CLI工具名为`dtm`)是一个开源的DevOps工具链管理器。 - -[v0.6.0 Demo](https://www.bilibili.com/video/BV1W3411P7oW/) - -想象你正在开始一个新的项目或组建一个新的团队。在写第一行代码之前,你需要一个能够高效运转SDLC(软件开发生命周期)和承载开发至部署全过程的工具。 - -通常情况下,你需要以下几个部分来高效地工作。 - -- 项目管理软件或 `issue` 追溯工具(JIRA等) -- 源代码管理(GitHub、Bitbucket等) -- 持续集成(Jenkins、CircleCI、Travis CI等) -- 持续交付/部署(Flux CD/Flux2、Argo CD等) -- 密钥和证书的单一事实来源(A single source of truth)(密钥管理器,如HashiCorp的Vault) -- 集成化的日志和监控工具(例如,ELK、Prometheus/Grafana) -- ...... - -具体内容远远不止这些,不过你应该已经明白意思了。 - -在创建一个高效、定制化的工作流上,当前有许多挑战。 - -- 我们有很多选择。哪个是最好的?没有"放之四海而皆准"的答案,因为这完全取决于你的需求和喜好。 -- 不同部分之间的整合是非常具有挑战性的,否则将导致项目孤岛化、碎片化。 -- 软件领域演进很快。今天最好的东西可能明天就毫无意义。如果你想换掉一些组件或工具,管理起来会很困难,也很耗费资源。 - -说实话,有一些产品可能包含你需要的一切,但它们可能并不完全适合你的具体要求。因此,你仍然需要自己去搜寻,找到最好的组件,并自己将它们整合起来。也就是说,选择、启动、连接和管理所有这些组件需要大量的时间和精力。 - -你可能已经看到了我们想要做的事情...... - -我们想简化整合组件的过程,所以我们建立了DevStream,一个开源的DevOps工具链管理器。 - -想一想Linux内核与不同发行版的关系。不同的发行版提供不同的软件包,这样你就可以随时选择你最需要的。 - -或者,想想`yum`、`apt`或`apk`。你可以使用这些包管理器为任何新环境轻松设置你最喜欢的软件包。 - -**DevStream的目标是成为DevOps工具的软件包管理器。** - -**更具野心的是,DevStream想成为Linux内核,你可以用各种组件创建不同的发行版,为SDLC工作流的每个部分选择最适合的组件。** - -## 为什么是 `dtm` ? -Q:CLI被命名为 `dtm`,而工具本身被称为 `DevStream`。这是怎么回事!?一致性在哪里? - -A:受 [`git`](https://github.com/git/git#readme) 的启发,这个名字可以是(取决于你的心情): - -- "**d**evs**t**rea**m**": 一个对称缩写。 -- "**D**evops **T**oolchain **M**anager":当它对你有用的时候。 -- "**d**ead **t**o **m**e":当它崩溃的时候。 - -## 为什么使用DevStream? - -不再需要手动的 `curl/wget` 下载、`apt` 安装、`helm` 安装;不再需要预先的本地试验以保证组件能正确安装。 - -在一个人类可读的 `YAML` 配置文件中定义你所需要的DevOps工具,只需按一个按钮(或一个命令),你就能建立起整个DevOps工具链和SDLC工作流。 - -五分钟,一个命令。 - -想安装另一个不同的工具来试一试?没问题。 - -想删除或重新安装工作流中的某个特定部分?DevStream已经帮你解决了! - -## 快速入门 - -现在就跟随我们的[快速入门](https://docs.devstream.io/en/latest/quickstart.zh/)文档开始使用 DevStream - -## 最佳实践 - -DevStream支持许多工具的管理。你可以灵活地结合一些工具来满足你所需要的DevOps工具链。 - -是的,如果你问我是否有可以开箱即用的推荐实践。 - -我很高兴地告诉你,我们有,而且我们正在不断增加更多可能的组合。 - -我们非常欢迎你告诉我们你期望的组合。 - -- [GitOps工具链](https://docs.devstream.io/en/latest/best-practices/gitops.zh/) -- [用 DevStream 搭建 GitLab + Jenkins + Harbor 工具链,管理 Java Spring Boot 项目开发生命周期全流程](https://docs.devstream.io/en/latest/best-practices/gitlab-jenkins-harbor-java-springboot.zh/) - -## 支持的DevOps工具 - -DevStream已经支持许多工具,而且还在不断增加。关于支持的工具的完整列表,请查看我们的 [插件列表](https://docs.devstream.io/en/latest/plugins/plugins-list) 文档。 - -或者,运行 `dtm list plugins`,它将显示所有可用的插件。 - -## 开发指南 - -### 前提条件 - -- Git -- Go (1.18版本以上) - -### 开发指南 - -- [开发环境搭建](https://docs.devstream.io/en/latest/development/dev/dev-env-setup.zh) -- [代码格式检查](https://docs.devstream.io/en/latest/development/dev/lint.zh) -- [源码构建](https://docs.devstream.io/en/latest/development/dev/build.zh) -- [代码测试:单元测试(unit test)、端到端测试(e2e test)](https://docs.devstream.io/en/latest/development/dev/test.zh) -- [开发新插件](https://docs.devstream.io/en/latest/development/dev/creating-a-plugin.zh) - -## 贡献 - -首先,感谢你愿意为DevStream做贡献! - -关于如何贡献、贡献者成长计划、风格指南等更多细节,请查看我们的 [CONTRIBUTING](CONTRIBUTING.md) 文档。 - -## 社区 - -我们将定期组织 "DevStream Community Meeting",请访问 [WIKI](https://github.com/devstream-io/devstream/wiki) 页面了解详情。 - -另外,请加入我们的Slack channel,请参见如下步骤: - -1. [给自己发送进入CNCF的Slack的邀请](https://slack.cncf.io)。 - - 输入邮箱地址,点击"get my invite"。 - - 打开收件箱,找到邀请邮件,点击邮件中的"join now"。 - - 你可以用邮箱地址或者Google账号等方式加入,跟随屏幕提示即可。 -2. 对于会说英文的用户:加入DevStream的英语频道。以下两种方式均可: - - [点击这里](https://cloud-native.slack.com/messages/devstream)。 - - 在Slack app里,在左侧的导航栏,找到“频道”区域,将鼠标移动到上面,这时“频道”的右侧会出现一个加号。点击加号,然后选择“浏览频道”。输入"devstream",找到频道然后加入。 -3. 对于只说中文的用户,可以加入[devstream-mandarin](https://cloud-native.slack.com/messages/devstream-mandarin)频道,这里的讨论都是用中文进行的。 - -对于使用微信的用户,请扫描二维码进群: - -![](docs/images/wechat-group-qr-code.png) - -## 行为守则 - -[DevStream行为守则](/CODE_OF_CONDUCT.md) - -DevStream于2022年6月加入CNCF沙盒。我们也需要遵循[云原生计算基金会(CNCF)社区行为准则](https://github.com/cncf/foundation/blob/main/code-of-conduct-languages/zh.md). diff --git a/ROADMAP.md b/ROADMAP.md index eb9f5d745..258cd5725 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,109 +1 @@ -# Roadmap - -## 1 New Tools to Support (Plugins) - -- artifactory: https://github.com/devstream-io/devstream/issues/607 -- Jenkins pipeline plugin: https://github.com/devstream-io/devstream/issues/582 -- Cloudflare IP List Monitor: https://github.com/devstream-io/devstream/issues/560 -- Use Validator for Config Validation: https://github.com/devstream-io/devstream/issues/558 -- GitLab CE: https://github.com/devstream-io/devstream/issues/509 -- zendao: https://github.com/devstream-io/devstream/issues/508 -- Tekton -- Jira-GitHub, Jira-GitLab integration -- Jenkins-GitHub, Jenkins-GitLab integration -- ArgoCD-GitHub SSO integration -- Repository bootstrapping for Python/Nodejs for GitHub, Golang/Python/Nodejs for GitLab -- GitLab CI workflows for Python/Nodejs -- FluxCD plugin -- Trello-GitLab Integration - -## 2 Core Features - -General: - -- single config profile support: https://github.com/devstream-io/devstream/issues/596 -- make sure people who don't have optimum internet connections (e.g., users behind firewall or proxy) can still use DevStream smoothly. - -### `dtm show config` - -This is already supported, but we will improve the features of it, for example: - -- show the default configs of multiple plugins that are used together -- interactive: user select plugin then show the default config - -## 3 Quality of Life Improvements for Developers - -### Automated End-to-End Testing in a Staging Environment - -- AWS EC2 (linux-amd64) creation with Terraform/Ansible -- Push notification to Slack/Lark when the testing environment is occupied or released - -### Misc - -- Integrate the golangci-lint command in the makefile https://github.com/devstream-io/devstream/issues/632 -- Push notification when CI failed: https://github.com/devstream-io/devstream/issues/636 -- Shorter CI time: for example, adding packages into a base image -- More end-to-end tests coverage, to test more typical usecases and plugins -- Push notification to core committers when there is a new PR ready for review - -## 4 Already Done - -Core: -- local state support: https://github.com/devstream-io/devstream/issues/16 -- pluginmanager module: https://github.com/devstream-io/devstream/issues/17 -- statemanager supports concurrent map: https://github.com/devstream-io/devstream/issues/71 -- pluginmanager download status bar: https://github.com/devstream-io/devstream/issues/88, https://github.com/devstream-io/devstream/issues/98 -- multi-plugin instance support: https://github.com/devstream-io/devstream/issues/136 -- force delete feature: https://github.com/devstream-io/devstream/issues/177, https://github.com/devstream-io/devstream/issues/278 -- `verify` command: https://github.com/devstream-io/devstream/issues/252, https://github.com/devstream-io/devstream/issues/253 -- output support: https://github.com/devstream-io/devstream/issues/324 -- Homebrew support: https://github.com/devstream-io/devstream/issues/351, https://github.com/devstream-io/devstream/issues/372 -- remote state: https://github.com/devstream-io/devstream/issues/378, https://github.com/devstream-io/devstream/issues/485 -- autocomplete: https://github.com/devstream-io/devstream/issues/380 -- generate default config: https://github.com/devstream-io/devstream/issues/383 -- list all plugins: https://github.com/devstream-io/devstream/issues/384 -- config supports global variables: https://github.com/devstream-io/devstream/issues/393 -- plugin status: https://github.com/devstream-io/devstream/issues/401 -- parallel download: https://github.com/devstream-io/devstream/issues/579 -- plugins released to AWS S3 instead of GitHub releases page - -Plugins: -- Jenkins plugin: https://github.com/devstream-io/devstream/issues/11 -- GitLab CI plugin: https://github.com/devstream-io/devstream/issues/12 -- GitHub Actions plugin for Nodejs and Python: https://github.com/devstream-io/devstream/issues/14 -- GitHub Actions plugin supports test coverage: https://github.com/devstream-io/devstream/issues/133 -- GitHub repo scaffolding for Golang plugin: https://github.com/devstream-io/devstream/issues/191, https://github.com/devstream-io/devstream/issues/520 -- Prometheus/grafana plugin: https://github.com/devstream-io/devstream/issues/231 -- Helm type plugins support values: https://github.com/devstream-io/devstream/issues/272 -- openldap plugin: https://github.com/devstream-io/devstream/issues/284 -- trello plugin: https://github.com/devstream-io/devstream/issues/307, https://github.com/devstream-io/devstream/issues/314 -- GitLab CI generic plugin: https://github.com/devstream-io/devstream/issues/377 -- Helm generic plugin: https://github.com/devstream-io/devstream/issues/424 - -Develop: -- cross-platform build: https://github.com/devstream-io/devstream/issues/21, https://github.com/devstream-io/devstream/issues/170 -- end to end test: https://github.com/devstream-io/devstream/issues/50, https://github.com/devstream-io/devstream/issues/118 -- logging level: https://github.com/devstream-io/devstream/issues/176 -- parallel build: https://github.com/devstream-io/devstream/issues/361 -- automated release: https://github.com/devstream-io/devstream/issues/364 -- command to help contributors to generate scaffolding code: https://github.com/devstream-io/devstream/issues/454, https://github.com/devstream-io/devstream/issues/443, https://github.com/devstream-io/devstream/issues/436 -- params validation improvement: https://github.com/devstream-io/devstream/issues/511 -- editor config: https://github.com/devstream-io/devstream/issues/629 - -By versions: - -v0.3.1: -- automated release: when a new tag is generated, build binaries for different platforms/OS and distribute the binaries to the plugin storage. - -v0.3.0: -- "Destroy" and "force delete": everything can be cleared up without any residue or side effects. -- "Output": all plugin's output is printed for users to review. -- Plugin dependency management: a common way to handle plugin dependencies and execution order using graph/topology sort. -- Automated e2e testing: AWS EKS cluster with Terraform. -- Trello plugin that creates boards. - -v0.4.0-v0.5.0: -- generic GitLab CI plugin -- define variables and use it in the config file. -- auto-complete support for `dtm` commands -- HashiCorp Vault +todo diff --git a/SECURITY.md b/SECURITY.md index cb7c3d3ed..ab70a6ddd 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,6 +1,6 @@ # DevStream Security Policy -Version: **v0.1 (2022-02-28)** +Version: **v0.12 (2023-04-24)** ## Overview @@ -20,7 +20,6 @@ In some cases, where a security fix needs complex re-design of a feature or is o Please report vulnerabilities by e-mail to the following address: -- tiexin.guo@merico.dev - tao.hu@merico.dev If you find a security-related bug in DevStream, we kindly ask you to disclose responsibly and give us appropriate time to react to mitigate the vulnerability. diff --git a/cmd/commit.go b/cmd/commit.go new file mode 100644 index 000000000..6bf840e6d --- /dev/null +++ b/cmd/commit.go @@ -0,0 +1,49 @@ +package cmd + +import ( + "os" + + "github.com/spf13/cobra" + + "github.com/devstream-io/devstream/internal/log" + "github.com/devstream-io/devstream/internal/pkg/commit" + "github.com/devstream-io/devstream/internal/response" +) + +// commit message got from the command line by -m flag +var message string + +// commitCmd represents the commit command +var commitCmd = &cobra.Command{ + Use: "commit", + Short: "commit is used to execute git commit operations", + Long: `commit is used to execute git commit operations + +e.g. + +1. dtm commit -m "commit message" +`, + Run: func(cmd *cobra.Command, args []string) { + if message == "" { + errStr := "message is required" + log.Error(errStr) + r := response.New(response.StatusError, response.MessageError, errStr) + r.Print(OutputFormat) + os.Exit(1) + } + err := commit.Commit(message) + if err != nil { + log.Errorf("commit error: %v", err) + r := response.New(response.StatusError, response.MessageError, err.Error()) + r.Print(OutputFormat) + } else { + r := response.New(response.StatusOK, response.MessageOK, "") + r.Print(OutputFormat) + } + }, +} + +func init() { + rootCmd.AddCommand(commitCmd) + commitCmd.Flags().StringVarP(&message, "message", "m", "", "commit message") +} diff --git a/cmd/devstream/apply.go b/cmd/devstream/apply.go deleted file mode 100644 index c1abf3694..000000000 --- a/cmd/devstream/apply.go +++ /dev/null @@ -1,39 +0,0 @@ -package main - -import ( - "os" - "strings" - - "github.com/spf13/cobra" - - "github.com/devstream-io/devstream/internal/pkg/pluginengine" - "github.com/devstream-io/devstream/pkg/util/log" -) - -var applyCMD = &cobra.Command{ - Use: "apply", - Short: "Create or update DevOps tools according to DevStream configuration file", - Long: `Create or update DevOps tools according to DevStream configuration file. -DevStream will generate and execute a new plan based on the config file and the state file by default.`, - Run: applyCMDFunc, - SuggestFor: []string{"install"}, -} - -func applyCMDFunc(cmd *cobra.Command, args []string) { - checkConfigFile() - log.Info("Apply started.") - if err := pluginengine.Apply(configFilePath, continueDirectly); err != nil { - log.Errorf("Apply failed => %s.", err) - if strings.Contains(err.Error(), "config not valid") { - log.Info("It seems your config file is not valid. Please check the official documentation https://docs.devstream.io, or use the \"dtm show config\" command to get an example.") - } - os.Exit(1) - } - log.Success("Apply finished.") -} - -func init() { - addFlagConfigFile(applyCMD) - addFlagPluginDir(applyCMD) - addFlagContinueDirectly(applyCMD) -} diff --git a/cmd/devstream/common.go b/cmd/devstream/common.go deleted file mode 100644 index c6944d415..000000000 --- a/cmd/devstream/common.go +++ /dev/null @@ -1,44 +0,0 @@ -package main - -import ( - "os" - "strings" - - "github.com/spf13/cobra" - - "github.com/devstream-io/devstream/internal/pkg/completion" - "github.com/devstream-io/devstream/pkg/util/log" -) - -var ( - configFilePath string - pluginDir string - continueDirectly bool -) - -const ( - configFlagName = "config-file" - pluginDirFlagName = "plugin-dir" - defaultPluginDir = "~/.devstream/plugins" -) - -func checkConfigFile() { - if strings.TrimSpace(configFilePath) == "" { - log.Errorf(`Config file is required. You could use "-f filename" or "-f directory" to specify it.`) - os.Exit(1) - } -} - -func addFlagConfigFile(cmd *cobra.Command) { - cmd.Flags().StringVarP(&configFilePath, configFlagName, "f", "", "config file or directory") - completion.FlagFilenameCompletion(cmd, configFlagName) -} - -func addFlagPluginDir(cmd *cobra.Command) { - cmd.Flags().StringVarP(&pluginDir, pluginDirFlagName, "d", defaultPluginDir, "plugins directory") - completion.FlagDirnameCompletion(cmd, pluginDirFlagName) -} - -func addFlagContinueDirectly(cmd *cobra.Command) { - cmd.Flags().BoolVarP(&continueDirectly, "yes", "y", false, "continue directly without confirmation") -} diff --git a/cmd/devstream/completion.go b/cmd/devstream/completion.go deleted file mode 100644 index c7ea6a49e..000000000 --- a/cmd/devstream/completion.go +++ /dev/null @@ -1,64 +0,0 @@ -package main - -import ( - "io" - "os" - "path/filepath" - - "github.com/spf13/cobra" - - cobracompletefig "github.com/withfig/autocomplete-tools/integrations/cobra" - - "github.com/devstream-io/devstream/internal/pkg/completion" -) - -func completionCMD(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "completion", - Short: "Generate the autocompletion script for dtm for the specified shell", - Long: "See each sub-command's help for details on how to use the generated script.", - DisableFlagsInUseLine: true, - Args: cobra.ExactValidArgs(1), - } - - binaryName := filepath.Base(os.Args[0]) - bash := &cobra.Command{ - Use: "bash", - Short: "generate autocompletion script for bash", - Example: completion.BashExample(binaryName), - RunE: func(cmd *cobra.Command, args []string) error { - return completion.CompletionBash(out, cmd) - }, - } - - zsh := &cobra.Command{ - Use: "zsh", - Short: "generate autocompletion script for zsh", - Example: completion.ZshExample(binaryName), - RunE: func(cmd *cobra.Command, args []string) error { - return completion.CompletionZsh(out, cmd) - }, - } - - fish := &cobra.Command{ - Use: "fish", - Short: "generate autocompletion script for fish", - Example: completion.FishExample(binaryName), - RunE: func(cmd *cobra.Command, args []string) error { - return cmd.Root().GenFishCompletion(out, true) - }, - } - - powershell := &cobra.Command{ - Use: "powershell", - Short: "generate autocompletion script for powershell", - Example: completion.PowershellExample(binaryName), - RunE: func(cmd *cobra.Command, args []string) error { - return cmd.Root().GenPowerShellCompletionWithDesc(out) - }, - } - - cmd.AddCommand(bash, zsh, fish, powershell, cobracompletefig.CreateCompletionSpecCommand(cobracompletefig.Opts{Use: "fig"})) - - return cmd -} diff --git a/cmd/devstream/create.go b/cmd/devstream/create.go deleted file mode 100644 index 18fc7b5a9..000000000 --- a/cmd/devstream/create.go +++ /dev/null @@ -1,23 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/spf13/cobra" - - "github.com/devstream-io/devstream/internal/pkg/create" -) - -var createCMD = &cobra.Command{ - Use: "create", - Short: "create", - Long: `create.`, - Run: createCMDFunc, -} - -func createCMDFunc(cmd *cobra.Command, args []string) { - err := create.Create() - if err != nil && err.Error() != "^C" { - fmt.Printf("Failed with error: %s", err) - } -} diff --git a/cmd/devstream/delete.go b/cmd/devstream/delete.go deleted file mode 100644 index b2cd65a1c..000000000 --- a/cmd/devstream/delete.go +++ /dev/null @@ -1,39 +0,0 @@ -package main - -import ( - "os" - - "github.com/spf13/cobra" - - "github.com/devstream-io/devstream/internal/pkg/pluginengine" - "github.com/devstream-io/devstream/pkg/util/log" -) - -var isForceDelete bool - -var deleteCMD = &cobra.Command{ - Use: "delete", - Short: "Delete DevOps tools according to DevStream configuration file", - Long: `Delete DevOps tools according to DevStream configuration file. -DevStream will delete everything defined in the config file, regardless of the state.`, - Run: deleteCMDFunc, -} - -func deleteCMDFunc(cmd *cobra.Command, args []string) { - checkConfigFile() - log.Info("Delete started.") - if err := pluginengine.Remove(configFilePath, continueDirectly, isForceDelete); err != nil { - log.Errorf("Delete error: %s.", err) - os.Exit(1) - } - - log.Success("Delete finished.") -} - -func init() { - addFlagConfigFile(deleteCMD) - addFlagPluginDir(deleteCMD) - addFlagContinueDirectly(deleteCMD) - - deleteCMD.Flags().BoolVarP(&isForceDelete, "force", "", false, "force delete by config") -} diff --git a/cmd/devstream/destroy.go b/cmd/devstream/destroy.go deleted file mode 100644 index f5f5afeb8..000000000 --- a/cmd/devstream/destroy.go +++ /dev/null @@ -1,37 +0,0 @@ -package main - -import ( - "os" - - "github.com/spf13/cobra" - - "github.com/devstream-io/devstream/internal/pkg/pluginengine" - "github.com/devstream-io/devstream/pkg/util/log" -) - -var isForceDestroy bool - -var destroyCMD = &cobra.Command{ - Use: "destroy", - Short: "Destroy DevOps tools deployment according to DevStream configuration file & state file", - Long: `Destroy DevOps tools deployment according to DevStream configuration file & state file.`, - Run: destroyCMDFunc, -} - -func destroyCMDFunc(cmd *cobra.Command, args []string) { - checkConfigFile() - log.Info("Destroy started.") - if err := pluginengine.Destroy(configFilePath, continueDirectly, isForceDestroy); err != nil { - log.Errorf("Destroy failed => %s.", err) - os.Exit(1) - } - log.Success("Destroy finished.") -} - -func init() { - addFlagConfigFile(destroyCMD) - addFlagPluginDir(destroyCMD) - addFlagContinueDirectly(destroyCMD) - - destroyCMD.Flags().BoolVarP(&isForceDestroy, "force", "", false, "force destroy by config") -} diff --git a/cmd/devstream/develop.go b/cmd/devstream/develop.go deleted file mode 100644 index 95aeaa5be..000000000 --- a/cmd/devstream/develop.go +++ /dev/null @@ -1,66 +0,0 @@ -package main - -import ( - "github.com/spf13/cobra" - - "github.com/devstream-io/devstream/internal/pkg/develop" - "github.com/devstream-io/devstream/pkg/util/cli" - "github.com/devstream-io/devstream/pkg/util/log" -) - -var ( - name string - all bool -) - -var validatePluginFlagNames = []string{ - "name", - "all", -} - -var developCMD = &cobra.Command{ - Use: "develop", - Short: "Develop is used for develop a new plugin", -} - -var developCreatePluginCMD = &cobra.Command{ - Use: "create-plugin", - Short: "Create a new plugin", - Long: `Create-plugin is used for creating a new plugin. -Examples: - dtm develop create-plugin --name=YOUR-PLUGIN-NAME`, - Run: developCreateCMDFunc, -} - -var developValidatePluginCMD = &cobra.Command{ - Use: "validate-plugin", - Short: "Validate a plugin", - Long: `Validate-plugin is used for validating an existing plugin or all plugins. -Examples: - dtm develop validate-plugin --name=YOUR-PLUGIN-NAME, - dtm develop validate-plugin --all`, - Run: developValidateCMDFunc, - PreRun: cli.BindPFlags(validatePluginFlagNames), -} - -func developCreateCMDFunc(cmd *cobra.Command, args []string) { - if err := develop.CreatePlugin(); err != nil { - log.Fatal(err) - } -} - -func developValidateCMDFunc(cmd *cobra.Command, args []string) { - if err := develop.ValidatePlugin(); err != nil { - log.Fatal(err) - } -} - -func init() { - developCMD.AddCommand(developCreatePluginCMD) - developCMD.AddCommand(developValidatePluginCMD) - - developCreatePluginCMD.PersistentFlags().StringVarP(&name, "name", "n", "", "specify name of the plugin to be created") - - developValidatePluginCMD.PersistentFlags().StringVarP(&name, "name", "n", "", "specify name of the plugin to be validated") - developValidatePluginCMD.PersistentFlags().BoolVarP(&all, "all", "a", false, "validate all plugins") -} diff --git a/cmd/devstream/init.go b/cmd/devstream/init.go deleted file mode 100644 index d5a466f43..000000000 --- a/cmd/devstream/init.go +++ /dev/null @@ -1,128 +0,0 @@ -package main - -import ( - "errors" - "fmt" - "runtime" - "strings" - - "github.com/spf13/cobra" - - "github.com/devstream-io/devstream/cmd/devstream/list" - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/pluginmanager" - "github.com/devstream-io/devstream/internal/pkg/version" - "github.com/devstream-io/devstream/pkg/util/log" -) - -var initCMD = &cobra.Command{ - Use: "init", - Short: "Download needed plugins according to the config file", - Long: `Download needed plugins according to the config file`, - Run: initCMDFunc, -} - -var ( - downloadOnly bool // download plugins only, from command line flags - downloadAll bool // download all plugins - pluginsToDownload []string // download specific plugins - initOS string // download plugins for specific os - initArch string // download plugins for specific arch -) - -func initCMDFunc(_ *cobra.Command, _ []string) { - if version.Dev { - log.Fatalf("Dev version plugins can't be downloaded from the remote plugin repo; please run `make build-plugin.PLUGIN_NAME` to build them locally.") - } - - var ( - tools configmanager.Tools - err error - ) - - if downloadOnly { - // download plugins from flags - tools, err = GetPluginsFromFlags() - } else { - // download plugins according to the config file - tools, err = GetPluginsFromConfig() - } - - if err != nil { - log.Fatal(err) - } - - pluginDir, err = pluginmanager.GetPluginDir() - if err != nil { - log.Fatal(err) - } - log.Debugf("Plugin directory: %s.", pluginDir) - - if err = pluginmanager.DownloadPlugins(tools, pluginDir, initOS, initArch); err != nil { - log.Fatal(err) - } - - fmt.Println() - log.Success("Initialize finished.") -} - -func GetPluginsFromConfig() (tools configmanager.Tools, err error) { - cfg, err := configmanager.NewManager(configFilePath).LoadConfig() - if err != nil { - return nil, err - } - - return cfg.Tools, nil -} - -func GetPluginsFromFlags() (tools configmanager.Tools, err error) { - // 1. get plugins from flags - var pluginsName []string - if downloadAll { - // download all plugins - pluginsName = list.PluginsNameSlice() - } else { - // download specific plugins - for _, pluginName := range pluginsToDownload { - if p := strings.ToLower(strings.TrimSpace(pluginName)); p != "" { - pluginsName = append(pluginsName, p) - } - } - // check if plugins to download are supported by dtm - for _, plugin := range pluginsName { - if _, ok := list.PluginNamesMap()[plugin]; !ok { - return nil, fmt.Errorf("plugin %s is not supported by dtm", plugin) - } - } - } - - if len(pluginsName) == 0 { - return nil, errors.New("please use --plugins to specify plugins to download or use --all to download all plugins") - } - log.Debugf("plugins to download: %v", pluginsName) - - if initOS == "" || initArch == "" { - return nil, fmt.Errorf("once you use the --all flag, you must specify the --os and --arch flags") - } - - log.Infof("Plugins to download: %v", pluginsName) - - // build the plugin list - for _, pluginName := range pluginsName { - tools = append(tools, &configmanager.Tool{Name: pluginName}) - } - - return tools, nil -} - -func init() { - addFlagConfigFile(initCMD) - addFlagPluginDir(initCMD) - - // downloading specific plugins from flags - initCMD.Flags().BoolVar(&downloadOnly, "download-only", false, "download plugins only") - initCMD.Flags().StringSliceVarP(&pluginsToDownload, "plugins", "p", []string{}, "the plugins to be downloaded") - initCMD.Flags().BoolVarP(&downloadAll, "all", "a", false, "download all plugins") - initCMD.Flags().StringVar(&initOS, "os", runtime.GOOS, "download plugins for specific os") - initCMD.Flags().StringVar(&initArch, "arch", runtime.GOARCH, "download plugins for specific arch") -} diff --git a/cmd/devstream/list.go b/cmd/devstream/list.go deleted file mode 100644 index ca0b92145..000000000 --- a/cmd/devstream/list.go +++ /dev/null @@ -1,39 +0,0 @@ -package main - -import ( - "github.com/spf13/cobra" - - "github.com/devstream-io/devstream/cmd/devstream/list" -) - -var ( - pluginFilter string -) - -var listCMD = &cobra.Command{ - Use: "list", - Short: "This command only supports listing plugins now", -} - -var listPluginsCMD = &cobra.Command{ - Use: "plugins", - Short: "List all plugins", - Long: `This command lists all of the plugins. -Examples: - dtm list plugins - dtm list plugins --filter=argo.* - dtm list plugins -r ^argo -`, - Run: listPluginsCMDFunc, -} - -func listPluginsCMDFunc(_ *cobra.Command, _ []string) { - list.List(pluginFilter) -} - -// TODO Use `--group=somegroup` to filter the specified groups on feature -func init() { - listCMD.AddCommand(listPluginsCMD) - - listPluginsCMD.PersistentFlags().StringVarP(&pluginFilter, "filter", "r", "", "filter plugin by regex") -} diff --git a/cmd/devstream/list/list.go b/cmd/devstream/list/list.go deleted file mode 100644 index c75a999c2..000000000 --- a/cmd/devstream/list/list.go +++ /dev/null @@ -1,45 +0,0 @@ -package list - -import ( - "fmt" - "regexp" - "sort" - "strings" -) - -// list is the version of DevStream. -// Assign the value when building with the -X parameter. Example: -// -X github.com/devstream-io/devstream/cmd/devstream/list.PluginsName=${PLUGINS_NAME} -// See the Makefile for more info. - -var PluginsName string - -// List all plugins name -func List(pluginFilter string) { - r, _ := regexp.Compile(pluginFilter) - for _, pluginName := range PluginsNameSlice() { - if r.Match([]byte(pluginName)) { - fmt.Println(pluginName) - } - } -} - -// PluginsNameSlice Gets plugins name in slice -func PluginsNameSlice() []string { - listPluginsName := strings.Fields(PluginsName) - sort.Strings(listPluginsName) - return listPluginsName -} - -// PluginNamesMap Gets plugins name in map -func PluginNamesMap() map[string]struct{} { - mp := make(map[string]struct{}) - - listPluginsName := strings.Fields(PluginsName) - - for _, pluginName := range listPluginsName { - mp[pluginName] = struct{}{} - } - - return mp -} diff --git a/cmd/devstream/main.go b/cmd/devstream/main.go deleted file mode 100644 index ffea2e158..000000000 --- a/cmd/devstream/main.go +++ /dev/null @@ -1,108 +0,0 @@ -package main - -import ( - "os" - - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - "github.com/spf13/viper" - - "github.com/devstream-io/devstream/pkg/util/log" -) - -var ( - isDebug bool - rootCMD = &cobra.Command{ - Use: "dtm", - Short: `DevStream is an open-source DevOps toolchain manager`, - Long: `DevStream is an open-source DevOps toolchain manager - -###### ##### -# # ###### # # # # ##### ##### ###### ## # # -# # # # # # # # # # # # ## ## -# # ##### # # ##### # # # ##### # # # ## # -# # # # # # # ##### # ###### # # -# # # # # # # # # # # # # # # -###### ###### ## ##### # # # ###### # # # # -`, - PersistentPreRun: func(cmd *cobra.Command, args []string) { - initLog() - }, - } -) - -func init() { - cobra.OnInitialize(initConfig) - rootCMD.PersistentFlags().BoolVarP(&isDebug, "debug", "", false, "debug level log") - rootCMD.AddCommand(completionCMD(os.Stdout)) - rootCMD.AddCommand(versionCMD) - rootCMD.AddCommand(initCMD) - rootCMD.AddCommand(applyCMD) - rootCMD.AddCommand(deleteCMD) - rootCMD.AddCommand(destroyCMD) - rootCMD.AddCommand(verifyCMD) - rootCMD.AddCommand(developCMD) - rootCMD.AddCommand(listCMD) - rootCMD.AddCommand(showCMD) - rootCMD.AddCommand(upgradeCMD) - - rootCMD.AddCommand(startCMD) - rootCMD.AddCommand(createCMD) -} - -func initConfig() { - viper.AutomaticEnv() - if err := viper.BindEnv("github_token"); err != nil { - log.Fatal(err) - } - if err := viper.BindEnv("kubeconfig"); err != nil { - log.Fatal(err) - } - if err := viper.BindEnv("dockerhub_username"); err != nil { - log.Fatal(err) - } - if err := viper.BindEnv("dockerhub_token"); err != nil { - log.Fatal(err) - } - if err := viper.BindEnv("trello_api_key"); err != nil { - log.Fatal(err) - } - if err := viper.BindEnv("trello_token"); err != nil { - log.Fatal(err) - } - if err := viper.BindPFlags(rootCMD.Flags()); err != nil { - log.Fatal(err) - } - if err := viper.BindPFlags(developCreatePluginCMD.Flags()); err != nil { - log.Fatal(err) - } - if err := viper.BindPFlags(developValidatePluginCMD.Flags()); err != nil { - log.Fatal(err) - } - if err := viper.BindPFlags(showConfigCMD.Flags()); err != nil { - log.Fatal(err) - } - if err := viper.BindPFlags(showStatusCMD.Flags()); err != nil { - log.Fatal(err) - } - if err := viper.BindPFlags(initCMD.Flags()); err != nil { - log.Fatal(err) - } -} - -func initLog() { - if isDebug { - logrus.SetLevel(logrus.DebugLevel) - log.Infof("Log level is: %s.", logrus.GetLevel()) - } else { - logrus.SetLevel(logrus.InfoLevel) - } -} - -func main() { - - err := rootCMD.Execute() - if err != nil { - os.Exit(1) - } -} diff --git a/cmd/devstream/show.go b/cmd/devstream/show.go deleted file mode 100644 index 0ef7e4851..000000000 --- a/cmd/devstream/show.go +++ /dev/null @@ -1,74 +0,0 @@ -package main - -import ( - "github.com/spf13/cobra" - - "github.com/devstream-io/devstream/internal/pkg/completion" - "github.com/devstream-io/devstream/internal/pkg/show/config" - "github.com/devstream-io/devstream/internal/pkg/show/status" - "github.com/devstream-io/devstream/pkg/util/log" -) - -var plugin string -var instanceID string -var statusAllFlag bool -var template string - -var showCMD = &cobra.Command{ - Use: "show", - Short: "Show is used to print plugins' configuration templates or status", -} - -var showConfigCMD = &cobra.Command{ - Use: "config", - Short: "Show configuration information", - Long: `Show config is used for showing plugins' template configuration information. -Examples: - dtm show config --plugin=A-PLUGIN-NAME, - dtm show config --template=quickstart, - dtm show config --template=gitops, - dtm show config --template=apps`, - Run: showConfigCMDFunc, -} - -var showStatusCMD = &cobra.Command{ - Use: "status", - Short: "Show status information", - Long: `Show status is used for showing plugins' status information. -Examples: - dtm show status --plugin=A-PLUGIN-NAME --id=A-PLUGIN-INSTANCE-ID - dtm show status -p=A-PLUGIN-NAME -i=INSTANCE-ID - dtm show status --all - dtm show status -a`, - Run: showStatusCMDFunc, -} - -func showConfigCMDFunc(_ *cobra.Command, _ []string) { - log.Debug("Show configuration information.") - if err := config.Show(); err != nil { - log.Fatal(err) - } -} - -func showStatusCMDFunc(_ *cobra.Command, _ []string) { - log.Debug("Show status information.") - if err := status.Show(configFilePath); err != nil { - log.Fatal(err) - } -} - -func init() { - showCMD.AddCommand(showConfigCMD) - showCMD.AddCommand(showStatusCMD) - - addFlagConfigFile(showConfigCMD) - addFlagPluginDir(showConfigCMD) - - showConfigCMD.Flags().StringVarP(&plugin, "plugin", "p", "", "specify name with the plugin") - showConfigCMD.Flags().StringVarP(&template, "template", "t", "", "print a template config, e.g. quickstart/gitops/...") - completion.FlagPluginsCompletion(showConfigCMD, "plugin") - - showStatusCMD.Flags().StringVarP(&plugin, "plugin", "p", "", "specify name with the plugin") - showStatusCMD.Flags().StringVarP(&instanceID, "id", "i", "", "specify id with the plugin instance") - showStatusCMD.Flags().BoolVarP(&statusAllFlag, "all", "a", false, "show all instances of all plugins status") -} diff --git a/cmd/devstream/start.go b/cmd/devstream/start.go deleted file mode 100644 index ab2213967..000000000 --- a/cmd/devstream/start.go +++ /dev/null @@ -1,23 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/spf13/cobra" - - "github.com/devstream-io/devstream/internal/pkg/start" -) - -var startCMD = &cobra.Command{ - Use: "start", - Short: "start", - Long: `start.`, - Run: startCMDFunc, -} - -func startCMDFunc(_ *cobra.Command, _ []string) { - err := start.Start() - if err != nil && err.Error() != "^C" { - fmt.Printf("Failed with error: %s", err) - } -} diff --git a/cmd/devstream/upgrade.go b/cmd/devstream/upgrade.go deleted file mode 100644 index 2302ffdd3..000000000 --- a/cmd/devstream/upgrade.go +++ /dev/null @@ -1,25 +0,0 @@ -package main - -import ( - "github.com/spf13/cobra" - - "github.com/devstream-io/devstream/internal/pkg/upgrade" - "github.com/devstream-io/devstream/pkg/util/log" -) - -var upgradeCMD = &cobra.Command{ - Use: "upgrade", - Short: "Upgrade dtm to the latest release version", - Long: `Upgrade dtm to the latest release version.`, - Run: upgradeCMDFunc, -} - -func upgradeCMDFunc(cmd *cobra.Command, args []string) { - if err := upgrade.Upgrade(continueDirectly); err != nil { - log.Fatal(err) - } -} - -func init() { - addFlagContinueDirectly(upgradeCMD) -} diff --git a/cmd/devstream/verify.go b/cmd/devstream/verify.go deleted file mode 100644 index e86e67756..000000000 --- a/cmd/devstream/verify.go +++ /dev/null @@ -1,29 +0,0 @@ -package main - -import ( - "github.com/spf13/cobra" - - "github.com/devstream-io/devstream/internal/pkg/pluginengine" - "github.com/devstream-io/devstream/pkg/util/log" -) - -var verifyCMD = &cobra.Command{ - Use: "verify", - Short: "Verify DevOps tools according to DevStream config file and state", - Long: `Verify DevOps tools according to DevStream config file and state.`, - Run: verifyCMDFunc, -} - -func verifyCMDFunc(cmd *cobra.Command, args []string) { - log.Info("Verify started.") - if pluginengine.Verify(configFilePath) { - log.Success("Verify succeeded.") - } else { - log.Info("Verify finished.") - } -} - -func init() { - addFlagConfigFile(verifyCMD) - addFlagPluginDir(verifyCMD) -} diff --git a/cmd/devstream/version.go b/cmd/devstream/version.go deleted file mode 100644 index ecdaf99c6..000000000 --- a/cmd/devstream/version.go +++ /dev/null @@ -1,20 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/spf13/cobra" - - "github.com/devstream-io/devstream/internal/pkg/version" -) - -var versionCMD = &cobra.Command{ - Use: "version", - Short: "Print the version number of DevStream", - Long: `All software has versions. This is DevStream's`, - Run: versionCMDFunc, -} - -func versionCMDFunc(cmd *cobra.Command, args []string) { - fmt.Println(version.Version) -} diff --git a/cmd/github.go b/cmd/github.go new file mode 100644 index 000000000..4c1c64d52 --- /dev/null +++ b/cmd/github.go @@ -0,0 +1,22 @@ +package cmd + +import ( + "github.com/spf13/cobra" + + "github.com/devstream-io/devstream/internal/pkg/github" +) + +// githubCmd represents the github command +var githubCmd = &cobra.Command{ + Use: "github", + Short: "github is used to execute github operations", + Long: `github is used to execute github operations + 参考 gh`, + Run: func(cmd *cobra.Command, args []string) { + github.Run() + }, +} + +func init() { + rootCmd.AddCommand(githubCmd) +} diff --git a/cmd/patch.go b/cmd/patch.go new file mode 100644 index 000000000..d03ebd325 --- /dev/null +++ b/cmd/patch.go @@ -0,0 +1,49 @@ +package cmd + +import ( + "os" + + "github.com/devstream-io/devstream/internal/response" + + "github.com/devstream-io/devstream/internal/log" + "github.com/devstream-io/devstream/internal/pkg/patch" + + "github.com/spf13/cobra" +) + +// patchCmd represents the patch command +var patchCmd = &cobra.Command{ + Use: "patch", + Short: "apply a diff file to an original", + Long: `patch will take a patch file containing any of the four forms of difference listing +produced by the diff program and apply those differences to an original file, +producing a patched version. If patchfile is omitted, or is a hyphen, +the patch will be read from the standard input. + +e.g. +- dtm patch file.patch +- dtm patch file.patch -ojson +`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 1 { + errMsg := "Incorrect number of arguments" + log.Error(errMsg) + r := response.New(response.StatusError, response.MessageError, errMsg) + r.Print(OutputFormat) + os.Exit(1) + } + err := patch.Patch(args[0]) + if err != nil { + log.Errorf("patch error: %v", err) + r := response.New(response.StatusError, response.MessageError, err.Error()) + r.Print(OutputFormat) + } else { + r := response.New(response.StatusOK, response.MessageOK, "") + r.Print(OutputFormat) + } + }, +} + +func init() { + rootCmd.AddCommand(patchCmd) +} diff --git a/cmd/plugin/argocdapp/main.go b/cmd/plugin/argocdapp/main.go deleted file mode 100644 index 29653e2cb..000000000 --- a/cmd/plugin/argocdapp/main.go +++ /dev/null @@ -1,41 +0,0 @@ -package main - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/argocdapp" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" -) - -// NAME is the name of this DevStream plugin. -const NAME = "argocdapp" - -// Plugin is the type used by DevStream core. It's a string. -type Plugin string - -// Create implements the create of an argocdapp. -func (p Plugin) Create(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return argocdapp.Create(options) -} - -// Update implements the update of an argocdapp. -func (p Plugin) Update(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return argocdapp.Update(options) -} - -// Read implements the read of argocdapp. -func (p Plugin) Read(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return argocdapp.Read(options) -} - -// Delete implements the delete of argocdapp. -func (p Plugin) Delete(options configmanager.RawOptions) (bool, error) { - return argocdapp.Delete(options) -} - -// DevStreamPlugin is the exported variable used by the DevStream core. -var DevStreamPlugin Plugin - -func main() { - log.Infof("%T: %s is a plugin for DevStream. Use it with DevStream.\n", DevStreamPlugin, NAME) -} diff --git a/cmd/plugin/ci-generic/main.go b/cmd/plugin/ci-generic/main.go deleted file mode 100644 index 5632f8ab2..000000000 --- a/cmd/plugin/ci-generic/main.go +++ /dev/null @@ -1,41 +0,0 @@ -package main - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/cigeneric" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" -) - -// NAME is the name of this DevStream plugin. -const NAME = "ci-generic" - -// Plugin is the type used by DevStream core. It's a string. -type Plugin string - -// Create implements the create of ci-generic. -func (p Plugin) Create(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return cigeneric.Create(options) -} - -// Update implements the update of ci-generic. -func (p Plugin) Update(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return cigeneric.Update(options) -} - -// Delete implements the delete of ci-generic. -func (p Plugin) Delete(options configmanager.RawOptions) (bool, error) { - return cigeneric.Delete(options) -} - -// Read implements the read of ci-generic. -func (p Plugin) Read(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return cigeneric.Read(options) -} - -// DevStreamPlugin is the exported variable used by the DevStream core. -var DevStreamPlugin Plugin - -func main() { - log.Infof("%T: %s is a plugin for DevStream. Use it with DevStream.\n", DevStreamPlugin, NAME) -} diff --git a/cmd/plugin/devlake-config/main.go b/cmd/plugin/devlake-config/main.go deleted file mode 100644 index 0ca594263..000000000 --- a/cmd/plugin/devlake-config/main.go +++ /dev/null @@ -1,41 +0,0 @@ -package main - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/devlakeconfig" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" -) - -// NAME is the name of this DevStream plugin. -const NAME = "devlake-config" - -// Plugin is the type used by DevStream core. It's a string. -type Plugin string - -// Create implements the create of devlake-config. -func (p Plugin) Create(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return devlakeconfig.Create(options) -} - -// Update implements the update of devlake-config. -func (p Plugin) Update(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return devlakeconfig.Update(options) -} - -// Delete implements the delete of devlake-config. -func (p Plugin) Delete(options configmanager.RawOptions) (bool, error) { - return devlakeconfig.Delete(options) -} - -// Read implements the read of devlake-config. -func (p Plugin) Read(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return devlakeconfig.Read(options) -} - -// DevStreamPlugin is the exported variable used by the DevStream core. -var DevStreamPlugin Plugin - -func main() { - log.Infof("%T: %s is a plugin for DevStream. Use it with DevStream.\n", DevStreamPlugin, NAME) -} diff --git a/cmd/plugin/github-actions/main.go b/cmd/plugin/github-actions/main.go deleted file mode 100644 index e3cfda802..000000000 --- a/cmd/plugin/github-actions/main.go +++ /dev/null @@ -1,41 +0,0 @@ -package main - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - general "github.com/devstream-io/devstream/internal/pkg/plugin/githubactions" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" -) - -// NAME is the name of this DevStream plugin. -const NAME = "github-actions" - -// Plugin is the type used by DevStream core. It's a string. -type Plugin string - -// Create implements the create of github-actions. -func (p Plugin) Create(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return general.Create(options) -} - -// Update implements the update of github-actions. -func (p Plugin) Update(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return general.Update(options) -} - -// Read implements the read of github-actions. -func (p Plugin) Read(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return general.Read(options) -} - -// Delete implements the delete of github-actions. -func (p Plugin) Delete(options configmanager.RawOptions) (bool, error) { - return general.Delete(options) -} - -// DevStreamPlugin is the exported variable used by the DevStream core. -var DevStreamPlugin Plugin - -func main() { - log.Infof("%T: %s is a plugin for DevStream. Use it with DevStream.\n", DevStreamPlugin, NAME) -} diff --git a/cmd/plugin/gitlab-ce-docker/main.go b/cmd/plugin/gitlab-ce-docker/main.go deleted file mode 100644 index db1e7318d..000000000 --- a/cmd/plugin/gitlab-ce-docker/main.go +++ /dev/null @@ -1,41 +0,0 @@ -package main - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/gitlabcedocker" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" -) - -// NAME is the name of this DevStream plugin. -const NAME = "gitlab-ce-docker" - -// Plugin is the type used by DevStream core. It's a string. -type Plugin string - -// Create implements the create of gitlab-ce-docker. -func (p Plugin) Create(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return gitlabcedocker.Create(options) -} - -// Update implements the update of gitlab-ce-docker. -func (p Plugin) Update(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return gitlabcedocker.Update(options) -} - -// Delete implements the delete of gitlab-ce-docker. -func (p Plugin) Delete(options configmanager.RawOptions) (bool, error) { - return gitlabcedocker.Delete(options) -} - -// Read implements the read of gitlab-ce-docker. -func (p Plugin) Read(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return gitlabcedocker.Read(options) -} - -// DevStreamPlugin is the exported variable used by the DevStream core. -var DevStreamPlugin Plugin - -func main() { - log.Infof("%T: %s is a plugin for DevStream. Use it with DevStream.\n", DevStreamPlugin, NAME) -} diff --git a/cmd/plugin/gitlab-ci/main.go b/cmd/plugin/gitlab-ci/main.go deleted file mode 100644 index 5b25d7806..000000000 --- a/cmd/plugin/gitlab-ci/main.go +++ /dev/null @@ -1,41 +0,0 @@ -package main - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/gitlabci" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" -) - -// NAME is the name of this DevStream plugin. -const NAME = "gitlab-ci" - -// Plugin is the type used by DevStream core. It's a string. -type Plugin string - -// Create implements the create of gitlab-ci. -func (p Plugin) Create(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return gitlabci.Create(options) -} - -// Update implements the update of gitlab-ci. -func (p Plugin) Update(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return gitlabci.Update(options) -} - -// Delete implements the delete of gitlab-ci. -func (p Plugin) Delete(options configmanager.RawOptions) (bool, error) { - return gitlabci.Delete(options) -} - -// Read implements the read of gitlab-ci. -func (p Plugin) Read(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return gitlabci.Read(options) -} - -// DevStreamPlugin is the exported variable used by the DevStream core. -var DevStreamPlugin Plugin - -func main() { - log.Infof("%T: %s is a plugin for DevStream. Use it with DevStream.\n", DevStreamPlugin, NAME) -} diff --git a/cmd/plugin/harbor-docker/main.go b/cmd/plugin/harbor-docker/main.go deleted file mode 100644 index 319e5468e..000000000 --- a/cmd/plugin/harbor-docker/main.go +++ /dev/null @@ -1,41 +0,0 @@ -package main - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/harbordocker" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" -) - -// NAME is the name of this DevStream plugin. -const NAME = "harbor-docker" - -// Plugin is the type used by DevStream core. It's a string. -type Plugin string - -// Create implements the create of harbor-docker. -func (p Plugin) Create(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return harbordocker.Create(options) -} - -// Update implements the update of harbor-docker. -func (p Plugin) Update(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return harbordocker.Update(options) -} - -// Delete implements the delete of harbor-docker. -func (p Plugin) Delete(options configmanager.RawOptions) (bool, error) { - return harbordocker.Delete(options) -} - -// Read implements the read of harbor-docker. -func (p Plugin) Read(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return harbordocker.Read(options) -} - -// DevStreamPlugin is the exported variable used by the DevStream core. -var DevStreamPlugin Plugin - -func main() { - log.Infof("%T: %s is a plugin for DevStream. Use it with DevStream.\n", DevStreamPlugin, NAME) -} diff --git a/cmd/plugin/helm-installer/main.go b/cmd/plugin/helm-installer/main.go deleted file mode 100644 index c7d41e354..000000000 --- a/cmd/plugin/helm-installer/main.go +++ /dev/null @@ -1,41 +0,0 @@ -package main - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/helminstaller" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" -) - -// NAME is the name of this DevStream plugin. -const NAME = "helm-installer" - -// Plugin is the type used by DevStream core. It's a string. -type Plugin string - -// Create implements the create of helm-installer. -func (p Plugin) Create(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return helminstaller.Create(options) -} - -// Update implements the update of helm-installer. -func (p Plugin) Update(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return helminstaller.Update(options) -} - -// Delete implements the delete of helm-installer. -func (p Plugin) Delete(options configmanager.RawOptions) (bool, error) { - return helminstaller.Delete(options) -} - -// Read implements the read of helm-installer. -func (p Plugin) Read(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return helminstaller.Read(options) -} - -// DevStreamPlugin is the exported variable used by the DevStream core. -var DevStreamPlugin Plugin - -func main() { - log.Infof("%T: %s is a plugin for DevStream. Use it with DevStream.\n", DevStreamPlugin, NAME) -} diff --git a/cmd/plugin/jenkins-pipeline/main.go b/cmd/plugin/jenkins-pipeline/main.go deleted file mode 100644 index 370fb9f09..000000000 --- a/cmd/plugin/jenkins-pipeline/main.go +++ /dev/null @@ -1,41 +0,0 @@ -package main - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/jenkinspipeline" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" -) - -// NAME is the name of this DevStream plugin. -const NAME = "jenkins-pipeline" - -// Plugin is the type used by DevStream core. It's a string. -type Plugin string - -// Create implements the create of jenkins-pipeline. -func (p Plugin) Create(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return jenkinspipeline.Create(options) -} - -// Update implements the update of jenkins-pipeline. -func (p Plugin) Update(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return jenkinspipeline.Update(options) -} - -// Delete implements the delete of jenkins-pipeline. -func (p Plugin) Delete(options configmanager.RawOptions) (bool, error) { - return jenkinspipeline.Delete(options) -} - -// Read implements the read of jenkins-pipeline. -func (p Plugin) Read(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return jenkinspipeline.Read(options) -} - -// DevStreamPlugin is the exported variable used by the DevStream core. -var DevStreamPlugin Plugin - -func main() { - log.Infof("%T: %s is a plugin for DevStream. Use it with DevStream.\n", DevStreamPlugin, NAME) -} diff --git a/cmd/plugin/jira/main.go b/cmd/plugin/jira/main.go deleted file mode 100644 index 3e4e1e5d4..000000000 --- a/cmd/plugin/jira/main.go +++ /dev/null @@ -1,41 +0,0 @@ -package main - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/jira" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" -) - -// NAME is the name of this DevStream plugin. -const NAME = "jira" - -// Plugin is the type used by DevStream core. It's a string. -type Plugin string - -// Create implements the installation of some jira-github-integ workflows. -func (p Plugin) Create(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return jira.Create(options) -} - -// Update implements the installation of some jira-github-integ workflows. -func (p Plugin) Update(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return jira.Update(options) -} - -// Read implements the healthy check of jira-github-integ workflows. -func (p Plugin) Read(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return jira.Read(options) -} - -// Delete implements the installation of some jira-github-integ workflows. -func (p Plugin) Delete(options configmanager.RawOptions) (bool, error) { - return jira.Delete(options) -} - -// DevStreamPlugin is the exported variable used by the DevStream core. -var DevStreamPlugin Plugin - -func main() { - log.Infof("%T: %s is a plugin for DevStream. Use it with DevStream.\n", DevStreamPlugin, NAME) -} diff --git a/cmd/plugin/repo-scaffolding/main.go b/cmd/plugin/repo-scaffolding/main.go deleted file mode 100644 index 865a2067b..000000000 --- a/cmd/plugin/repo-scaffolding/main.go +++ /dev/null @@ -1,41 +0,0 @@ -package main - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/reposcaffolding" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" -) - -// NAME is the name of this DevStream plugin. -const NAME = "repo-scaffolding" - -// Plugin is the type used by DevStream core. It's a string. -type Plugin string - -// Create implements the create of repo-scaffolding. -func (p Plugin) Create(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return reposcaffolding.Create(options) -} - -// Update implements the update of repo-scaffolding. -func (p Plugin) Update(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return reposcaffolding.Update(options) -} - -// Delete implements the delete of repo-scaffolding. -func (p Plugin) Delete(options configmanager.RawOptions) (bool, error) { - return reposcaffolding.Delete(options) -} - -// Read implements the read of repo-scaffolding. -func (p Plugin) Read(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return reposcaffolding.Read(options) -} - -// DevStreamPlugin is the exported variable used by the DevStream core. -var DevStreamPlugin Plugin - -func main() { - log.Infof("%T: %s is a plugin for DevStream. Use it with DevStream.\n", DevStreamPlugin, NAME) -} diff --git a/cmd/plugin/trello/main.go b/cmd/plugin/trello/main.go deleted file mode 100644 index 85b446202..000000000 --- a/cmd/plugin/trello/main.go +++ /dev/null @@ -1,41 +0,0 @@ -package main - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/trello" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" -) - -// NAME is the name of this DevStream plugin. -const NAME = "trello" - -// Plugin is the type used by DevStream core. It's a string. -type Plugin string - -// Create implements the creation of trello board. -func (p Plugin) Create(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return trello.Create(options) -} - -// Update implements the creation of trello board. -func (p Plugin) Update(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return trello.Update(options) -} - -// Read implements the healthy check of trello board. -func (p Plugin) Read(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return trello.Read(options) -} - -// Delete implements the creation of trello board. -func (p Plugin) Delete(options configmanager.RawOptions) (bool, error) { - return trello.Delete(options) -} - -// DevStreamPlugin is the exported variable used by the DevStream core. -var DevStreamPlugin Plugin - -func main() { - log.Infof("%T: %s is a plugin for DevStream. Use it with DevStream.\n", DevStreamPlugin, NAME) -} diff --git a/cmd/plugin/zentao/main.go b/cmd/plugin/zentao/main.go deleted file mode 100644 index 4ce8aa1ba..000000000 --- a/cmd/plugin/zentao/main.go +++ /dev/null @@ -1,41 +0,0 @@ -package main - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/zentao" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" -) - -// NAME is the name of this DevStream plugin. -const NAME = "zentao" - -// Plugin is the type used by DevStream core. It's a string. -type Plugin string - -// Create implements the create of zentao. -func (p Plugin) Create(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return zentao.Create(options) -} - -// Update implements the update of zentao. -func (p Plugin) Update(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return zentao.Update(options) -} - -// Delete implements the delete of zentao. -func (p Plugin) Delete(options configmanager.RawOptions) (bool, error) { - return zentao.Delete(options) -} - -// Read implements the read of zentao. -func (p Plugin) Read(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return zentao.Read(options) -} - -// DevStreamPlugin is the exported variable used by the DevStream core. -var DevStreamPlugin Plugin - -func main() { - log.Infof("%T: %s is a plugin for DevStream. Use it with DevStream.\n", DevStreamPlugin, NAME) -} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 000000000..5f01a3954 --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,90 @@ +package cmd + +import ( + "fmt" + "os" + + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/devstream-io/devstream/internal/log" + "github.com/devstream-io/devstream/internal/option" +) + +var cfgFile string + +// isDebug is a flag to enable debug level log +var isDebug bool + +// OutputFormat is the output format for the command. One of: json|yaml|raw +// Default value is "raw" +var OutputFormat string + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "dtm", + Short: "dtm is a tool to manage variaties of development platforms", + Long: `dtm is a tool to manage variaties of development platforms.`, + PersistentPreRun: func(cmd *cobra.Command, args []string) { + initLog() + }, +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + err := rootCmd.Execute() + if err != nil { + os.Exit(1) + } +} + +func init() { + cobra.OnInitialize(initConfig) + + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.devstream.yaml)") + rootCmd.PersistentFlags().StringVarP(&OutputFormat, "output", "o", "raw", "Output format. One of: json|yaml|raw") + rootCmd.PersistentFlags().BoolVarP(&isDebug, "debug", "", false, "debug level log") + rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} + +// initConfig reads in config file and ENV variables if set. +func initConfig() { + if cfgFile != "" { + // Use config file from the flag. + viper.SetConfigFile(cfgFile) + } else { + // Find home directory. + home, err := os.UserHomeDir() + cobra.CheckErr(err) + + // Search config in home directory with name ".devstream" (without extension). + viper.AddConfigPath(home) + viper.SetConfigType("yaml") + viper.SetConfigName(".devstream") + } + + viper.AutomaticEnv() // read in environment variables that match + + // If a config file is found, read it in. + if err := viper.ReadInConfig(); err == nil { + fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) + } + + if OutputFormat != "raw" { + option.Silence = true + } +} + +func initLog() { + // if OutputFormat is not "raw", set log level to PanicLevel to disable log + if OutputFormat != "raw" { + logrus.SetLevel(logrus.PanicLevel) + } else if isDebug { + logrus.SetLevel(logrus.DebugLevel) + log.Infof("Log level is: %s.", logrus.GetLevel()) + } else { + logrus.SetLevel(logrus.InfoLevel) + } +} diff --git a/cmd/scaffold.go b/cmd/scaffold.go new file mode 100644 index 000000000..462ebe2e9 --- /dev/null +++ b/cmd/scaffold.go @@ -0,0 +1,45 @@ +package cmd + +import ( + "os" + + "github.com/spf13/cobra" + + "github.com/devstream-io/devstream/internal/log" + "github.com/devstream-io/devstream/internal/pkg/scaffold" +) + +var structure string + +// scaffoldCmd represents the scaffold command +var scaffoldCmd = &cobra.Command{ + Use: "scaffold", + Short: "scaffold is used to generate folder and file structure", + Long: ` +dtm scaffold " +project/ +├── src/ +│ ├── main.go +│ └── utils/ +│ ├── file1.go +│ └── file2.go +└── README.md +" + `, + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 1 { + log.Error("Incorrect number of arguments") + os.Exit(1) + } + if err := scaffold.Scaffold(args[0]); err != nil { + log.Error(err) + os.Exit(1) + } + }, +} + +func init() { + rootCmd.AddCommand(scaffoldCmd) + + scaffoldCmd.Flags().StringVarP(&structure, "structure", "s", "", "structure specify the folder and file structure") +} diff --git a/docs/assets/versions.css b/docs/assets/versions.css deleted file mode 100644 index 0f51d07dd..000000000 --- a/docs/assets/versions.css +++ /dev/null @@ -1,180 +0,0 @@ -.md-header__title { - display: flex; -} - -.dropdown-caret { - display: inline-block !important; - position: absolute; - right: 4px; -} - -.fa .fa-caret-down { - display: none !important; -} - -.rst-other-versions { - text-align: left; -} - -.rst-other-versions > dl, .rst-other-versions dt, .rst-other-versions small { - display: none; -} - -.rst-other-versions > dl:first-child { - display: flex !important; - flex-direction: column; - line-height: 0px !important; -} - -.rst-versions.shift-up .rst-other-versions { - display: flex !important; -} - -.rst-versions .rst-other-versions { - display: none; -} - -/* Version Warning */ -div[data-md-component=announce] { - background-color: rgb(248, 243, 236); - position: sticky; - top: 0; - z-index: 2; -} -div[data-md-component=announce]>div#announce-msg{ - color:green; - font-size: .8rem; - text-align: center; - margin: 15px; -} -div[data-md-component=announce]>div#announce-msg>a{ - color: var(--md-typeset-a-color); - text-decoration: underline; -} - -/* from https://assets.readthedocs.org/static/css/badge_only.css, -most styles have to be overriden here */ -.rst-versions{ - position: relative !important; - bottom: 0; - left: 0; - width: 100px !important; - /* background: hsla(173, 100%, 24%, 1) !important; */ - background: #4051b5 !important; - font-family: inherit !important; - z-index: 0 !important; -} -.rst-versions a{ - color:#2980B9; - text-decoration:none -} -.rst-versions .rst-badge-small{ - display:none -} -.rst-versions .rst-current-version{ - padding:12px; - /* background: hsla(173, 100%, 24%, 1) !important; */ - background: #4051b5 !important; - display:block; - text-align:right; - font-size:90%; - cursor:pointer; - color: white !important; - *zoom:1 -} -.rst-versions .rst-current-version:before,.rst-versions .rst-current-version:after{ - display:table;content:"" -} -.rst-versions .rst-current-version:after{ - clear:both -} -.rst-versions .rst-current-version .fa{ - color:#fcfcfc -} -.rst-versions .rst-current-version .fa-caret-down{ - display: none; -} -.rst-versions.shift-up .rst-other-versions{ - display:block -} -.rst-versions .rst-other-versions{ - font-size:90%; - padding-left:12px; - padding-right:12px; - padding-top:0px; - padding-bottom:0px; - color:gray; - display:none -} -.rst-versions .rst-other-versions hr{ - display: none !important; - height: 0px !important; - border: 0px; - margin: 0px !important; - padding: 0px; - border-top: none !important; -} -.rst-versions .rst-other-versions dd{ - display:inline-block; - margin:0 -} -.rst-versions .rst-other-versions dd a{ - display:inline-block; - padding: 1em 0em !important; - color:#fcfcfc; - font-size: .6rem !important; - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; - width: 80px; -} -.rst-versions .rst-other-versions dd a:hover{ - font-size: .7rem !important; - font-weight: bold; -} -.rst-versions.rst-badge{ - display: block !important; - width: 100px !important; - bottom: 0px !important; - right: 0px !important; - left:auto; - border:none; - text-align: center !important; - line-height: 0; -} -.rst-versions.rst-badge .icon-book{ - display: none; -} -.rst-versions.rst-badge .fa-book{ - display: none !important; -} -.rst-versions.rst-badge.shift-up .rst-current-version{ - text-align: left !important; -} -.rst-versions.rst-badge.shift-up .rst-current-version .fa-book{ - display: none !important; -} -.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{ - display: none !important; -} -.rst-versions.rst-badge .rst-current-version{ - width: 70px !important; - height: 2.4rem !important; - line-height:2.4rem !important; - padding: 0px 5px !important; - display: inline-block !important; - font-size: .6rem !important; - overflow: hidden !important; - text-overflow: ellipsis !important; - white-space: nowrap !important; - text-align: left !important; -} -@media screen and (max-width: 768px){ - .rst-versions{ - width:85%; - display:none - } - .rst-versions.shift{ - display:block - } -} diff --git a/docs/assets/versions.js b/docs/assets/versions.js deleted file mode 100644 index 60777dc6f..000000000 --- a/docs/assets/versions.js +++ /dev/null @@ -1,59 +0,0 @@ -setTimeout(function () { - const callbackName = 'callback_' + new Date().getTime(); - window[callbackName] = function (response) { - const div = document.createElement('div'); - div.innerHTML = response.html; - document.querySelector(".md-header__inner > .md-header__title").appendChild(div); - const container = div.querySelector('.rst-versions'); - var caret = document.createElement('div'); - caret.innerHTML = "" - caret.classList.add('dropdown-caret') - div.querySelector('.rst-current-version').appendChild(caret); - div.querySelector('.rst-current-version').addEventListener('click', function () { - const classes = container.className.split(' '); - const index = classes.indexOf('shift-up'); - if (index === -1) { - classes.push('shift-up'); - } else { - classes.splice(index, 1); - } - container.className = classes.join(' '); - }); - } - - var CSSLink = document.createElement('link'); - CSSLink.rel = 'stylesheet'; - CSSLink.href = '/assets/versions.css'; - document.getElementsByTagName('head')[0].appendChild(CSSLink); - - var script = document.createElement('script'); - script.src = 'https://devstream.readthedocs.io/_/api/v2/footer_html/?' + - 'callback=' + callbackName + '&project=devstream&page=&theme=mkdocs&format=jsonp&docroot=docs&source_suffix=.md&version=' + (window['READTHEDOCS_DATA'] || { version: 'latest' }).version; - document.getElementsByTagName('head')[0].appendChild(script); -}, 0); - -// VERSION WARNINGS -window.addEventListener("DOMContentLoaded", function () { - var rtdData = window['READTHEDOCS_DATA'] || { version: 'latest' }; - var margin = 30; - var headerHeight = document.getElementsByClassName("md-header")[0].offsetHeight; - if (rtdData.version === "latest") { - document.querySelector("div[data-md-component=announce]").innerHTML = "
You are viewing the latest of DevStream docs. Maybe you want to choose a specific version.
" - var bannerHeight = document.getElementById('announce-msg').offsetHeight + margin - document.querySelector("header.md-header").style.top = bannerHeight + "px"; - document.querySelector('style').textContent += - "@media screen and (min-width: 76.25em){ .md-sidebar { height: 0; top:" + (bannerHeight + headerHeight) + "px !important; }}" - document.querySelector('style').textContent += - "@media screen and (min-width: 60em){ .md-sidebar--secondary { height: 0; top:" + (bannerHeight + headerHeight) + "px !important; }}" - } - // at the moment we do not have stable version; keep the following code for future reference - // else if (rtdData.version !== "stable") { - // document.querySelector("div[data-md-component=announce]").innerHTML = "
You are viewing the docs for a previous version of DevStream, click here to go to the latest stable version.
" - // var bannerHeight = document.getElementById('announce-msg').offsetHeight + margin - // document.querySelector("header.md-header").style.top = bannerHeight +"px"; - // document.querySelector('style').textContent += - // "@media screen and (min-width: 76.25em){ .md-sidebar { height: 0; top:"+ (bannerHeight+headerHeight)+"px !important; }}" - // document.querySelector('style').textContent += - // "@media screen and (min-width: 60em){ .md-sidebar--secondary { height: 0; top:"+ (bannerHeight+headerHeight)+"px !important; }}" - // } -}); diff --git a/docs/commands/apply.md b/docs/commands/apply.md deleted file mode 100644 index 9977246e1..000000000 --- a/docs/commands/apply.md +++ /dev/null @@ -1,29 +0,0 @@ -# dtm apply - -When _applying_ a config file using `dtm`, here's what happens: - -## 1 For Each _Tool_ Defined in the _Config_ - -We compare the _Tool_, its _State_, and the _Resoruce_ it has created before (if the state exists). - -We generate a plan of changes according to the comparison result: - -- If the _Tool_ isn't in the _State_, the `Create` interface will be called. -- If the _Tool_ is in the _State_, but the _Config_ is different than the _State_ (meaning users probably updated the config after the last `apply`,) the `Update` interface will be called. -- If the _Tool_ is in the _State_, and the _Config_ is the same as the _State_, we try to read the _Resource_. - - If the _Resource_ doesn't exist, the `Create` interface will be called. It probably suggests that the _Resource_ got deleted manually after the last successful `apply`. - - If the _Resource_ does exist but drifted from the _State_ (meaning somebody modified it), the `Update` interface will be called. - - Last but not least, nothing would happen if the _Resource_ is exactly the same as the _State_. - -## 2 For Each _State_ That Doesn't Have a _Tool_ in the _Config_ - -We generate a "Delete" change to delete the _Resource_. Since there isn't a _Tool_ in the config but there is a _State_, it means maybe the _Resource_ had been created previously then the user removed the _Tool_ from the _Config_, which means the user doesn't want the _Resource_ any more. - -## 3 Flags - -| Short | Long | Default | Description | -|-------|---------------|--------------------------|--------------------------------------| -| -f | --config-file | `"config.yaml"` | The path to the config file. | -| -d | --plugin-dir | `"~/.devstream/plugins"` | The path to store plugins. | -| -y | --yes | `false` | Apply directly without confirmation. | - diff --git a/docs/commands/apply.zh.md b/docs/commands/apply.zh.md deleted file mode 100644 index 30ba51d53..000000000 --- a/docs/commands/apply.zh.md +++ /dev/null @@ -1,28 +0,0 @@ -# dtm apply - -当使用 `dtm` _apply_ 配置文件时,会发生以下事情: - -## 1 对于 _Config(配置文件)_ 中定义的每个 _Tool_ - -我们将会对比 _Tool_、它的 _State_ 和它之前创建的 _Resoruce_(如果存在 state)。 - -根据对比结果,变更规则如下: - -- 若该 _Tool_ 不在 _State_ 中,调用 `Create` 接口; -- 若该 _Tool_ 存在于 _State_ 中,但当前 _Config_ 中关于该 _Tool_ 的配置与 _State_ 中的定义不同(意味着用户可能在上一次 `apply` 之后更新了配置),则调用 `Update` 接口; -- 若该 _Tool_ 存在于 _State_ 中,且当前 _Config_ 中关于该 _Tool_ 的配置与 _State_ 相同。我们将会继续尝试通过 `Read` 接口读取 _Resource_ ,并与 _State_ 中记录的 _Resource_ 比对: - - 若从 `Read` 读取到的 _Resource_ 不存在,调用 `Create` 接口。这可能表明 _Resource_ 在最后一次成功 `apply` 后被手动删除; - - 若从 `Read` 读取到的 _Resource_ 存在,但与 _State_ 中记录的 _Resource_ 不一致(意味着有人修改了 _State_ 或插件状态发生了变化),调用 `Update` 接口; - - 最后,若读取到的 _Resource_ 和 _State_ 中的 _Resource_ 一致,什么都不做。 - -## 2 _State_ 中含有某 _Tool_,但 _Config_ 中没有 - -我们将对其执行"删除"操作,以删除相应的 _Resource_ 。因为 _State_ 中含有此 _Tool_,但配置文件中不存在了,这意味着用户先前为该 _Tool_,创建了 _Resource_,但后面从 _Config_ 中删除了该 _Tool_,表明用户不想要该 _Resource_ 了。 - -## 3 命令行参数 - -| 短 | 长 | 默认值 | 描述 | -|-----|---------------|--------------------------|------------| -| -f | --config-file | `"config.yaml"` | 配置文件路径 | -| -d | --plugin-dir | `"~/.devstream/plugins"` | 插件目录 | -| -y | --yes | `false` | 取消二次确认 | diff --git a/docs/commands/autocomplete.md b/docs/commands/autocomplete.md deleted file mode 100644 index 43719dbf4..000000000 --- a/docs/commands/autocomplete.md +++ /dev/null @@ -1,136 +0,0 @@ -# Autocomplete - -## Fig Autocomplete - -In order to give a better experience for terminal users, we [support the Fig Auto-complete](https://github.com/withfig/autocomplete/blob/master/src/dtm.ts). - -Unlike other auto-complete tools, [Fig](https://fig.io) is more intuitive. It brings an IDE-style experience to the terminal users. Detailed introduction see the [official website](https://fig.io/) - -![](fig/fig-intro.gif) - -**Notice: Temporary only supports MacOS now!** - -### Setup - -See [https://fig.io](https://fig.io) - -![](fig/fig-terminal.png) - -Once the installation is complete, you need to integrate the terminal you are using. - -### Examples - -#### Get Specified Plugin Information - -![](fig/cmd-show-plugins.gif) - -#### Get Subcommand Help Information - -![](fig/cmd-help.gif) - -#### Build a Specified Plugin - -![](fig/cmd-make.gif) - -## Shell Autocomplete - -### Bash Autocompletion - -#### On Linux - -**Note: Main reference [Install and Set Up kubectl on Linux](https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/#enable-shell-autocompletion)** - -The completion script depends on `bash-completion`, So you have to install it first. - -```bash -apt-get install bash-completion # For Ubuntu - -yum install bash-completion # For CentOS and RedHat -``` - -The above commands create `/usr/share/bash-completion/bash_completion`, which is the main script of `bash-completion`. Depending on your package manager, you have to manually source this file in your `~/.bashrc` file. - -To find out, reload your shell and run `type _init_completion`. If the command succeeds, you're already set, otherwise add the following to your `~/.bashrc` file: - -```bash -source /usr/share/bash-completion/bash_completion -``` - -Reload your shell and verify that bash-completion is correctly installed by typing `type _init_completion`. - -Then You can generate completion script for Bash with the command `dtm completion bash` and add the following line to your `~/.bashrc` file. You can execute the following command to add it automatically. - -```bash -echo 'source <(dtm completion bash)' >>~/.bashrc -source ~/.bashrc -``` - -After reloading your shell, dtm autocompletion should be working! -#### On MacOS - -```{admonition} Note -:class: note -Main reference [Install and Set Up kubectl on macOS](https://kubernetes.io/docs/tasks/tools/install-kubectl-macos/#enable-shell-autocompletion) -``` - -The completion script depends on `bash-completion`, So you have to install it first. - -```bash -brew install bash-completion@2 -``` - -As stated in the output of this command, add the following to your `~/.bash_profile` file: - -```bash -brew_etc="$(brew --prefix)/etc" -echo "[[ -r \"${brew_etc}/profile.d/bash_completion.sh\" ]] && . \"${brew_etc}/profile.d/bash_completion.sh\"" >>~/.bash_profile -source ~/.bash_profile -``` - -Reload your shell and verify that bash-completion v2 is correctly installed with `type _init_completion`. - -Then You can generate completion script for Bash with the command `dtm completion bash` and add the following to your `~/.bash_profile` file. You can execute the following command to add it automatically. - -```bash -echo 'source <(dtm completion bash)' >>~/.bash_profile -``` - -After reloading your shell, dtm autocompletion should be working! - -### Zsh Autocompletion - -You can generate completion script for Zsh with the command `dtm completion zsh`. Then add the following line to your `~/.zshrc` file. You can execute the following command to add it automatically. - -```zsh -echo 'source <(dtm completion zsh)' >>~/.zshrc -``` - -After reloading your shell, dtm autocompletion should be working! - -### Fish Autocompletion - -You can generate completion script for Fish with the command `dtm completion fish`.Then add the following line to your `~/.config/fish/config.fish` file: - -```fish -dtm completion fish | source -``` - -After reloading your shell, dtm autocompletion should be working! - -### PowerShell Autocompletion - -You can generate completion script for PowerShell with the command `dtm completion powershell`. Then add the following line to your `$PROFILE` file: - -```powershell -dtm completion powershell | Out-String | Invoke-Expression -``` - -This command will regenerate the auto-completion script on every PowerShell start up. You can also add the generated script directly to your `$PROFILE` file. - -To add the generated script to your `$PROFILE` file, run the following line in your powershell prompt: - -```powershell -dtm completion powershell >> $PROFILE -``` - -After reloading your shell, dtm autocompletion should be working. diff --git a/docs/commands/autocomplete.zh.md b/docs/commands/autocomplete.zh.md deleted file mode 100644 index 225c4ce22..000000000 --- a/docs/commands/autocomplete.zh.md +++ /dev/null @@ -1,139 +0,0 @@ -# 自动补全 - -## Fig 自动补全 - -为了给终端用户提供更好的体验,我们支持[Fig 自动补全](https://github.com/withfig/autocomplete/blob/master/src/dtm.ts)。 - -与其他自动补全工具不同,[Fig](https://fig.io) 更直观。它为终端用户带来了 IDE 风格的体验。详细介绍见[官方网站](https://fig.io/) - -![](fig/fig-intro.gif) - -**注意:Fig 目前仅支持 MacOS!** - -### 安装 Fig - -详见 [https://fig.io](https://fig.io) - -![](fig/fig-terminal.png) - -安装完成后,你需要将 Fig 集成到你正在使用的终端。 - -### 补全示例 - -#### 查看插件模板配置时,补全插件名 - -![](fig/cmd-show-plugins.gif) - -#### 提示子命令 - -![](fig/cmd-help.gif) - -#### 构建时,补全插件名 - -![](fig/cmd-make.gif) - -## Shell 自动补全 - -### Bash 自动补全 - -#### Linux - -**注:主要参考了 [Install and Set Up kubectl on Linux](https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/#enable-shell-autocompletion)** - -自动补全脚本依赖于 `bash-completion`, 需要提前安装: - -```bash -apt-get install bash-completion # Ubuntu - -yum install bash-completion # CentOS 和 RedHat -``` - -上面的命令会创建 `/usr/share/bash-completion/bash_completion`,这是 `bash-completion` 的主要脚本。取决于不同的包管理器,你需要手动在 `~/.bashrc` 中引入(`source`) 这个文件。 - -为了测试是否配置成功,请重新加载 shell ,并运行 `type _init_completion` 来确认。若命令执行成功,则配置正确,否则请在 `~/.bashrc` 中添加如下内容: - -```bash -source /usr/share/bash-completion/bash_completion -``` - -重新加载 shell ,并运行 `type _init_completion` 来确认是否安装、配置成功。 - -然后,你可以通过 `dtm completion bash` 命令生成 Bash 自动补全脚本,执行下述命令,以将相应内容添加到 `~/.bashrc` 中: - -```bash -echo 'source <(dtm completion bash)' >>~/.bashrc -source ~/.bashrc -``` - -在重新加载 shell 后,dtm 自动补全应该就可以正常使用了! - -#### MacOS - -```{admonition} Note -:class: note -主要参考了 [Install and Set Up kubectl on macOS](https://kubernetes.io/docs/tasks/tools/install-kubectl-macos/#enable-shell-autocompletion) -``` - -自动补全脚本依赖于 `bash-completion`, 需要提前安装: - -```bash -brew install bash-completion@2 -``` - -正如以上命令的输出所述,请在 `~/.bash_profile` 文件中添加以下内容: - -```bash -brew_etc="$(brew --prefix)/etc" -echo "[[ -r \"${brew_etc}/profile.d/bash_completion.sh\" ]] && . \"${brew_etc}/profile.d/bash_completion.sh\"" >>~/.bash_profile -source ~/.bash_profile -``` - -重新加载 shell,用 `type _init_completion` 验证 bash-completion v2 是否正确安装。 - -接着,用 `dtm completion bash` 命令为 Bash 生成自动补全脚本。 执行以下命令,以将相应内容添加到 `~/.bashrc` 中: - -```bash -echo 'source <(dtm completion bash)' >>~/.bash_profile -source ~/.bash_profile -``` - -在重新加载 shell 后,dtm 自动补全应该就可以正常使用了! - -### Zsh 自动补全 - -你可以用 `dtm completion zsh` 命令为 Zsh 生成自动补全脚本。 执行以下命令,以将相应内容添加到 `~/.zshrc` 中: - -```zsh -echo 'source <(dtm completion zsh)' >>~/.zshrc -source ~/.zshrc -``` - -在重新加载 shell 后,dtm 自动补全应该就可以正常使用了! - -### Fish 自动补全 - -你可以用 `dtm completion fish` 命令为 Fish 生成自动补全脚本。然后将以下行添加到 `~/.config/fish/config.fish` 文件中: - -```fish -dtm completion fish | source -``` - -在重新加载 shell 后,dtm 自动补全应该就可以正常使用了! - -### PowerShell 自动补全 - -你可以用 `dtm completion powershell` 命令为 PowerShell 生成自动补全脚本。然后将以下行添加到 `$PROFILE` 文件中: - -```powershell -dtm completion powershell | Out-String | Invoke-Expression -``` - -这个命令会在每次 PowerShell 启动时重新生成自动补全脚本。你也可以直接将生成的脚本添加到 `$PROFILE` 文件中。 - -为了将生成的脚本添加到 `$PROFILE` 文件中,请在 PowerShell 提示符中运行以下行: - -```powershell -dtm completion powershell >> $PROFILE -``` - -在重新加载 shell 后,dtm 自动补全应该就可以正常使用了! diff --git a/docs/commands/delete.md b/docs/commands/delete.md deleted file mode 100644 index b7b950da2..000000000 --- a/docs/commands/delete.md +++ /dev/null @@ -1,27 +0,0 @@ -# dtm delete - -## 1 Normal (Non-Force) Delete - -When _deleting_ using `dtm delete`, here's what happens: - -- Read the _Config_ -- For each _Tool_ defined in the _Config_, if there is a corresponding _State_, the `Delete` interface will be called. - -_Note: the `Delete` change will be executed only if the _State_ exists._ - -## 2 Force Delete - -When _deleting_ using `dtm delete --force`, here's what happens: - -- Read the _Config_ -- The `Delete` interface will be called for each _Tool_ defined in the _Config_. - -_Note: the difference between "force delete" and "normal delete" is that in force mode, no matter the _State_ exists or not, `dtm` will try to trigger the `Delete` interface. The purpose is for corner cases where the state gets corrupted or even lost during testing (I hope this only happens in the development environment), there is still a way to clean up the _Tools_._ - -## 3 Flags -| Short | Long | Default | Description | -|-------|---------------|--------------------------|---------------------------------------| -| | --force | `false` | Force delete by config. | -| -f | --config-file | `"config.yaml"` | The path to the config file. | -| -d | --plugin-dir | `"~/.devstream/plugins"` | The path to plugins. | -| -y | --yes | `false` | Delete directly without confirmation. | diff --git a/docs/commands/delete.zh.md b/docs/commands/delete.zh.md deleted file mode 100644 index 2195df529..000000000 --- a/docs/commands/delete.zh.md +++ /dev/null @@ -1,28 +0,0 @@ -# dtm delete - -## 1 普通(非强制)删除 - -使用 `dtm delete` 执行 _删除_ 时,会发生: - -- 读取 _Config_ -- 对于 _Config_ 中定义的每一个 _Tool_,如果有对应的 _State_,就会调用 `Delete` 接口。 - -_注意:`Delete` 操作只会在 _State_ 存在时执行。_ - -## 2 强制删除 - -使用 `dtm delete --force` 执行 _强制删除_ 时,会发生: - -- 读取 _Config_ -- 对于 _Config_ 中定义的每一个 _Tool_,调用 `Delete` 接口。 - -_说明:"普通删除"和"强制删除"的区别在于,无论 _State_ 是否存在,`dtm` 都会尝试调用 `Delete` 接口。此举目的在于,当 _State_ 损坏甚至丢失时(我们希望这只发生在开发环境),仍能有办法清除 _Tools_。 - -## 3 命令行参数 - -| 短 | 长 | 默认值 | 描述 | -|-----|---------------|--------------------------|------------| -| | --force | `false` | 强制删除 | -| -f | --config-file | `"config.yaml"` | 配置文件路径 | -| -d | --plugin-dir | `"~/.devstream/plugins"` | 插件目录 | -| -y | --yes | `false` | 取消二次确认 | diff --git a/docs/commands/destroy.md b/docs/commands/destroy.md deleted file mode 100644 index 7f452d5cd..000000000 --- a/docs/commands/destroy.md +++ /dev/null @@ -1,14 +0,0 @@ -# dtm destroy - -`dtm destroy` acts like `dtm apply -f an_empty_config.yaml`. - -The purpose of `destroy` is that in case you accidentally deleted your config file during testing, there would still be a way to destroy everything that is defined in the _State_ so that you can have a clean state. - -## 1 Flags - -| Short | Long | Default | Description | -|-------|---------------|---------|----------------------------------------| -| | --force | `false` | Force destroy by config. | -| -d | --plugin-dir | `"~/.devstream/plugins"` | The path to plugins. | -| -f | --config-file | `"config.yaml"` | The path to the config file. | -| -y | --yes | `false` | Destroy directly without confirmation. | diff --git a/docs/commands/destroy.zh.md b/docs/commands/destroy.zh.md deleted file mode 100644 index cee63dcce..000000000 --- a/docs/commands/destroy.zh.md +++ /dev/null @@ -1,14 +0,0 @@ -# dtm destroy - -`dtm destroy` 的效果类似于 `dtm apply -f an_empty_config.yaml`. - -`destroy` 命令的目的在于,一旦你在测试的时候不小心删除了配置文件,仍能通过此方式删除 _State_ 中定义的所有内容,以拥有干净的环境。 - -## 1 命令行参数 - -| 短 | 长 | 默认值 | 描述 | -|-----|---------------|--------------------------|------------| -| | --force | `false` | 强制删除 | -| -d | --plugin-dir | `"~/.devstream/plugins"` | 插件目录 | -| -f | --config-file | `"config.yaml"` | 配置文件路径 | -| -y | --yes | `false` | 取消二次确认 | diff --git a/docs/commands/develop.md b/docs/commands/develop.md deleted file mode 100644 index 9f92c78db..000000000 --- a/docs/commands/develop.md +++ /dev/null @@ -1,28 +0,0 @@ -# dtm develop - -`dtm develop` is used to develop a new plugin for anything. - -## 1 Brief - -`dtm develop create-plugin` generates plugin scaffolding based on dtm [pre-built templates](https://github.com/devstream-io/devstream/tree/main/internal/pkg/develop/plugin/template). - -`dtm develop validate-plugin` is used for verifying whether all the necessary files required for the plugin exist - -## 2 Full Process Guide - -A detailed guide to the plugin development process can be found in [creating a plugin](../development/dev/creating-a-plugin.md). - -## 3 Flags - -`dtm develop create-plugin`: - -| Short | Long | Default | Description | -|-------|--------|---------|------------------------------------------| -| -n | --name | `""` | specify name of the plugin to be created | - -`dtm develop validate-plugin`: - -| Short | Long | Default | Description | -|-------|--------|---------|--------------------------------------------| -| -n | --name | `""` | specify name of the plugin to be validated | -| -a | --all | `false` | validate all plugins | diff --git a/docs/commands/develop.zh.md b/docs/commands/develop.zh.md deleted file mode 100644 index 3df669b37..000000000 --- a/docs/commands/develop.zh.md +++ /dev/null @@ -1,27 +0,0 @@ -# dtm develop - -`dtm develop` 用于开发一个新的插件,做任何你想做的事情。 - -## 1 简介 - -- `dtm develop create-plugin` 根据[dtm 插件模板](https://github.com/devstream-io/devstream/tree/main/internal/pkg/develop/plugin/template)生成插件脚手架。 -- `dtm develop validate-plugin` 用于验证插件所需的必要文件是否都存在。 - -## 2 完整插件开发指南 - -插件开发的详细指南可以在[创建一个插件](../development/dev/creating-a-plugin.zh.md)找到。 - -## 3 命令行参数 - -`dtm develop create-plugin`: - -| 短 | 长 | 默认值 | 描述 | -|-----|--------|-------|-----------------| -| -n | --name | `""` | 要创建的插件的名称 | - -`dtm develop validate-plugin`: - -| 短 | 长 | 默认值 | 描述 | -|-----|--------|---------|-----------------| -| -n | --name | `""` | 要验证的插件的名称 | -| -a | --all | `false` | 验证所有插件 | diff --git a/docs/commands/fig/cmd-help.gif b/docs/commands/fig/cmd-help.gif deleted file mode 100644 index 5946ff06b..000000000 Binary files a/docs/commands/fig/cmd-help.gif and /dev/null differ diff --git a/docs/commands/fig/cmd-make.gif b/docs/commands/fig/cmd-make.gif deleted file mode 100644 index 3c3285323..000000000 Binary files a/docs/commands/fig/cmd-make.gif and /dev/null differ diff --git a/docs/commands/fig/cmd-show-plugins.gif b/docs/commands/fig/cmd-show-plugins.gif deleted file mode 100644 index 851cb6d39..000000000 Binary files a/docs/commands/fig/cmd-show-plugins.gif and /dev/null differ diff --git a/docs/commands/fig/fig-intro.gif b/docs/commands/fig/fig-intro.gif deleted file mode 100644 index 7dd68d1ae..000000000 Binary files a/docs/commands/fig/fig-intro.gif and /dev/null differ diff --git a/docs/commands/fig/fig-terminal.png b/docs/commands/fig/fig-terminal.png deleted file mode 100644 index 08bf1e9de..000000000 Binary files a/docs/commands/fig/fig-terminal.png and /dev/null differ diff --git a/docs/commands/init.md b/docs/commands/init.md deleted file mode 100644 index f4bc99070..000000000 --- a/docs/commands/init.md +++ /dev/null @@ -1,49 +0,0 @@ -# dtm init - -`dtm` releases plugins to an AWS S3 bucket. - -When running `dtm init`, it will download plugins from the AWS S3 bucket(through Cloudflare CDN.) - -There are two ways to download plugins: - -1. from config file: download plugins according to `tools` and `apps` in the config -2. from command line: download plugins according to the command line arguments - -## Downloading Plugins from Config File - -In this way, `dtm init` will download the required plugins according to `tools` and `apps` in the config. - -**command:** `dtm init -f `. - -You can put all the configuration in one file, or you can spread it out into multiple files in the same directory with `yaml` or `yaml` as a suffix. - -For config file, tools and apps, see the [config](../core-concepts/config.md) section of this documentation. - -## Downloading Plugins from Command Line - -This can be used to pre-download the plugin to use `dtm` in an **offline environment**. - -command: - -- download specify plugins. e.g. `dtm init --download-only --plugins="repo-scaffolding, github-actions" -d=.devstream/plugins` -- download all plugins. e.g. `dtm init --download-only --all -d=.devstream/plugins` - -## Init Logic - -- Based on the config file and the tool file or command line flags, decide which plugins are required. -- If the plugin exists locally and the version is correct, do nothing. -- If the plugin is missing, download the plugin. -- After downloading, `dtm` also downloads the md5 value of that plugin and compares them. If matched, succeed. - -## Flags - -| Short | Long | Default | Description | -|-------|-----------------|--------------------------|-----------------------------------------------------------------------------------| -| -f | --config-file | `"config.yaml"` | The config file to use | -| -d | --plugin-dir | `"~/.devstream/plugins"` | The directory to store plugins | -| | --download-only | `false` | Download plugins only, generally used for using dtm offline | -| -p | --plugins | `""` | Plugins to be downloaded, seperated with ",", should be used with --download-only | -| -a | --all | `false` | Download all plugins, should be used with --download-only | -| | --os | OS of this machine | The OS to download plugins for. | -| | --arch | Arch of this machine | The architecture to download plugins for. | - diff --git a/docs/commands/init.zh.md b/docs/commands/init.zh.md deleted file mode 100644 index 86fe5acf6..000000000 --- a/docs/commands/init.zh.md +++ /dev/null @@ -1,52 +0,0 @@ -# dtm init - -`dtm` 将插件发布在 AWS S3 Bucket。 - -当运行 `dtm init` 时,它会从 AWS S3 下载插件(通过Cloudflare CDN)。 - -`dtm` 提供了两种下载插件的方式: - -1. 根据配置文件:根据配置中的 `tools` 和 `apps` 下载插件 -2. 根据命令行参数:根据命令行的参数下载插件 - -## 根据配置文件下载插件 - -这种方式下,`dtm init` 将根据配置中定义的 `tools` 和 `apps` 下载所需的插件。 - -**命令:** `dtm init -f `。 - -你可以把所有的配置放在一个文件中,也可以把配置分散至同一个目录下的多个以 `yaml` 或 `yaml` 为后缀的文件中。 - -关于配置文件、tools and apps,详见[DevStream 配置](../core-concepts/config.zh.md)。 - -## 根据命令行参数下载插件 - -这可以用来预先下载插件,以在**离线环境**使用 `dtm`。 - -**命令:** - -- 下载指定插件,如:`dtm init --download-only --plugins="repo-scaffolding, github-actions" -d=.devstream/plugins`。 -- 下载所有插件,如:`dtm init --download-only --all -d=.devstream/plugins`。 - - -## Init 逻辑 - -- 根据配置文件和 tool file,或命令行参数,决定下载哪些插件。 -- 如果该插件在本地存在,而且版本正确,就什么都不做。 -- 如果缺少该插件,则下载该插件。 -- 下载后,`dtm` 还会校验该插件的md5值。 - -## 命令行参数 - -| 短 | 长 | 默认 | 描述 | -|-----------------|---------------|--------------------------|--------------------------------------------------| -| -f | --config-file | `"config.yaml"` | 配置文件路径 | -| -d | --plugin-dir | `"~/.devstream/plugins"` | 插件存储目录 | -| --download-only | `false` | `""` | 只下载插件,一般用于离线使用 `dtm` | -| -p | --plugins | `""` | 要下载的插件,用","隔开,应与 --download-only 一起使用 | -| -a | --all | `false` | 下载所有插件,应与 --download-only 一起使用 | -| | --os | 本机的操作系统 | 要下载插件的操作系统。 | -| | --arch | 本机的架构 | 要下载插件的架构。 | - - - diff --git a/docs/commands/list.md b/docs/commands/list.md deleted file mode 100644 index b30a6496e..000000000 --- a/docs/commands/list.md +++ /dev/null @@ -1,9 +0,0 @@ -# dtm list - -`dtm list` lists all supported plugins. - -## 1 Flags - -| Short | Long | Default | Description | -|-------|----------|---------|------------------------| -| -r | --filter | `""` | Filter plugin by regex | diff --git a/docs/commands/list.zh.md b/docs/commands/list.zh.md deleted file mode 100644 index 5e5a3ef68..000000000 --- a/docs/commands/list.zh.md +++ /dev/null @@ -1,9 +0,0 @@ -# dtm list - -`dtm list` 列出所有支持的插件。 - -## 1 命令行参数 - -| 短 | 长 | 默认值 | 描述 | -|-----|----------|-------|--------------------| -| -r | --filter | `""` | 通过正则表达式过滤插件 | diff --git a/docs/commands/show.md b/docs/commands/show.md deleted file mode 100644 index 598de7875..000000000 --- a/docs/commands/show.md +++ /dev/null @@ -1,33 +0,0 @@ -# dtm show - -`dtm show` shows plugins' configuration templates or status. - -## 1 show config template of plugin/toolchain - -`dtm show config` shows configuration templates of plugins or sample toolchains. - -Flags: - -| Short | Long | Default | Description | -|-------|------------|---------|----------------------------------------------------------------------------| -| -p | --plugin | `""` | plugin name | -| -t | --template | `""` | name of template tool chain, currently supports "quickstart" and "gitopts" | - -## 2 show plugin status - -`dtm show status` shows status of plugin instances. - -Flags: - -| Short | Long | Default | Description | -|-------|---------------|--------------------------|------------------------------------------| -| -p | --plugin | `""` | plugin name | -| -i | --id | `""` | plugin instance id | -| -a | --all | `false` | show all instances of all plugins status | -| -d | --plugin-dir | `"~/.devstream/plugins"` | plugins directory | -| -f | --config-file | `"config.yaml"` | config file | - - -_Note: If `-p` and `-i` are both empty, `dtm` will show all instances of all plugins' status._ - - diff --git a/docs/commands/show.zh.md b/docs/commands/show.zh.md deleted file mode 100644 index 7bad717eb..000000000 --- a/docs/commands/show.zh.md +++ /dev/null @@ -1,30 +0,0 @@ -# dtm show - -`dtm show` 用于展示插件的配置文件模板或状态。 - -## 1 展示 插件/工具链 配置模板 - -`dtm show config` 展示插件的配置文件模板或示例工具链的配置文件模板。 - -命令行参数: - -| 短 | 长 | 默认值 | 描述 | -|-----|------------|-------|------------------------------------------------| -| -p | --plugin | `""` | 插件名字 | -| -t | --template | `""` | 示例工具链名字,目前支持 "quickstart" 和 "gitopts" | - -## 2 展示插件状态 - -`dtm show status` 展示插件实例的状态。 - -命令行参数: - -| 短 | 长 | 默认值 | 描述 | -|-----|---------------|--------------------------|-------------------------| -| -p | --plugin | `""` | 插件名字 | -| -i | --id | `""` | 插件实例 id | -| -a | --all | `false` | 展示所有插件的所有实例的状态 | -| -d | --plugin-dir | `"~/.devstream/plugins"` | 插件目录 | -| -f | --config-file | `"config.yaml"` | 配置文件 | - -_说明:如果 `-p` 和 `-i` 都为空,`dtm` 将展示所有插件的所有实例的状态。_ diff --git a/docs/commands/upgrade.md b/docs/commands/upgrade.md deleted file mode 100644 index 0f35a6b8b..000000000 --- a/docs/commands/upgrade.md +++ /dev/null @@ -1,9 +0,0 @@ -# dtm upgrade - -`dtm upgrade` upgrades dtm to the latest release version - -## 1 Flags - -| Short | Long | Default | Description | -|-------|-------|---------|---------------------------------------| -| -y | --yes | `false` | upgrade directly without confirmation | diff --git a/docs/commands/upgrade.zh.md b/docs/commands/upgrade.zh.md deleted file mode 100644 index 79d51a924..000000000 --- a/docs/commands/upgrade.zh.md +++ /dev/null @@ -1,9 +0,0 @@ -# dtm upgrade - -`dtm upgrade` 将 dtm 升级到最新的发布版本。 - -## 1 命令行参数 - -| 短 | 长 | 默认值 | 描述 | -|-----|-------|---------|-------------------| -| -y | --yes | `false` | 直接升级,不二次确认 | diff --git a/docs/commands/verify.md b/docs/commands/verify.md deleted file mode 100644 index 937a89113..000000000 --- a/docs/commands/verify.md +++ /dev/null @@ -1,37 +0,0 @@ -# dtm verify - -The command `dtm verify` checks the following: - -## 1 Config File - -`dtm verify` first verifies if the config file can be loaded successfully. - -If not, the following information might be printed out: - -- if the config file doesn't exist, it reminds you if you forgot to specify the config file by using the "-f" parameter; -- if the config format isn't correct, it would print some error. - -## 2 Plugins - -`dtm verify` then checks if all required plugins (according to the config file) exist. - -If not, it tries to give you a hint that maybe you forgot to run `dtm init` first. - -## 3 State - -`dtm verify` also tries to create a state manager that operates a backend. If something is wrong with the state, it generates an error telling you what exactly the error is. - -## 4 Config / State / ResourceStatus - -For definitions of _Config_, _State_, and _ResourceStatus_, see [Core Concepts](../core-concepts/overview.md). - -`dtm verify` tries to see if the _Config_ matches the _State_ and the _Resource_ or not. If not, it tells you what exactly is not the same, and what would happen if you run `dtm apply`. - -If all the above checks are successful, `dtm verify` finishes with a success log "Verify succeeded." - -## 5 Flags - -| Short | Long | Default | Description | -|-------|---------------|--------------------------|------------------------------| -| -f | --config-file | `"config.yaml"` | The path to the config file. | -| -d | --plugin-dir | `"~/.devstream/plugins"` | The path to plugins. | diff --git a/docs/commands/verify.zh.md b/docs/commands/verify.zh.md deleted file mode 100644 index e0aaf300a..000000000 --- a/docs/commands/verify.zh.md +++ /dev/null @@ -1,37 +0,0 @@ -# dtm verify - -`dtm verify` 命令将检查以下内容: - -## 1 配置文件 - -`dtm verify` 首先验证配置文件是否能成功加载。 - -若失败,可能会打印出以下信息: - -- 若配置文件不存在,它会提醒你是否忘记使用 "-f" 参数来指定配置文件。 -- 若配置文件格式不正确,它将打印一些错误。 - -## 2 插件列表 - -接着,`dtm verify` 会检查配置文件中引用的所有插件是否存在。 - -如果不存在,它会提示你忘了先运行 `dtm init`(也有可能是 "plugin-dir" 参数不对)。 - -## 3 State - -`dtm verify` 也会尝试创建一个操作 _backend_ 的 _State_ 管理器。若 _State_ 出现问题,将提示错误所在。 - -## 4 Config / State / ResourceStatus - -关于 _Config_ 、 _State_ 和 _ResourceStatus_ 的定义,见[核心概念](../core-concepts/overview.zh.md)。 - -`dtm verify` 尝试判断 _Config_ 是否与 _State_ 和 _ResourceStatus_ 匹配。如果不匹配,它会告诉你到底是什么不一样,以及如果你运行 `dtm apply` 会发生什么。 - -如果所有上述检查都成功,`dtm verify` 最后将输出 "Verify succeeded." 日志提示。 - -## 5 命令行参数 - -| 短 | 长 | 默认值 | 描述 | -|-----|---------------|--------------------------|------------| -| -f | --config-file | `"config.yaml"` | 配置文件路径 | -| -d | --plugin-dir | `"~/.devstream/plugins"` | 插件目录 | diff --git a/docs/contributing_guide.md b/docs/contributing_guide.md deleted file mode 100644 index f7b70bfa5..000000000 --- a/docs/contributing_guide.md +++ /dev/null @@ -1,126 +0,0 @@ -# Contributing Guide - -## New Contributor Guide - -Welcome! We are glad that you want to contribute to DevStream! 💖 - -As you get started, you are in the best position to give us feedback on areas of our project that we need help with include: - -- problems found during setting up a new developer environment; -- gaps in our quick start guide or documentation; -- bugs in our automation scripts. - -If anything doesn't work when you run it or doesn't make sense, please open a bug report and let us know! - -## Ways to Contribute - -We welcome many different types of contributions including: - -- New features -- Builds, CI/CD -- Bug fixes -- Documentation -- Issue Triage -- Answering questions on Slack/Mailing List -- Web design -- Communications / Social Media / Blog Posts -- Release management - -Not everything happens through a GitHub pull request. Please come to our [meetings](https://github.com/devstream-io/devstream/wiki) or [contact us](https://cloud-native.slack.com/archives/C03LA2B8K0A) and let's discuss how we can work together. - -## Come to Meetings - -Absolutely everyone is welcome to come to any of our meetings. You never need an invitation to join us. We want you to join us, even if you don’t have anything you feel like you want to contribute. Just being there is enough! - -- You can find out more about our meetings on [our wiki page here](https://github.com/devstream-io/devstream/wiki). -- Please also consider joining our [Slack channel](https://cloud-native.slack.com/archives/C03LA2B8K0A) because meeting schedules and announcements will also be made available there. -- You don’t have to turn on your video. The first time you come, introducing yourself is more than enough. -- Over time, we hope that you feel comfortable voicing your opinions, giving feedback on others’ ideas, and even share your ideas and experiences. - -## Find an Issue - -We have good first issues for new contributors and help wanted issues suitable for any contributor. - -- [good first issue](https://github.com/devstream-io/devstream/labels/good%20first%20issue) has extra information to help you make your first contribution. If you are new to DevStream (or even new to open-source,) this is a good place to get yourself started. For more information, see the [good first issues doc](development/git-workflow/good-first-issues.md). -- [help wanted](https://github.com/devstream-io/devstream/labels/help%20wanted) are issues suitable for someone who isn't a core maintainer and is good to move onto after your "good first issue." For more information, see the [help wanted doc](development/git-workflow/help-wanted.md). -- Sometimes there won’t be any issues with these labels. That’s ok! There is likely still something for you to work on. If you want to contribute but you don’t know where to start or can't find a suitable issue, you can reach out to us on our [Slack channel](https://cloud-native.slack.com/archives/C03LA2B8K0A) and ask for an issue to work on. - -Once you see an issue that you'd like to work on, please post a comment saying that you want to work on it. Something like "I want to work on this" is fine. - -## Ask for Help - -The best way to reach us with a question when contributing is to ask: - -- The original GitHub issue -- Our [Slack channel](https://cloud-native.slack.com/archives/C03LA2B8K0A). - -## Pull Request Lifecycle - -The unwritten rules (heck, we are writing them anyways) for PRs, contributors, and reviewers: - -- Contributors are encouraged to submit a PR even when it's in a work-in-progress state. -- If a PR is still a work-in-progress, use the "[draft PR](https://github.blog/2019-02-14-introducing-draft-pull-requests/)" function of GitHub to indicate you are already working on it, and show the community members your progress. -- When a PR is ready for review (not in the draft PR status,) reviewers are expected to do an initial review within 24 hours. If it's a weekend or holiday, it might get delayed. -- The author should ping/bump when the pull request is ready for further review. If the PR appears to be stalled for over 24 hours, you can also ping/bump. -- If your PR is stuck (seems can't get reviewed,) besides ping/bump, you can also reach out to the [Slack channel](https://cloud-native.slack.com/archives/C03LA2B8K0A) and notify the reviewers there. -- Small scope (incremental value, even one spelling correction) PR is completely fine. No PR is too small. Don't worry that because you only changed a word and that PR won't get merged. -- Complete feature PRs are also welcome. In this case, however, try not to make the PR so huge that it becomes extremely hard for reviewers to review. -- Small or big PR, it's nice to create an issue for each PR to keep things tracked. -- We don't have a specific feature branch. Normally you would do a PR against the main branch. If it's a bugfix specific to a version, make sure a PR to that release branch is also created. -- If you as a contributor do not want to follow through with a PR, please try to let us know via our Slack channel or directly in the PR/issue, so that the maintainers can continue working on it by committing to the PR directly to help get it merged. -- Maintainers might close a PR if the contributor hasn’t responded in a month. -- Currently, we don't release regularly, so there isn't any guarantee about when your PR will be included in the next release. However, we are trying our best to make releases as frequent as possible. -- As an encouragement to contributors, it's fine to close an issue or merge a PR even if the originally designed features aren't 100% implemented. In cases like this, please encourage contributors to create a follow-up issue and PR to further implement that. Do not make the PR process last too long because it might discourage contributors. - -For more documentation on GitHub collaboration, see the [GitHub Collaboration Process Guidelines](./development/git-workflow/git-workflow.md). - -## Development - -- Recommended IDEs: [Visual Studio Code](https://code.visualstudio.com/), [GoLand](https://www.jetbrains.com/go/) -- [Development Environment Setup](./development/dev/dev-env-setup.md) -- [Code linter](./development/dev/lint.md) -- [Build the source code](./development/dev/build.md) -- [Test the source code: unit test, e2e test](./development/dev/test.md) -- [Create a plugin](./development/dev/creating-a-plugin.md) - -## DevStream Architecture - -- [Architecture Overall](development/architecture.md) -- [Project Layout](development/project-layout.md) - -## Documentation Contribution - -- [Create documentation for DevStream](./development/docs-contribution/mkdocs.md) -- [Document translation](./development/docs-contribution/translation.md) - -## Sign Your Commits - -Licensing is important to open source projects. It provides some assurances that the software will continue to be available based on the terms that the author(s) desired. We require that contributors sign off on commits submitted to our project's repositories. The [Developer Certificate of Origin (DCO)](https://developercertificate.org/) is a way to certify that you wrote and have the right to contribute the code you are submitting to the project. - -You sign off by adding the following to your commit messages. Your sign-off must match the git user and email associated with the commit. - - This is my commit message - - Signed-off-by: Your Name - -Git has a `-s` command-line option to do this automatically: - - git commit -s -m 'This is my commit message' - -If you forgot to do this and have not yet pushed your changes to the remote repository, you can amend your commit with the sign-off by running - - git commit --amend -s - -## Pull Request Checklist - -When you submit your pull request, or you push new commits to it, our automated systems will run some checks on your new code. We require that your pull request passes these checks, but we also have more criteria than just that before we can accept and merge it. We recommend that you check the following things locally before you submit your code: - -- Lint your code. Although this will be checked by our CI, linting it yourself locally first before creating a PR will save you some time and effort. -- Build/test your code. Same as above. -- Double-check your commit messages. More details in the [commit message guidelines](./development/git-workflow/commit-messages.md). - -## Maintainer Team at Merico - -A group of engineers maintains DevStream at Merico, led by [@ironcore864](https://github.com/ironcore864). - -We aim to reply to issues within 24 hours. diff --git a/docs/contributing_guide.zh.md b/docs/contributing_guide.zh.md deleted file mode 100644 index 18e623941..000000000 --- a/docs/contributing_guide.zh.md +++ /dev/null @@ -1,130 +0,0 @@ -# 贡献指引 - -## 致新贡献者 -欢迎你的到来,非常感谢你愿意一起建设 DevStream 💖。 - -初次参与,你遇到任何问题都可以直接反馈与联系社区,包括但不限于: - -- 开发环境搭建时遇到任何问题。 -- 快速开始(quick start) 文档遇到任何问题。 -- 自动化脚本的任何问题。 - -如果你在运行项目的时候发现任何不符合预期或不合理的地方,请直接提交 Bug 报告! - -## 如何贡献 -我们欢迎各种贡献,包括但不限于: - -- 新功能(feature) -- 代码构建、CI/CD -- Bug 修复 -- 文档 -- Issue 分类、发起、回复、管理、维护等(Issue Triage) -- 在 微信群、Slack、邮件列表解答问题 -- 网站页面设计 -- 在各种媒体、博客文章宣传 DevStream -- 发版管理 - -不是只有提交 Pull Request 才能与 DevStream 擦出火花,你还可以参与我们的 [社区会议](https://github.com/devstream-io/devstream/wiki),或者直接联系我们([slack](https://cloud-native.slack.com/archives/C03LA2B8K0A)、[微信群](https://raw.githubusercontent.com/devstream-io/devstream/main/docs/images/wechat-group-qr-code.png))一起聊聊怎么共建社区。 - -## 参与社区会议 -DevStream 真诚地欢迎每一个人参与我们的会议,不需要被邀请,直接来就行!哪怕你暂时没有贡献的想法,只是来听听,我们也非常热烈欢迎。来听,就够了。 - -- 你可以在我们的 [wiki 页面](https://github.com/devstream-io/devstream/wiki)) 找到关于社区会议的详细信息 -- 也请加入我们的 [Slack 频道](https://cloud-native.slack.com/archives/C03LA2B8K0A) ,因为会议日程和重要的事情会在 Slack 公布。 -- 对于中国用户,也可以加 [微信群](https://raw.githubusercontent.com/devstream-io/devstream/main/docs/images/wechat-group-qr-code.png)。 -- 第一次参会你可以不打开摄像头,简单地介绍自己就足够了。当然,如果你想多聊几句,无论是 Q&A 还是聊天,我们都非常欢迎。 -- 随着时间的推移与你对 DevStream 沟通的加深,我们希望你能自如地发表意见、对他人的想法提出反馈,甚至分享你的想法与经验。 - -## 寻找 Issue -你可以在 Issue 列表找到这样两个标签: `good first issue` 表示仅向新贡献者开放,`help wanted` 适合于所有贡献者。 - -- [good first issue](https://github.com/devstream-io/devstream/labels/good%20first%20issue) 含有更多的描述信息与指导,往往只涉及一小部分,完成它不需要你熟悉整个项目。如果你是第一次参与 DevStream(甚至是第一次参与开源),非常推荐你从 good first issue 开始共建之旅。更多信息,请参阅 [good first issue 文档](development/git-workflow/good-first-issues.zh.md)。 -- [help wanted](https://github.com/devstream-io/devstream/labels/help%20wanted) 指引了你在完成了 good first issue 后,适合继续贡献的地方。带有这个标签的 issue,除了核心贡献者外的任何人都可参与。更多信息,请参阅 [help wanted 文档](development/git-workflow/help-wanted.zh.md)。 -- 你不需要拘泥于带有这两个标签的 issue,其他任何你感兴趣的 issue,都可以直接参与,无论是方案修改建议、讨论与回复、还是代码。 -- 有时 good first issue 或 help wanted 因为社区过于热情导致暂时没有空余,只要你愿意,你仍可以参与贡献!直接在 [Slack](https://join.slack.com/t/devstream-io/shared_invite/zt-16tb0iwzr-krcFGYRN7~Vv1suGZjdv4) 或 [微信群](https://raw.githubusercontent.com/devstream-io/devstream/main/docs/images/wechat-group-qr-code.png) 联系我们,告诉我们你愿意参与! - -如果你对某个 issue 有兴趣,想完成对应内容,请在 issue 内留下评论,例如:"I want to work on this"。 - -## 寻求帮助 -在贡献时,向我们提问的最好的方式如下: - -- 直接在 GitHub issue 下评论 -- [Slack 频道](https://cloud-native.slack.com/archives/C03LA2B8K0A) -- [微信群](https://raw.githubusercontent.com/devstream-io/devstream/main/docs/images/wechat-group-qr-code.png) 也可以 - -我们更推荐你在 GitHub 或 Slack 提问,这有助于技术讨论被沉淀下来,以帮助后来的贡献者。 - -## Pull Request 流程与指南 -针对 PR、贡献者和审阅者的不成文规则(我们不想有那么多条条框框,但这确实能为你减少不必要的时间浪费)。 - -- 我们鼓励贡献者提交 PR,即使它处于“work-in-progress”状态。 -- 如果你的 PR 代码还没完成编写,请使用 GitHub 的 "[draft PR](https://github.blog/2019-02-14-introducing-draft-pull-requests/)" 功能来告诉社区这个 PR 还在编写中。同时,这也意味着,你不一定要在所有代码完成后再 push,可以先实现大部分内容,提交个 draft PR,这有助于与社区及时交流沟通。 -- 当你的 PR 做好了被审阅(review)(处于非 draft PR 状态)的准备后,审阅者应在 24 小时内进行初始审阅。如果是周末或节假日,可能会延迟。 -- PR 的作者应当主动请求审阅(request-review),或者在 PR 内评论与 @审阅者,也可直接通过社交媒体进行沟通。如果审阅者未在24小时内回复,请相信我们绝不是有意遗漏你,请再次提醒我们。 -- 微小的修改(甚至是拼写错误)也完全可以提 PR。不存在所谓的"小 PR",不必担心 PR 过小导致代码不被合并。 -- 无论是较大的 PR 还是较小的,我们建议你创建一个对应的 issue 以持续跟进与追踪。 -- 我们没有特定的特性分支。通常,你会将 PR 并入主分支。如果它针对于特定某个版本的错误修复,请确保你的 PR 请求合并的是该特定分支。 -- 如果你想贡献,但不想通过提 PR 来贡献,可以在 Slack 频道、微信群。或直接在 Issue、PR 下告诉我们,以便 maintainer 直接提交 PR。 -- 如果你长期未回复 maintainer(通常是一个月),你的 PR 可能会被关闭,敬请谅解。 -- 目前,我们不会定期发布,因此无法保证你的 PR 何时包含在下一个版本中。但是,我们正在尽最大努力使发布尽可能频繁。 -- 为了鼓励贡献者,你可以在未100%完成原有设计的情况下,完成相应的任务,即关闭 issue 与合并 PR。在这种情况下,你可以再创建个新的 issue,并提交个新的 PR 来完成之前遗漏的工作。我们不希望你在一个 PR 上耗时太久,这会打击你的兴趣。 - -更多关于 GitHub 协作的文档,可参考 [GitHub 协作流程指引](./development/git-workflow/git-workflow.md)。 - -## 开发 - -- 推荐的 IDE:[Visual Studio Code](https://code.visualstudio.com/), [GoLand](https://www.jetbrains.com/go/) -- [开发环境搭建](./development/dev/dev-env-setup.zh.md) -- [代码格式检查](./development/dev/lint.zh.md) -- [源码构建](./development/dev/build.zh.md) -- [代码测试:单元测试(unit test)、端到端测试(e2e test)](./development/dev/test.zh.md) -- [开发新插件](./development/dev/creating-a-plugin.zh.md) - -## 架构解读 - -- [整体架构](development/architecture.zh.md) -- [项目组织结构](development/project-layout.zh.md) - -## 文档类贡献 - -- [为DevStream创建文档](./development/docs-contribution/mkdocs.zh.md) -- [文档翻译](./development/docs-contribution/translation.zh.md) - -## 代码提交署名(Sign Off) - -授权与认证(licensing) 对于开源项目非常重要,它确保了软件能基于作者提供的条款继续运作。我们需要你在贡献代码的时候署名你的提交,[Developer Certificate of Origin (DCO)](https://developercertificate.org/) 是一种认证你编写了此段代码,并表明你持有这段代码的方式。 - -你可以通过在提交信息(Git Commit Message)中附加这段信息。注意,你的署名必须与 Git 的用户名和邮箱对应。 - - This is my commit message - - Signed-off-by: Your Name - -Git 有一个 `-s` 的命令行选项可以帮助你自动署名: - - git commit -s -m 'This is my commit message' - -如果你忘记了署名操作,但未将代码提交到远程仓库,可以通过这行命令来补充署名信息: - - git commit --amend -s - -## Pull Request 检查项清单 - -当你提交 PR 或向其推送新提交时,我们的自动化系统将对你的新代码运行一些检查。我们要求你的 PR 通过这些检查,但在我们接受和合并它之前,我们还有更多的标准。我们建议你在提交代码之前在本地检查以下事项: - -- 检查代码格式问题([golangci-lint](https://github.com/golangci/golangci-lint)): 虽然我们的 CI 会在提交代码时自动运行 lint,但在本地先执行 lint 确保无误后再提交能节省你更多的精力与时间。 -- 构建/测试 代码,同上。 -- 仔细检查你的提交信息(Commit Message):详见 [提交信息规范](./development/git-workflow/commit-messages.zh.md)。 - -## Maintainer 团队 - -DevStream 由最初由 [思码逸 Merico](https://www.crunchbase.com/organization/merico) 的工程师们发起,并由 [@IronCore864](https://github.com/ironcore864) 带领。 - -目前,maintainer 们为: - -- @IronCore864 -- @daniel-hutao - -> 注意:虽然 DevStream 由 Merico 发起,以及现在的 maintainer 们都来自 Merico,但我们不希望开源社区中存在 _任何_ 专制的行为。我们将促进长期参与 member 和 reviewer 们加入 maintainer 队伍。我们欢迎来自任何公司与组织的你成为我们社区的一员。 - -你可以阅读 [贡献者成长阶梯计划](https://docs.devstream.io/en/latest/contributor_ladder.zh/) 以了解详情。 diff --git a/docs/contributor_ladder.md b/docs/contributor_ladder.md deleted file mode 100644 index bcccf5425..000000000 --- a/docs/contributor_ladder.md +++ /dev/null @@ -1,156 +0,0 @@ -# Contributor Ladder - -Hello! We are excited that you want to learn more about our project contributor ladder! This contributor ladder outlines the different contributor roles within the project, along with the responsibilities and privileges that come with them. Community members generally start at the first levels of the "ladder" and advance up it as their involvement in the project grows. Our project members are happy to help you advance along the contributor ladder. - -Each of the contributor roles below is organized into lists of three types of things. "Responsibilities" are things that contributor is expected to do. "Requirements" are qualifications a person needs to meet to be in that role, and "Privileges" are things contributors on that level are entitled to. - -## Contributor - -_Description: A Contributor contributes directly to the project and adds value to it. Contributions need not be code. Non-code contributions are equally, if not more, valued. People at the Contributor level may be new contributors, or they may only contribute occasionally._ - -- Responsibilities: - - Follow the CNCF CoC - - Follow the project contributing guide - -- Requirements (one or several of the below items): - - Report and sometimes resolve issues - - Occasionally submit PRs - - Contribute to the documentation - - Show up at meetings, takes notes - - Answer questions from other community members - - Submit feedback on issues and PRs - - Test releases and patches and submit reviews - - Run or helps run events - - Promote the project in public - - Help run the project infrastructure - -- Privileges: - - Invitations to contributor events - - Eligible to become an Organization Member - -## Organization Member - -_Description: An Organization Member is an established contributor who regularly participates in the project. Organization Members have privileges in both project repositories and elections, and as such are expected to act in the interests of the whole project._ - -An Organization Member _must meet the responsibilities and has the requirements of a Contributor_, plus: - -- Responsibilities: - - Continues to contribute regularly, as demonstrated by having at least 50 GitHub contributions per year, as demonstrated by [GitHub Insights](https://github.com/devstream-io/devstream/pulse). - -- Requirements: - - Must have successful contributions to the project, including at least one of the following: - - 5 accepted PRs, - - Reviewed 5 PRs, - - Resolved and closed 5 Issues, - - Become responsible for a key project management area, - - Or some equivalent combination or contribution - - Must have been contributing for at least 1 months - - Must be actively contributing to at least one project area - - Must have two sponsors who are also Organization Members, at least one of whom does not work for the same employer - -- Privileges: - - May be assigned Issues and Reviews - - May give commands to CI/CD automation - - Entitled to vote in elections [TODO] - - Can be added to @devstream teams - - Can recommend other contributors to become Org Members - -The process for a Contributor to become an Organization Member is as follows: - -1. The member contributor is nominated by a sponsor, by opening an issue in the appropriate repository. -2. The second sponsor from a different employer seconds the nomination in the issue. -3. The nomination is reviewed publicly in a community meeting. All members vote for the nomination. A majority is required in order to approve the nomination. -4. Publicly announce the result in the issue, and in the [contributor slack channel](https://cloud-native.slack.com/messages/devstream-contributors)。 - -## Reviewer - -_Description: A Reviewer has responsibility for specific code, documentation, test, or other project areas. They are collectively responsible, with other Reviewers, for reviewing all changes to those areas and indicating whether those changes are ready to merge. They have a track record of contribution and review in the project._ - -Reviewers are responsible for a "specific area." This can be a specific code directory, driver, chapter of the docs, test job, event, or other clearly-defined project component that is smaller than an entire repository or subproject. Most often it is one or a set of directories in one or more Git repositories. The "specific area" below refers to this area of responsibility. - -Reviewers have all the rights and responsibilities of an Organization Member, plus: - -- Responsibilities: - - Following the reviewing guide - - Reviewing most Pull Requests against their specific areas of responsibility - - Reviewing at least 20 PRs per year - - Helping other contributors become reviewers - -- Requirements: - - Experience as a Contributor for at least 1 months - - Is an Organization Member - - Has reviewed, or helped review, at least 5 Pull Requests - - Has analyzed and resolved test failures in their specific area - - Has demonstrated an in-depth knowledge of the specific area - - Commits to being responsible for that specific area - - Is supportive of new and occasional contributors and helps get useful PRs in shape to commit - -- Additional privileges: - - Has GitHub or CI/CD rights to approve pull requests in specific directories - - Can recommend and review other contributors to become Reviewers - - Is listed as Approver in the OWNERS file for certain directories - -The process of becoming a Reviewer is: - -1. The contributor is nominated by opening a PR against the appropriate repository, which adds their GitHub username to the OWNERS file for one or more directories. -2. At least two members of the team that owns that repository or main directory, who are already Approvers, approve the PR. - -## Maintainer - -_Description: Maintainers are very established contributors who are responsible for the entire project. As such, they have the ability to approve PRs against any area of the project, and are expected to participate in making decisions about the strategy and priorities of the project._ - -A Maintainer must meet the responsibilities and requirements of a Reviewer, plus: - -- Responsibilities: - - Reviewing at least 40 PRs per year, especially PRs that involve multiple parts of the project - - Mentoring new Reviewers - - Writing refactoring PRs - - Participating in CNCF maintainer activities - - Determining strategy and policy for the project - - Participating in, and leading, community meetings - -- Requirements - - Experience as a Reviewer for at least 3 months - - Demonstrates a broad knowledge of the project across multiple areas - - Is able to exercise judgement for the good of the project, independent of their employer, friends, or team - - Mentors other contributors - - Can commit to spending at least 40 hours per month working on the project - -- Additional privileges: - - Approve PRs to any area of the project - - Represent the project in public as a Maintainer - - Communicate with the CNCF on behalf of the project - - Have a vote in Maintainer decision-making meetings - -Process of becoming a maintainer: - -1. Any current Maintainer may nominate a current Reviewer to become a new Maintainer, by opening a PR against the root of the DevStream repo, adding the nominee as an Approver in the OWNERS file. -2. The nominee will add a comment to the PR testifying that they agree to all requirements of becoming a Maintainer. -3. A majority of the current Maintainers must then approve the PR. - -## Inactivity - -It is important for contributors to be and stay active to set an example and show commitment to the project. Inactivity is harmful to the project as it may lead to unexpected delays, contributor attrition, and a lost of trust in the project. - -- Inactivity is measured by: - - Periods of no contributions for longer than 2 months - - Periods of no communication for longer than 1 month -- Consequences of being inactive include: - - Involuntary removal or demotion - - Being asked to move to Emeritus status - -## Involuntary Removal or Demotion - -Involuntary removal/demotion of a contributor happens when responsibilities and requirements aren't being met. This may include repeated patterns of inactivity, extended period of inactivity, a period of failing to meet the requirements of your role, and/or a violation of the Code of Conduct. This process is important because it protects the community and its deliverables while also opens up opportunities for new contributors to step in. - -Involuntary removal or demotion is handled through a vote by a majority of the current Maintainers. - -## Stepping Down/Emeritus Process - -If and when contributors' commitment levels change, contributors can consider stepping down (moving down the contributor ladder) vs moving to emeritus status (completely stepping away from the project). - -Contact the Maintainers about changing to Emeritus status, or reducing your contributor level. - -## Contact - -For inquiries, please reach out to the @pmc chair @IronCore864. diff --git a/docs/contributor_ladder.zh.md b/docs/contributor_ladder.zh.md deleted file mode 100644 index 473aaf205..000000000 --- a/docs/contributor_ladder.zh.md +++ /dev/null @@ -1,152 +0,0 @@ -# 贡献者阶梯指南 -嗨,你好啊!听说你想要了解 DevStream 项目的贡献者阶梯(contributor ladder) 吗?这太酷了!让我们接着往下看吧! - -此贡献者阶梯指南涵盖了 DevStream 项目中不同的贡献者角色及其所对应的责任和特权等。 - -社区成员一般会从"阶梯"的第一级开始,然后在参与项目中逐步升级。 - -DevStream 项目的成员会尽可能的帮助所有的贡献者在贡献者阶梯中逐步攀登。 - -下面是分为三个维度来描述贡献者角色: -- "责任":贡献者在其级别应该做到的事情。 -- "要求":贡献者在其级别需要满足的资格。 -- "特权":贡献者在其级别被赋予的额外权限。 - -## 贡献者(Contributor) -_定义:直接为项目做出有价值的贡献。其表现形式可以不只是代码。非代码的贡献同样重要且被我们所认可!在 Contributor 级别的贡献者可能是一个新的 Contributor,或者只是偶尔提交一些贡献。_ - -- 责任: - - 遵循 [CNCF CoC](https://github.com/cncf/foundation/blob/main/code-of-conduct.md) - - 遵循 [项目贡献指南](https://github.com/devstream-io/devstream/blob/main/docs/contributing_guide.zh.md) -- 要求(以下的一项或者多项, P.S. 多多益善 💖): - - 提交 issue 或者偶尔解决一些 issue。 - - 偶尔提交一些 PR。 - - 提交文档类的贡献。 - - 参加 DevStream 社区例会或其他会议。 - - 尽可能解答其他社区成员提出的问题。 - - 提交一些 issue 或 PR 的反馈。 - - 在 releases 时做一些测试工作,如果发现问题可以提交一些补丁类代码,或者帮助 review。 - - 组织或协助组织社区活动。 - - 对 DevStream 项目做一些宣传。 - - 帮助维护项目的基础设施等。 -- 特权: - - 受邀参加 Contributor 活动。 - - 能够进一步成为组织成员(Organization Member)。 - -## 组织成员(Organization Member) -_定义:组织成员(Organization Member)首先也是贡献者(Contributor),不同的是组织成员(Organization Member)经常性地参与 DevStream 项目。 -组织成员(Organization Member)在项目资源库和选举中都拥有特权,他们对项目抱有极大的兴趣。_ - -组织成员(Organization Member)除了要遵循贡献者(Contributor)的责任,还应遵循以下责任: - -- 责任: - - 定期的做出项目贡献,例如:[Github Insights](https://github.com/devstream-io/devstream/pulse) 所示,每年至少有 50 个 Github 的贡献。 -- 要求: - - 要对项目有提交成功的贡献,至少包括以下一项: - - 5 个合并的 PR。 - - Review 5 个 PR。 - - 解决并关闭 5 个 issue。 - - 或与以上等效的贡献组合。 - - 参与贡献的时间不低于 1 个月。 - - 至少熟知 1 个项目区域。 - - 得到两名组织成员(Organization Member)推荐,并且这些推荐人不能来自于同一个雇主。 -- 特权: - - 被分配一些 issue 和 review 。 - - 执行 CI/CD 命令。 - - 投票选举权。 - - 加入 @devstream 团队。 - - 推荐其他贡献者成为组织成员(Organization Member)。 - -贡献者(Contributor)成为组织成员(Organization Member)的流程如下: - -1. 贡献者由推荐官在合适的存储库提交 issue 来提名。 -2. 来自不同出处的第二位推荐官同意该 issue 的提名。 -3. 提名将会在社区会议上公开 review。所有成员对该提名进行投票。需要多数票通过才能批准该提名。 -4. 在 issue 中公开宣布结果,且公开宣布在 Slack 的 [devstream-contributors 频道](https://cloud-native.slack.com/messages/devstream-contributors)中。 - -## 评审者(Reviewer) -_定义:评审者(Reviewer)在项目中有贡献和审查的记录,负责审查特定的代码、文档、测试或其他项目区域。评审者(Reviewer)们对该区域的代码变动的审查集体负责,判断是否可以合并该修改。他们的审查记录将会被作为贡献记录到项目中。_ - -评审者(Reviewer)负责"特定区域"。可以是特定的代码目录、驱动程序、文档章节、测试、事件或其他规模小于主库或子项目的组件。多数情况下,可能是 Git 仓库中的一个或一组目录。注:以下的"特定领域"指代的是其负责的特定项目区域。 - -此外,评审者除了拥有组织成员(Organization Member)的所有权利以外,还有其要肩负的责任。 - -- 责任: - - 遵循审查指南。 - - 审查他们负责的特定领域的大部分的 Pull Request。 - - 每年至少审查 20 个 PR。 - - 帮助其他贡献者(Contributor)成为评审者(Reviewer)。 - -- 要求: - - 担任贡献者(Contributor)至少满一个月。 - - 是一名组织成员(Organization Member)。 - - 审查或帮助审查过不低于 5 个 Pull Request。 - - 分析并解决了特定领域的测试失败问题。 - - 对某些领域有深入的理解。 - - 承诺负责特定领域。 - - 支持新的和临时的贡献者(Contributor),并帮助贡献者(Contributor)提交对社区有用的 PR 。 - -- 额外的特权: - - 拥有 Github 或 CI/CD 的权限,可以批准特定领域的 PR 。 - - 推荐或批准其他的贡献者成为评审者(Reviewer)。 - - 将会作为批准者(Approver),被列在特定领域对应文件夹下的 OWNERS 文件中。 - -成为评审者的流程: - -1. 在对应仓库打开一个 PR ,将提名者的 Github 用户名添加到一个或多个目录的 OWNERS 文件中。 -2. 至少要有两名主仓库或该特定领域的批准者(Approver)批准,才可批准此 PR 。 - -## 维护者(Maintainer) -_定义:维护者(Maintainer)是非常成熟的贡献者,负责整个项目。因此,他们有权批准针项目任何类型的 PR 。同时在有关项目战略和决策上有主导权。_ - -维护者(Maintainer)满足评审者(Reviewer)所有的责任和要求,并额外满足: - -- 责任:每年至少审查 40 个 PR ,尤其是那些跨模块的复杂的 PR 。 - - 指导新加入的评审者(Reviewer)。 - - 撰写重构的 PR 。 - - 参与 CNCF 维护者活动。 - - 确定项目的战略和政策。 - - 参与并主导社区例会。 - -- 要求: - - 担任评审者(Reviewer)至少满三个月。 - - 能够对项目的多个领域有深入了解。 - - 能够独立于雇主、朋友或团队,做出利于 DevStream 项目发展的判断。 - - 能够保证每月在 DevStream 项目工作的时间不低于 40 小时。 - -- 额外的特权: - - 批准项目中任何的 PR 。 - - 作为维护者(Maintainer)公开代表 DevStream 项目。 - - 代表 DevStream 项目与 CNCF 交流沟通。 - - 拥有维护者决策会议的投票权。 - -成为维护者(Maintainer)的流程: - -1. 任何维护者(Maintainer)都可以提名评审者(Reviewer)成为新的维护者(Maintainer),具体的提名方式是:在 DevStream 仓库的根目录打开一个 PR ,将提名者添加为 OWNERS 文件中的审批者。 -2. 提名者需要在上述打开的 PR 中留下评论,确保其承诺遵循维护者(Maintainer)的所有要求。 -3. 大多数维护者(Maintainer)持同意观点,并批准该 PR。 - -## 不活跃措施 -任何级别的贡献者们都需要保持活跃,对项目保持兴趣。不活跃有弊于项目,长期不活跃代表贡献者的流失、可能会丧失对项目的信任,导致项目延期。 - -- 衡量是否为活跃的贡献者,其标准是: - - 超过两个月没有任何贡献。 - - 超过一个月和社区无任何沟通交流。 - -- 长期不活跃会导致: - - 非自愿的撤职或降职。 - - 被要求转为"荣誉退休(Emeritus)"状态。 - -## 淘汰机制 -当贡献者的责任和能力没有满足时,会导致非自愿的移除/降级,可能是由于长时间不活跃或不满足所要求的能力又或者违反了社区的准则。对社区来说,执行淘汰机制很重要,因为它保护了社区及其可交付成果,同时也为新的贡献者提供了加入的机会。 - -非自愿的移除或降级需要多数维护者(Maintainer)的投票来处理。 - -## 降级/退出机制 -如果贡献者(Contributor)的承诺并没有兑现的话,可以考虑依据贡献阶梯降低,或退出 DevStream 项目。当然我们并不希望有此类事情发生。 - -请与维护者(Maintainer)们联系,商讨你的降级/退出事宜。 - -## 联系反馈 -如有疑问,请咨询 @PMC主席 @IronCore864. - diff --git a/docs/core-concepts/apps.md b/docs/core-concepts/apps.md deleted file mode 100644 index c623ced75..000000000 --- a/docs/core-concepts/apps.md +++ /dev/null @@ -1,277 +0,0 @@ -# Apps - -## 1 Concept - -An app in DevStream corresponds to a real-world application, and the app represents the whole software development lifecycle of that app, including source code management, code scaffolding, CI/CD (and their pipelines). - -Using "App", you can easily create these for an application. - -### 1.1 Apps - -There are situations where you need to define multiple DevOps tools for an application/microservice. For example, for a web-app typed microservice, you might need the following: - -- source code management, code repo scaffolding -- continuous integration (the installation of the DevOps tool, the creation of the CI pipeline) -- continuous deployment (the installation of the DevOps tool, the creation of the CD pipeline) - -If you are managing more than one application/microservice (chances are, you will be managing more than one application in the real world), the configuration of DevStream can be quite long, hard to read and hard to manage if you are only using "Tools". - -In order to solve this problem, DevStream provides another concept that is "App". You can easily define all DevOps tools and pipelines for an App with a couple of lines of YAML config, making the config file much easier to read and manage. - -In essence, "App" will be converted to "Tool", which you do not have to worry about at all; let DevStream handle that. - -## 1.2 pipelineTemplates - -pipelineTemplates define CI/CD pipelines, so that they can be referred to and shared by different DevStream Apps, reducing the length of the config file to the next level. - -## 2 Config - -### 2.1 App - -In the config, there is a `apps` section, which is a list, with each element having the following keys: - -- name: the name of the app, unique -- spec: application-specific information -- repo: info about the code repository -- repoTemplate: optional, same structure as "repo". If empty, DevStream will create/scaffold a repository from scratch. -- ci: optional, a list of CI pipelines, each element can have the following keys: - - type: the value can be a `template` or the name of a plugin - - templateName: optional, if type is `template`, it defines which pipelineTemplate to use - - vars: optional, variables to be passed to the template. Only works when type is `template`, apparently - - options: optional - - if type is the name of a plugin, the options are the options of that plugin - - if type is `template`, the options here will override the ones in the template. Use full path to override, for example, `options.docker.registry.type` -- cd: like `ci`, but stands for the list of CD pipelines. DevStream will execute CI first before CD - -### 2.2 pipelineTemplate - -Defined in the `pipelineTemplates` of the config, it's a list, with each element having the following keys: - -- name: unique name of the pipelineTemplate, unique -- type: corresponds to a plugin's name -- options: options for that plugin - -### 2.3 Local Variables - -DevStream has a "var" section in the config, serving as global variables that can be referred to by all Tools and Apps. - -Sometimes, however, we'd like to use the same DevOps tool with minor differences. For example, except the name of the project, everything else is different. - -In this case, we can define a pipelineTemplate with a local variable, and when referring to it, we can pass different values to it: - -```yaml hl_lines="13 15 23 30" title="pipelineTemplate and local variables" -apps: -- name: my-app - spec: - language: java - framework: springboot - repo: - url: https://github.com/testUser/testApp.git - branch: main - ci: - - type: github-actions # use a plugin directly without defining pipelineTemplates - cd: - - type: template # use a pipelineTemplate - templateName: my-cd-template # corresponds to the name of the pipelineTemplate - vars: - appName: my-app # a local variable passed to the pipelineTemplate - -pipelineTemplates: -cd: -- name: my-cd-template - type: argocdapp - options: - app: - name: [[ appName ]] # a local variable, passed to when referring to the template - namespace: argocd - destination: - server: https://kubernetes.default.svc - namespace: default - source: - valuefile: values.yaml - path: charts/[[ appName ]] -``` - -## 3 A Demo of the Whole Config - -A whole config for an App: - -```yaml -apps: -- name: testApp # name of the app - spec: # app-specific info - language: java # programming language of the app - framework: springboot # framework of the app - repo: # repository-related info for the app - url: https://github.com/testUser/testApp.git - branch: main - repoTemplate: # optional, used for repository bootstrapping/scaffolding. If not empty, a repo will be created with scaffolding code - url: https://github.com/devstream-io/dtm-repo-scaffolding-java-springboot.git - vars: - imageRepoOwner: repoOwner # variables used for repoTemplate - ci: # CI pipelines, here we use github-actions - - type: github-actions -- name: testApp2 - spec: - language: go - framework: gin - repo: # repository-related info for the app - owner: test_user - type: github - branch: main - repoTemplate: # optional, used for repository bootstrapping/scaffolding. If not empty, a repo will be created with scaffolding code - org: devstream-io - name: dtm-repo-scaffolding-java-springboot - type: github - ci: # CI pipelines, here we use github-actions - - type: github-actions - options: - imageRepo: - owner: repoOwner # override the plugin's options. Must use full YAML path. - cd: # CD pipelines, here we use argocd - - type: argocdapp -``` - -If we apply this config, DevStream will create two repositories in GitHub, with scaffolding code provided by DevStream [SpringBoot](https://github.com/devstream-io/dtm-repo-scaffolding-java-springboot.git). App `testApp` will trigger CI in GitHub Actions upon each commit, and App `testApp2` will trigger build/push in GitHub Actions upon commit, and deploy using Argo CD. - -### repo/repoTemplate Config - -The `repo` and `repoTemplate` in the Config represent a code repository. You can define it with a single URL or a few key/values: - -!!! note "two ways to configure code repo" - - === "using a single URL" - - ```yaml title="" - repo: - url: git@gitlab.example.com:root/myapps.git # repo URL, supports both git and https - apiURL: https://gitlab.example.com # not mandatory, if using gitlab and the URL protocol is git, here can be the GitLab API URL - branch: "" # not mandatory, defaults to main for GitHub and master for GitLab - ``` - - This example shows that we use GitLab `git@gitlab.example.com:root/myapps.git` for code clone, and DevStream uses `https://gitlab.example.com` to access GitLab API. Default branch is master. - - === "using detailed key/value config for the repo" - - ```yaml title="" - repo: - org: "" # only mandatory for GitHub organization - owner:"test_user" # if the repo belongs to a person. If the repo belongs to an org, use the org above. - name: "" # optional, defaults to the name of the app - baseURL: https://gitlab.example.com # optional. If GitLab, here we can put the GitLab domain. - branch: master # not mandatory, defaults to main for GitHub and master for GitLab - type: gitlab # mandatory, either gitlab or github - ``` - - This example shows that we use GitLab `https://gitlab.example.com`, repo name is the app name, belongs to owner `test_user`, with the default branch being "master". - -### CI Config - -The `CI` section in the config supports 4 types at the moment: `github-actions`/`gitlab-ci`/`jenkins-pipeline`/`template`. - -`template` means to use a pipelineTemplate; and the other three types correspond to GitHub Actions, GitLab CI, and Jenkins, respectively. - -Detailed config: - -```yaml - ci: - - type: jenkins-pipieline # type of the CI - options: # options for CI. If empty, CI will only run unit test. - jenkins: # config for jenkins - url: jenkins.exmaple.com # jenkins URL - user: admin # jenkins user - imageRepo: # docker image repo to be pushed to. If set, Ci will push the image after build. - url: http://harbor.example.com # image repo URL. Defaults to dockerhub. - owner: admin # image repo owner - dingTalk: # dingtalk notification settings. If set, CI result will be pushed to dingtalk. - name: dingTalk - webhook: https://oapi.dingtalk.com/robot/send?access_token=changemeByConfig # callback URL for dingtalk. - securityType: SECRET # use secret to encrypt dingtalk message - securityValue: SECRETDATA # dingtalk secret encryption data - sonarqube: # sonarqube config. If set, CI will test and execute sonarqube scan. - url: http://sonar.example.com # sonarqube URL - token: YOUR_SONAR_TOKEN # soanrqube token - name: sonar_test -``` - -The config above will trigger unit test and sonarqube code scan upon commit, then a Docker image will be built and pushed to dockerhub, and the result of the CI will be pushed to dingtalk. - -If the same pipeline is required for multiple apps, the config can be long and redundant. So, DevStream provides the `template` type to share similar settings for diffrent Apps. Detailed example: - -```yaml -apps: -- name: javaProject1 - spec: - language: java - framework: springboot - repo: - owner: testUser - type: github - repoTemplate: - url: https://github.com/devstream-io/dtm-repo-scaffolding-java-springboot.git - ci: - - type: template # use a pipelineTemplate - templateName: ci-pipeline # name of the pipelineTemplate - vars: - dingdingAccessToken: tokenForProject1 # variables for the pipelineTemplate - dingdingSecretValue: secretValProject1 -- name: javaProject2 - spec: - language: java - framework: springboot - repo: - owner: testUser - type: github - repoTemplate: - url: https://github.com/devstream-io/dtm-repo-scaffolding-java-springboot.git - ci: - - type: template # use a pipelineTemplate - templateName: ci-pipeline # name of the pipelineTemplate - vars: - dingdingAccessToken: tokenForProject2 # variables for the pipelineTemplate - dingdingSecretValue: secretValProject2 - -pipelineTemplates: # CI/CD pipeline templates -- name: ci-pipeline # name of the pipelineTemplate - type: jenkins-pipeline # type, supports jenkins-pipeline,github-actions and gitlab-ci at the moment - options: # options, same as CI options - jenkins: - url: jenkins.exmaple.com - user: admin - imageRepo: - url: http://harbor.example.com - owner: admin - dingTalk: - name: dingTalk - webhook: https://oapi.dingtalk.com/robot/send?access_token=[[ dingdingAccessToken ]] # local variable, passed to when referring to this template - securityType: SECRET - securityValue: [[ dingdingSecretValue ]] # local variable, passed to when referring to this template - sonarqube: - url: http://sonar.example.com - token: sonar_token - name: sonar_test - -``` - -If we apply the above config, we will create two Jenkins pipelines for two apps, with the only difference being that the dingtalk notification will be sent to different groups. - -### CD Config - -At the moment, CD only supports `argocdapp`. Argo CD itself can be deployed with a Tool, and `argocdapp` is responsible for deploying the app in a Kubernetes cluster. - -Detailed config example: - -```yaml -cd: -- type: argocdapp - options: - app: - name: hello # argocd app name - namespace: argocd # argocd namespace - destination: - server: https://kubernetes.default.svc # Kubernetes cluster - namespace: default # which namespace to deploy the app - source: - valuefile: values.yaml # helm values file - path: charts/go-hello-http # helm chart path -``` diff --git a/docs/core-concepts/apps.zh.md b/docs/core-concepts/apps.zh.md deleted file mode 100644 index 9d417f1ae..000000000 --- a/docs/core-concepts/apps.zh.md +++ /dev/null @@ -1,270 +0,0 @@ -# 应用(Apps) - -## 1 概念 - -应用在 DevStream 中表示对一个服务的生命周期配置,包括对服务的项目脚手架,CI 流程以及 CD 流程的配置,在 devstream 中使用应用可以使用少数几行配置就构建出服务的整条 CI/CD 流水线配置。 - -### 1.1 应用(Apps) - -有时候,你需要为一个应用/微服务定义多个 工具。例如,对于一个 web 应用程序,你可能需要指定以下工具: - -- 仓库脚手架 -- 持续集成 -- 持续部署 - -如果你有多个应用需要管理,你需要在配置中创建多个 工具,这可能会很繁琐,而且配置文件难以阅读。 - -为了更容易地管理多个应用/微服务,DevStream 提供了另一层抽象,称为 应用。你可以在一个应用中定义所有内容(例如,上面提到的仓库脚手架、持续集成、持续部署等),只需要几行配置,就可以使配置文件更容易阅读和管理。 - -在底层,DevStream 仍然会将你的 应用 配置转换为 工具 的定义,但你不需要关心它。 - -## 1.2 流水线模板(pipelineTemplates) - -一个 DevStream 应用 可以引用一个或多个 流水线模板,这些模板主要是 CI/CD 定义,这样 应用 的定义可以更短,以在多个微服务之间共享公共的 CI/CD 流水线。 - -## 2 配置方式 - -### 2.1 应用 - -应用在配置中由 `apps` 来定义,它是一个数组,每个元素的字段如下: - -- name: 应用的名称,必须是唯一的 -- spec: 配置应用特定的信息 -- repo: 代码仓库信息 -- repoTemplate: 可选。结构和 repo 一致。若非空,DevStream 将使用项目脚手架来创建仓库。 -- ci: 可选,一个 CI 流水线的数组,每个元素包含以下字段: - - type: 类型。值为 `template` 或某个插件的名称。 - - templateName: 可选。当 type 为 `template` 时,指定使用的模板名称。 - - vars: 可选。传入模板的变量,仅当 type 为 `template` 时有效。 - - options: 可选。 - - 当 type 是某个插件名时,就是该插件的 Options - - 当 type 为 `template` 时,覆盖模板中的 Options。你需要写清想要覆盖的选项的完整路径,例如 `options.docker.registry.type`。 -- cd: 与 `ci` 类似,是 CD 流水线列表。dtm 会先执行 ci 流水线,再执行 cd 流水线。 - -### 2.2 流水线模板 - -流水线模板在配置中由 `pipelineTemplates` 来定义,它是一个数组,每个元素的字段如下: - -- name: 流水线模板的名称,必须是唯一的 -- type: 对应插件的名称 -- options: 插件的 Options (可能和原始的插件 Options 不同) - -### 2.3 局部变量 - -DevStream 已经有了全局变量,所有的 工具 和 应用 都可以直接引用。 - -但有时,我们想多次引用某个 DevOps 工具,但他们只有一些细微的差别。例如,除了项目名称不同,其他的都相同。 - -在这种情况下,我们可以定义一个流水线模板,在其中定义一个局部变量,然后在 应用 引用该流水线模板时,传入不同的值。 - -```yaml hl_lines="13 15 23 30" title="流水线模板的使用与局部变量" -apps: -- name: my-app - spec: - language: java - framework: springboot - repo: - url: https://github.com/testUser/testApp.git - branch: main - ci: - - type: github-actions # 不定义 pipelineTemplates,直接使用插件 - cd: - - type: template # 使用流水线模板 - templateName: my-cd-template # 对应 pipelineTemplates 中的 name - vars: - appName: my-app # 传入模板的变量 - -pipelineTemplates: -cd: -- name: my-cd-template - type: argocdapp - options: - app: - name: [[ appName ]] # 定义一个局部变量,在引用使用模板时传入 - namespace: argocd # argocd 的命名空间 - destination: - server: https://kubernetes.default.svc # 部署的 kubernetes 服务地址 - namespace: default # 应用要部署的命名空间 - source: - valuefile: values.yaml # 项目中的 helm 变量文件名 - path: charts/[[ appName ]] # 项目中的 helm 配置路径 -``` - -## 3 完整配置示例 - -应用 的真实示例配置如下: - -```yaml -apps: -- name: testApp # 应用名称 - spec: # 该配置项用于配置应用特定的信息 - language: java #应用所使用的编程语言 - framework: springboot #应用所使用的编程框架 - repo: # 该配置项用于应用的代码仓库信息 - url: https://github.com/testUser/testApp.git - branch: main - repoTemplate: # 可选,用于创建应用的脚手架。不为空时,会使用该脚手架创建应用的代码仓库 - url: https://github.com/devstream-io/dtm-repo-scaffolding-java-springboot.git - vars: - imageRepoOwner: repoOwner # 用于渲染脚手架模版的变量 - ci: # 配置应用的 ci 流程,如下即为使用 github-actions 运行应用 ci 流程 - - type: github-actions -- name: testApp2 - spec: - language: go - framework: gin - repo: # 该配置项用于应用的代码仓库信息 - owner: test_user - type: github - branch: main - repoTemplate: # 该配置用于创建应用的脚手架 - org: devstream-io - name: dtm-repo-scaffolding-java-springboot - type: github - ci: # 配置应用的 ci 流程,如下即为使用 github-actions 运行应用 ci 流程 - - type: github-actions - options: - imageRepo: - owner: repoOwner # 覆盖 插件/模板 原有的 Options,YAML 路径要写完全 - cd: # 配置应用的 cd,如果为使用 argocdapp 运行应用的 cd 流程 - - type: argocdapp -``` - -使用该配置就会在 `gitlab` 仓库中创建两个应用,项目的脚手架均为 DevStream 官方提供的 [SpringBoot](https://github.com/devstream-io/dtm-repo-scaffolding-java-springboot.git) 项目。应用 `testApp` 会在每次代码提交后使用 `github-actions` 运行测试。应用 `testApp2` 会在每次代码提交后使用 `github-actions` 运行测试并构建镜像推送到 `repoOwner` 的镜像仓库中,最后使用 Argo CD 将应用部署到集群中。 - -### repo/repoTemplate 配置 - -应用配置中的 `repo` 和 `repoTemplate` 均表示一个代码仓库,支持通过 url 或 详细字段来配置: - -!!! note "两种配置代码仓库的方式" - - === "使用 url 来配置" - - ```yaml title="" - repo: - url: git@gitlab.example.com:root/myapps.git # url 表示仓库的地址,支持 git 地址和 http 地址 - apiURL: https://gitlab.example.com # 非必填,如果使用 gitlab 而且 url 使用的是 git 地址,则需要配置该字段用于表示 gitlab 的 api 请求地址 - branch: "" # 非必填,github 默认为 main 分支,gitlab 默认为 master 分支 - ``` - - 该配置表示使用的仓库为 `gitlab`, 要使用 `git@gitlab.example.com:root/myapps.git` 来克隆代码,devstream 会使用使用 `https://gitlab.example.com` 和 gitlab 进行交互,仓库的主分支为 `master` 分支。 - - === "使用仓库的详细字段来配置" - - ```yaml title="" - repo: - org: "" # 非必填,仓库的拥有组织名称,如果使用 github 的组织则需要填写此字段 - owner:"test_user" # 如果仓库是非组织的,则需要填写该字段表示仓库拥有者 - name: "" # 非必填,默认为应用名 - baseURL: https://gitlab.example.com # 非必填,如果使用 gitlab 则需要填写该字段表示 gitlab 的域名 - branch: master #非必填,github 默认为 main 分支,gitlab 默认为 master 分支 - type: gitlab #必填,表示该仓库的类型,目前支持 gitlab/github - ``` - - 该配置表示代码仓库使用的是 `gitlab` 且其地址为 `https://gitlab.example.com`,仓库名称为应用名,仓库的所有者为 `test_user`,主分支为 `master` 分支。 - -### ci 配置 - -应用配置中的 `ci` 目前支持 `github-actions`/`gitlab-ci`/`jenkins-pipeline`/`template` 4 种类型。 - -其中 `template` 类型表示使用 流水线模板 来运行流水线,前 3 种类型分别对应了 `github` 中的 actions 流水线,`gitlab` 中 ci 流水线和 `jenkins` 中的 pipeline,它们的具体配置如下: - -```yaml - ci: - - type: jenkins-pipieline # 表明当前 ci 的类型 - options: # ci 的具体配置项,如果该配置项为空,则 ci 只会运行单元测试然后结束 - jenkins: # 该配置项之用于 jenkins,表示 jenkins 的一些配置信息 - url: jenkins.exmaple.com # jenkins 的地址 - user: admin # jenkins 用户 - imageRepo: # 需要推送的镜像仓库信息,如果设置了该字段,则 ci 流程会在测试成功后构建镜像推送到该镜像仓库 - url: http://harbor.example.com # 镜像仓库地址,若为空则默认为 dockerhub - owner: admin # 镜像仓库拥有者名称 - dingTalk: # 钉钉通知的配置信息,如果设置了该字段,则 ci 流程中会将最后的构建结构通过钉钉发送通知 - name: dingTalk - webhook: https://oapi.dingtalk.com/robot/send?access_token=changemeByConfig # 钉钉的回调地址 - securityType: SECRET # 使用 secret 模式来加密钉钉的信息 - securityValue: SECRETDATA # 钉钉的 secret 加密字符串 - sonarqube: # sonarqube 的配置信息,如果设置了该字段,则 ci 流程会和测试并行执行 sonarqube 的代码扫描 - url: http://sonar.example.com # sonarqube 的地址 - token: YOUR_SONAR_TOKEN # soanrqube 的认证 token - name: sonar_test -``` - -上述的配置即会在应用更新推送到代码仓库后先并行执行单元测试和代码扫描,然后构建镜像推送到代码仓库,流程都成功后发送通知消息到指定的钉钉群中。如果所有应用都要配置一遍该类型 `ci`, 那配置就会变得比较繁琐,所以 devstream 还提供了 `template` 用于在多个应用间共享 `ci` 的流程配置,具体如下所示: - -```yaml -apps: -- name: javaProject1 - spec: - language: java - framework: springboot - repo: - owner: testUser - type: github - repoTemplate: - url: https://github.com/devstream-io/dtm-repo-scaffolding-java-springboot.git - ci: - - type: template # 表示该 ci 流程使用模版 - templateName: ci-pipeline # ci 使用的模版名称 - vars: - dingdingAccessToken: tokenForProject1 #用于渲染 ci 模版的变量 - dingdingSecretValue: secretValProject1 -- name: javaProject2 - spec: - language: java - framework: springboot - repo: - owner: testUser - type: github - repoTemplate: - url: https://github.com/devstream-io/dtm-repo-scaffolding-java-springboot.git - ci: - - type: template # 表示该 ci 流程使用模版 - templateName: ci-pipeline # ci 使用的模版名称 - vars: - dingdingAccessToken: tokenForProject2 # 设置传入模版 "ci-pipeline" 的变量 - dingdingSecretValue: secretValProject2 - -pipelineTemplates: # 即配置的 ci/cd 模版 -- name: ci-pipeline # 模版名称 - type: jenkins-pipeline #模版类型,支持 jenkins-pipeline,github-actions 和 gitlab-ci - options: # options 和 ci 的 options 完全一致 - jenkins: - url: jenkins.exmaple.com - user: admin - imageRepo: - url: http://harbor.example.com - owner: admin - dingTalk: - name: dingTalk - webhook: https://oapi.dingtalk.com/robot/send?access_token=[[ dingdingAccessToken ]] # 用于被 app 渲染的模版,这样就可以实现不同应用使用同一个模版发送通知到不同的钉钉群 - securityType: SECRET - securityValue: [[ dingdingSecretValue ]] # 局部变量,应用 在引用此模板时通过 vars 来传入 - sonarqube: - url: http://sonar.example.com - token: sonar_token - name: sonar_test - -``` - -使用以上的配置,就会创建两个和上述流程一致的 `jenkins` 流水线,不同之处只在于两个应用通知的钉钉群不一样。 - - -### cd 配置 - -应用的 cd 配置目前只支持 `argocdapp`,可以使用 `argocd` 来将应用部署在集群中,具体配置如下: - -```yaml -cd: -- type: argocdapp - options: - app: - name: hello # argocd 应用名称 - namespace: argocd # argocd 的命名空间 - destination: - server: https://kubernetes.default.svc # 部署的 Kubernetes 服务地址 - namespace: default # 应用要部署的命名空间 - source: - valuefile: values.yaml # 项目中的 helm 变量文件名 - path: charts/go-hello-http # 项目中的 helm 配置路径 -``` diff --git a/docs/core-concepts/config.md b/docs/core-concepts/config.md deleted file mode 100644 index 4deec7c64..000000000 --- a/docs/core-concepts/config.md +++ /dev/null @@ -1,167 +0,0 @@ -# Config - -DevStream uses YAML files to define your DevOps platform. - -## 1 Sections Overview - -As aforementioned in the overview, there are several sections in the config: - -- `config` -- `tools` -- `apps` -- `pipelineTemplates` -- `vars` - -Among which, `config` is mandatory, and you should have at least either `tools` or `apps`. Others are optional. - -## 2 Config File V.S. Config Folder - -DevStream supports both: - -- a single config file: put all sections of the config into one YAML file -- a directory: put multiple files in one directory, as long as the file names end with `.yaml` or `.yml` - -When you run the `init` (or other commands), add `-f` or `--config-file`. - -Examples: - -- single file: `dtm init -f config.yaml` -- directory: `dtm init -f dirname` - -## 3 Sections Detail - -### 3.1 The Main Config - -DevStream's own config, the `config` section, contains where to store the state. See [here](./state.md) for more details. - - -### 3.2 Tools Config - -The `tools` section defines DevOps tools. Read [this](./tools.md) for all the detail. - -### 3.3 Apps/pipelineTemplates Config - -The `apps` section defines Apps and the `pipelineTemplates` defines templates for pipelines. See [here](./apps.md) for more detail. - -### 3.4 Variables - -DevStream supports variables. Define key/values in the `vars` section and refer to it in the `tools`, `apps` and `pipelineTemplates` sections. - -Use double square brackets when referring to a variable: `[[ varName ]]`. - -example: - -```yaml -vars: - githubUsername: daniel-hutao # define a variable - repoName: dtm-test-go - defaultBranch: main - -tools: -- name: repo-scaffolding - instanceID: default - options: - destinationRepo: - owner: [[ githubUsername ]] # refer to the pre-defined variable - name: [[ repoName ]] - branch: [[ defaultBranch ]] - scmType: github - # ... -``` - -## 4 Expanded Features - -### 4.1 Environment Variables - -Similar to "variables", you can use `[[env "env_key"]]` to refer to environment variables. - -### 4.2 Output - -#### Introduction - -In DevStream's configuration file, we can use Output from one Tool as the options values for another Tool. - -For example, if Tool A has an output, we can use its value as Tool B's options. - -Notes: - -- At the moment, B using A's output doesn't mean B "depends on" A. -- If B needs to "depend on" A, i.e., we want to make sure A runs first before B runs, we still need to use the `dependsOn` keyword (see the previous section "[Core Concepts](overview.md)" for more details.) - -#### Syntax - -To use the output, follow this format: - -``` -${{ TOOL_NAME.TOOL_INSTANCE_ID.outputs.OUTPUT_KEY }} -``` - -For example, given config: - -```yaml -tools: -- name: trello - instanceID: default - options: - board: - name: golang-demo-board - apikey: xxx - token: xxx - scm: - owner: IronCore864 - repo: golang-demo - scmType: github - ``` - -- `TOOL_NAME` is "trello" -- `TOOL_INSTANCE_ID` is "default" - -If the "trello" tool/plugin has an output key name "boardId", then we can use its value by the following syntax: - -``` -${{ trello.default.outputs.boardId }} -``` - -#### Real-World Usage Example - -Config: - -```yaml hl_lines="2 3 20 31" -tools: -- name: repo-scaffolding - instanceID: golang-github - options: - destinationRepo: - owner: IronCore864 - name: golang-demo - branch: main - scmType: github - vars: - imageRepo: "ironcore864/golang-demo" - sourceRepo: - org: devstream-io - name: dtm-scaffolding-golang - scmType: github -- name: helm-installer - instanceID: argocd -- name: argocdapp - instanceID: default - dependsOn: [ "helm-installer.argocd", "repo-scaffolding.golang-github" ] - options: - app: - name: golang-demo - namespace: argocd - destination: - server: https://kubernetes.default.svc - namespace: default - source: - valuefile: values.yaml - path: helm/golang-demo - repoURL: ${{ repo-scaffolding.golang-github.outputs.repoURL }} # pay attention here -``` - -In this example: - -- The "default" instance of tool `argocdapp` depends on the "golang-github" instance of tool `repo-scaffolding` -- The "default" instance of tool `argocdapp` has a user option "options.source.repoURL", which uses the "golang-github" instance of tool `repo-scaffolding`'s output "repoURL" (`${{ repo-scaffolding.golang-github.outputs.repoURL }}`) - diff --git a/docs/core-concepts/config.zh.md b/docs/core-concepts/config.zh.md deleted file mode 100644 index 232217718..000000000 --- a/docs/core-concepts/config.zh.md +++ /dev/null @@ -1,164 +0,0 @@ -# 配置(Config) - -DevStream使用 YAML 文件来声明 DevOps 工具链的配置。 - -## 1 配置内容概览 - -正如概述中所提到的,配置包含了以下几个部分: - -- `config` -- `tools` -- `apps` -- `pipelineTemplates` -- `vars` - -其中,`config` 必选,`tools` 和 `apps` 至少有一个不为空,其余部分可选。 - -## 2 组织方式 - -DevStream 支持两种组织配置的方式: - -- 单文件:你可以把这些部分写到一个 YAML 文件中 -- 目录:也可以把它们分开放到同一个文件夹下的多个 YAML 文件中,只要这些文件的名字以 `.yaml` 或 `.yml` 结尾,且内容拼接后包含了 DevStream 所需的各个部分即可。 - -然后在执行 `init` 等命令时,加上 `-f` 或 `--config-file` 参数指定配置文件/目录的路径。 - -如: - -- 单文件:`dtm init -f config.yaml` -- 目录:`dtm init -f dirname` - -## 3 配置内容详解 - -### 3.1 主配置 - -指 DevStream 本身的配置,即 `config` 部分,比如状态存储的方式等。详见 [这里](./state.zh.md) - -### 3.2 工具的配置 - -`tools` 部分声明了工具链中的工具,详见 [这里](./tools.zh.md) - -### 3.2 应用与流水线模板的配置 - -`apps` 部分声明了 应用 的配置,`pipelineTemplates` 声明了 流水线模板,详见 [这里](./apps.zh.md) - -### 3.3 变量 - -DevStream 提供了变量语法。使用 `vars` 用来定义变量的值,而后可以在 `tools`、`apps`、`pipelineTemplates` 中使用,语法是 `[[ varName ]]`。 - -示例: - -```yaml -vars: - githubUsername: daniel-hutao # 定义变量 - repoName: dtm-test-go - defaultBranch: main - -tools: -- name: repo-scaffolding - instanceID: default - options: - destinationRepo: - owner: [[ githubUsername ]] # 使用变量 - name: [[ repoName ]] - branch: [[ defaultBranch ]] - scmType: github - # <后面略...> -``` - -## 4 拓展语法 - -### 4.1 环境变量 - -类似于"变量",你可以使用 `[[env "env_key"]]` 的方式来引用环境变量。 - -### 4.2 输出(Output) - -#### 介绍 - -在 DevStream 的配置文件中,我们在配置 工具 的 Options 时,可以使用其他 工具 的 输出 来填充。 - -例如,如果 工具 A 有一个输出,我们可以将这个输出值作为 工具 B 的 Options。 - -注意: - -- 当前,若 B 使用了 A 的输出,并不意味着 B "依赖于" A -- 如果 B 确实需要 "依赖于" A,即,我们想要保证在 B 运行之前行运行 A,我们仍然需要使用 `dependsOn` 关键字(详见上一节 "[核心概念](overview.zh.md)")。 - -#### 语法 - -我们可以通过以下格式来使用插件输出: - -``` -${{ TOOL_NAME.TOOL_INSTANCE_ID.outputs.OUTPUT_KEY }} -``` - -例如,对于下面给定的配置: - -```yaml -tools: -- name: trello - instanceID: default - options: - board: - name: golang-demo-board - apikey: xxx - token: xxx - scm: - owner: IronCore864 - repo: golang-demo - scmType: github -``` - -- `TOOL_NAME` 是 "trello" -- `TOOL_INSTANCE_ID` 是 "default" - -如果 "trello" 这个 工具 有一个键为 "boardId" 的输出项,那么我们就能通过以下语法来引用对应的输出的值: - -``` -${{ trello.default.outputs.boardId }} -``` - -#### 例子——真实使用场景 - -配置如下: - -```yaml hl_lines="2 3 20 31" -tools: -- name: repo-scaffolding - instanceID: golang-github - options: - destinationRepo: - owner: IronCore864 - name: golang-demo - branch: main - scmType: github - vars: - imageRepo: "ironcore864/golang-demo" - sourceRepo: - org: devstream-io - name: dtm-scaffolding-golang - scmType: github -- name: helm-installer - instanceID: argocd -- name: argocdapp - instanceID: default - dependsOn: [ "helm-installer.argocd", "repo-scaffolding.golang-github" ] - options: - app: - name: golang-demo - namespace: argocd - destination: - server: https://kubernetes.default.svc - namespace: default - source: - valuefile: values.yaml - path: helm/golang-demo - repoURL: ${{ repo-scaffolding.golang-github.outputs.repoURL }} -``` - -在这个例子中, - -- `argocdapp` 的 "default" 实例依赖于 `repo-scaffolding` 的 "golang-github" 实例 -- `argocdapp` 的 "default" 实例中有一个 options 是 "options.source.repoURL",它引用了 `repo-scaffolding` 的 "golang-github" 实例的 "repoURL" 输出(`${{ repo-scaffolding.golang-github.outputs.repoURL }}`)。 - diff --git a/docs/core-concepts/overview.md b/docs/core-concepts/overview.md deleted file mode 100644 index d2036b004..000000000 --- a/docs/core-concepts/overview.md +++ /dev/null @@ -1,52 +0,0 @@ -# Overview - -## 1 Architecture - -![](../images/architecture-overview.png) - -The diagram above shows how data flows through DevStream modules. - -In essence: - -- The core of DevStream (plugin engine) acts like a state machine, which calculates actions based on the configuration and state. -- Then DevStream core calls "plugins" to accomplish the CRUD actions of specific DevOps tools or integrations. - ---- - -## 2 Plugin - -Plugin is a critical concept of DevStream. - -As shown above, DevStream uses a core-plugin architecture where the core acts mainly as a state machine and engine. The core/engine in turn drives the plugins, which are responsible for creating/reading/updating/deleting/integrating DevOps tools. - -Plugins are automatically downloaded and managed by dtm according to the config file. - -Developers and contributors can write their own plugins to extend the capabilities of DevStream. See [creating a plugin](../development/dev/creating-a-plugin.md) for more detail. - ---- - -## 3 State - -The _State_ records the current status of your DevOps toolchain and platform defined and created by DevStream. - -The state contains the configuration of all the pieces and their corresponding status so that the DevStream core can rely on it to calculate the required actions to reach the state defined in the config. - ---- - -## 4 Config - -DevStream defines desired status of your DevOps platform in config files. - -The config can be a single YAML file, as well as a bunch of YAML files under the same directory. The config contains the following sections: - -- `config`: basic configuration of DevStream, at the moment mainly state-related settings. Read more [here](./state.md). -- `vars`: variable definitions. Key/value pairs, which can be referred to in the tools/apps/pipelineTemplates sections. -- `tools`: a list of DevStream _Tools_, each containing its name, instanceID (unique identifier), and options. Read more [here](./tools.md). -- `apps`: a list of _Apps_, another DevStream concept, each corresponding to a microservice. Read more [here](./apps.md). -- `pipelineTemplates`: a list of templates which can be referred to by DevStream _Apps_. Read more [here](./apps.md). - ---- - -## 5 Workflow - -![config state resource-status workflow](../images/config_state_resource.png) diff --git a/docs/core-concepts/overview.zh.md b/docs/core-concepts/overview.zh.md deleted file mode 100644 index db31de669..000000000 --- a/docs/core-concepts/overview.zh.md +++ /dev/null @@ -1,50 +0,0 @@ -# 概览 - -## 1 构架 - -![](../images/architecture-overview.png) - -上面的图展示了数据如何在 dtm 的各个模块中流动。 - -本质上: - -- DevStream 的核心(插件引擎)就像一个状态机,它根据配置和状态计算出需要执行的操作。 -- 接着 DevStream 核心调用“插件”来完成特定 DevOps 工具或它们之间的集成的 CRUD 操作。 - ---- - -## 2 插件(Plugin) - -插件 是 DevStream 的核心,由 dtm 负责下载、管理和调用。 - -每个插件拥有一定的功能,涵盖了 DevOps 工具的安装、配置和集成,用户通过自由组合插件来构建自己的 DevOps 工具链。 - -开发者可以通过编写插件来扩展 DevStream,详见 [创建插件](../development/dev/creating-a-plugin.zh.md)。 - ---- - -## 3 配置(Config) - -DevStream 在配置文件中定义了 DevOps 平台的期望状态(如插件列表、DevStream 自身的配置等)。 - -配置文件可以是单个 YAML 文件,也可以是同个目录下的多个 YAML 文件,拥有以下几个部分: - -- `config`: DevStream 的基本配置,目前主要是与 状态 相关的设置。更多信息请参考 [这里](./state.zh.md)。 -- `vars`: 变量的定义。键值对的形式,可以在 tools/apps/pipelineTemplates 部分中引用。 -- `tools`: DevStream 工具 的列表,每个工具包含插件名称、实例 ID(唯一标识符)和 Options。更多信息请参考 [这里](./tools.zh.md)。 -- `apps`: DevStream 应用 的列表,每个应用对应一个微服务。更多信息请参考 [这里](./apps.zh.md)。 -- `pipelineTemplates`: 一个流水线模板的列表,可以被 DevStream 应用 引用。更多信息请参考 [这里](./apps.zh.md)。 - ---- - -## 4 状态(State) - -状态 记录了 DevStream 定义和创建的 DevOps 工具链和平台的当前状态。 - -状态 包含了所有组件的配置和它们对应的状态,这样 DevStream 核心模块就可以依靠它计算出,达到配置中定义的状态所需要的操作。 - ---- - -## 5 工作流 - -![配置-状态-资源状态 工作流](../images/config_state_resource.png) diff --git a/docs/core-concepts/state.md b/docs/core-concepts/state.md deleted file mode 100644 index e0cee5186..000000000 --- a/docs/core-concepts/state.md +++ /dev/null @@ -1,50 +0,0 @@ -# State - -## 1 Concept - -State records the current status of the DevOps platform defined, created and managed by DevStream. DevStream relies on the State (and config, for that matter) to calculate required actions to ensure your DevOps platform is the same as defined in the config. - -A `backend` is where to store the state, which we can configure in the config. At the moment, the following types of backends are supported: - -- local -- k8s -- s3 - -## 2 How to Config the State - -In the `config.state` section of the config, we can define where and how to store DevStream state. - -### 2.1 Local File - -```yaml -config: - state: - backend: local - options: - stateFile: devstream.state # optional, defaults to "devstream.state" -``` - -### 2.2 Kubernetes - -```yaml -config: - state: - backend: k8s - options: - namespace: devstream # optional, defaults to "devstream", create if not exist - configmap: state # optional, defaults to "state", create if not exist -``` - -### 2.3 S3 - -```yaml -config: - state: - backend: s3 - options: - bucket: devstream-remote-state - region: ap-southeast-1 - key: devstream.state -``` - -_Note: `options` `bucket`、`region` and `key` under the options are mandatory keys for s3 backend._ diff --git a/docs/core-concepts/state.zh.md b/docs/core-concepts/state.zh.md deleted file mode 100644 index fb55163ac..000000000 --- a/docs/core-concepts/state.zh.md +++ /dev/null @@ -1,52 +0,0 @@ -# 状态(State) - -## 1 概念 - -状态记录了 DevStream 定义和创建的 DevOps 工具链和平台的当前状态。 - -状态包含了所有组件的配置和它们对应的状态,这样 DevStream 核心模块就可以依靠它计算出,达到配置中定义的状态所需要的操作。 - -我们可以在配置中指定使用哪种 `backend` 来存储状态,目前支持的 `backend` 有: - -- local -- k8s -- s3 - -## 2 配置方式 - -配置中的 `config.state` 部分指定了如何存储 DevStream 状态。 - -### 2.1 本地文件 - -```yaml -config: - state: - backend: local - options: - stateFile: devstream.state # 可选,默认为 devstream.state -``` - -### 2.2 Kubernetes - -```yaml -config: - state: - backend: k8s - options: - namespace: devstream # 可选, 默认是 "devstream", 不存在则自动创建 - configmap: state # 可选, 默认是 "state", 不存在则自动创建 -``` - -### 2.3 S3 - -```yaml -config: - state: - backend: s3 - options: - bucket: devstream-remote-state - region: ap-southeast-1 - key: devstream.state -``` - -_注意:`options` 下的 `bucket`、`region` 和 `key` 是 s3 后端的必填字段。_ diff --git a/docs/core-concepts/tools.md b/docs/core-concepts/tools.md deleted file mode 100644 index de8cde059..000000000 --- a/docs/core-concepts/tools.md +++ /dev/null @@ -1,64 +0,0 @@ -# Tools - -## 1 Tools - -DevStream treats everything as a concept named _Tool_: - -- Each _Tool_ corresponds to a DevStream plugin, which can either install, configure, or integrate some DevOps tools. -- Each _Tool_ has its Name, InstanceID, and Options. -- Each _Tool_ can have its dependencies, specified by the `dependsOn` keyword. - -The dependency `dependsOn` is an array of strings, each element being a dependency. - -Each dependency is named in the format of "TOOL_NAME.INSTANCE_ID". - ---- - -## 2 Configuration - -Define your needed `tools` in DevStream config: - -- `tools` is a list of `tool`. -- Each element in the list defines a DevOps tool (managed by a DevStream plugin), with the following key/values:. - - `name`: a string without underscore, corresponds to the name of the plugin. - - `instanceID`: unique instance ID of a tool. - - Multiple tools defined with the same `name` or `instanceID` are allowd, but `name + instanceID` must be unique. -- Each plugin has an optional setting `options`, and the options for each plugin is different. See the [list of plugins](../plugins/plugins-list.md) for more details. -- Each plugin has an optional setting `dependsOn` which defines the dependencies of this plugin. E.g., if A depends on B and C, then dtm will only execute A after B and C. - -An example of `tools` config: - -```yaml -tools: -- name: repo-scaffolding - instanceID: golang-github - options: - destinationRepo: - owner: [[ githubUsername ]] - name: [[ repoName ]] - branch: [[ defaultBranch ]] - scmType: github - vars: - ImageRepo: "[[ dockerhubUsername ]]/[[ repoName ]]" - sourceRepo: - org: devstream-io - name: dtm-scaffolding-golang - scmType: github - token: [[ env GITHUB_TOKEN ]] -- name: jira - instanceID: default - dependsOn: [ "repo-scaffolding.golang-github" ] - options: - scm: - owner: [[ githubUsername ]] - name: [[ repoName ]] - scmType: github - branch: main - jira: - baseUrl: https://xxx.atlassian.net - userEmail: foo@bar.com - projectKey: zzz - token: [[ env JIRA_TOKEN ]] -``` - -`[[ githubUsername ]]`, `[[ repoName ]]` (and other variables inside the double brackets) are global variables which are defined in the `vars` section of the config. diff --git a/docs/core-concepts/tools.zh.md b/docs/core-concepts/tools.zh.md deleted file mode 100644 index f7847bb7d..000000000 --- a/docs/core-concepts/tools.zh.md +++ /dev/null @@ -1,60 +0,0 @@ -# 工具(Tools) - -## 1 概览 - -DevStream 视一切为 "工具": - -- 每个 工具 对应一个 DevStream 插件,它可以安装、配置或集成一些 DevOps 工具。 -- 每个 工具 都有它的名称(对应插件名称)、实例 ID 和选项(Options)。 -- 每个 工具 都可以依赖其他的工具,通过 `dependsOn` 关键字指定。 - -依赖 `dependsOn` 是一个字符串数组,每个元素都是一个依赖项,格式是 "工具名.实例ID"。 - -## 2 配置方式 - -DevStream 通过在配置中定义 `tools` 来声明所需的工具集合: - -- `tools` 是一个定义多个 工具 的列表 -- 列表中的每个对象都定义了一个由 DevStream 插件管理的工具 - - `name`: 是一个不带下划线的字符串,用来定义插件的名称 - - `instanceID`: 实例的 ID,唯一标识一个工具实例 - - `name` 和 `instanceID` 可以分别重复,但是 `name + instanceID` 的组合必须唯一 -- 每个插件都有一个可选字段,"选项"(`options`),每个插件的选项都是不同的,详情请参考[插件列表](../plugins/plugins-list.md)。 -- 每个插件都有一个可选字段,"依赖项"(`dependsOn`),定义了该插件依赖的其他插件列表。如果 A 依赖了 B 和 C,那么 dtm 会在 B 和 C 插件执行成功后再执行 A 插件。 - -`tools` 的配置示例如下: - -```yaml -tools: -- name: repo-scaffolding - instanceID: golang-github - options: - destinationRepo: - owner: [[ githubUsername ]] - name: [[ repoName ]] - branch: [[ defaultBranch ]] - scmType: github - vars: - ImageRepo: "[[ dockerhubUsername ]]/[[ repoName ]]" - sourceRepo: - org: devstream-io - name: dtm-scaffolding-golang - scmType: github -- name: jira - instanceID: default - dependsOn: [ "repo-scaffolding.golang-github" ] - options: - scm: - owner: [[ githubUsername ]] - name: [[ repoName ]] - scmType: github - branch: main - jira: - baseUrl: https://xxx.atlassian.net - userEmail: foo@bar.com - projectKey: zzz - token: [[ env JIRA_TOKEN ]] -``` - -其中,[[ githubUsername ]]、[[ repoName ]] 等是全局变量,它们的值可以在 `vars` 字段中定义。 - diff --git a/docs/development/architecture.md b/docs/development/architecture.md deleted file mode 100644 index becda1b07..000000000 --- a/docs/development/architecture.md +++ /dev/null @@ -1,67 +0,0 @@ -# Architecture - -This document summarizes the main components of DevStream and how data flows between these components. - -## 0 Data Flow - -The following diagram shows an approximation of how DevStream executes a user command: - -![DevStream Architecture Diagram](../../images/architecture-overview.png) - -There are three major parts: - -- CLI: handles user input, commands and parameters. -- Plugin engine: achieves the core functionalities by calling other modules (config manager, plugin manager, state manager, and backend manager.) -- plugins: implements the actual CRUD interfaces for a certain DevOps tool, or integrates different tools together. Each plugin corresponds to a certain DevOps tool or an automated integration of tools. - -## 1 CLI (The `devstream` Package) - -Note: for simplicity, the CLI is named `dtm`(DevOps Tool Manager) instead of the full name DevStream. - -Every time a user runs the `dtm` program, the execution transfers immediately into one of the "command" implementations in the [`devstream`](https://github.com/devstream-io/devstream/tree/main/cmd/devstream) package, in which folder all commands' definitions reside. - -Then, each command calls the plugin engine package under [`internal/pkg`](https://github.com/devstream-io/devstream/tree/main/internal/pkg/pluginengine). - -The `pluginengine` calls the [config manager package](https://github.com/devstream-io/devstream/tree/main/internal/pkg/configmanager) first to read the local YAML config file into a struct. - -Then it calls the [`pluginmanager` package](https://github.com/devstream-io/devstream/tree/main/internal/pkg/pluginmanager) to download the required plugins. - -After that, the plugin engine calls the [state manager](https://github.com/devstream-io/devstream/tree/main/internal/pkg/statemanager) to calculate "changes" between the congfig, the state, and the actual DevOps tool's status. At last, the plugin engine executes actions according to the changes, and updates the state. During the execution, the plugin engine loads each plugin (`*.so` file) and calls the predefined interface according to each change. - -## 2 Plugin Engine - -The `pluginengine` has various responsibilities: - -- make sure the required plugins (according to the config file) are present -- generate changes according to the config, the state and tools' actual status -- execute the changes by loading each plugin and calling the desired action - -It achieves the goal by calling the following modules: - -### 2.1 Config Manager - -Model types in package [`configmanager`](https://github.com/devstream-io/devstream/blob/main/internal/pkg/configmanager/configmanager.go#L23) represent the top-level configuration structure. - -### 2.2 Plugin Manager - -The [`pluginmanager`](https://github.com/devstream-io/devstream/blob/main/internal/pkg/pluginmanager/manager.go) package is in charge of downloading necessary plugins according to the configuration. - -If a plugin with the desired version already exists locally, it will not download it again. - -### 2.3 State Manager - -The [`statemanager`](https://github.com/devstream-io/devstream/blob/main/internal/pkg/statemanager/manager.go) package manages the "state", i.e., what has been done successfully and what not. - -The `statemanager` stores the state in a [`backend`](https://github.com/devstream-io/devstream/blob/main/internal/pkg/backend/backend.go). - -### 2.4 Backend Manager - -The [`backend`](https://github.com/devstream-io/devstream/tree/main/internal/pkg/backend) package is the backend manager, which manages the actual state. Currently, local, remote (AWS S3 compatible), and k8s(ConfigMap) state are supported. - -## 3 Plugin - -A _plugin_ implements the aforementioned, predefined interfaces. - -It executes operations like `Create`, `Read`, `Update`, and `Delete`. - -To develop a new plugin, see [creating a plugin](dev/creating-a-plugin.md). diff --git a/docs/development/architecture.zh.md b/docs/development/architecture.zh.md deleted file mode 100644 index 6be738ce0..000000000 --- a/docs/development/architecture.zh.md +++ /dev/null @@ -1,65 +0,0 @@ -# 架构 - -本文介绍了DevStream的架构,总结了DevStream的主要组件,以及数据、命令是如何在各个组件之间流转的。 - -## 0 工作流程 - -下图展示了DevStream是如何执行一个用户命令的。 - -![DevStream架构图](../../images/architecture-overview.png) - -DevStream主要由三大块组成: - -- CLI:处理用户输入的命令和参数 -- Plugin engine: 插件引擎,通过调用其他组件(config manager, plugin manager, state manager, backend manager)来实现DevStream的核心功能。 -- 插件:实现某个DevOps工具的CRUD接口。 - -## 1 CLI - -_注意:为了简单起见,CLI被命名为`dtm`(DevOps Toolchain Manager)。_ - -用户运行`dtm`时,会调用[`devstream`](https://github.com/devstream-io/devstream/tree/main/cmd/devstream)包中的一个命令。所有命令的源文件定义都在这个文件夹中。 - -然后,每个命令调用[`internal/pkg`](https://github.com/devstream-io/devstream/tree/main/internal/pkg/pluginengine)下的`pluginengine`包。 - -`pluginengine`首先调用`configmanager`,将本地YAML配置文件读取到一个结构体中,然后调用`pluginmanager`来下载所需的插件。 - -之后,`pluginengine`调用`statemanager`来计算config、状态和实际DevOps工具的状态之间的"差异"。最后,`pluginengine`根据这变更执行对应的操作,并更新状态。在执行过程中,`pluginengine`加载每个插件(`*.so`文件)并根据每个变更调用相应的接口。 - -## 2 插件引擎 - -`pluginengine`有以下各种职责: - -- 根据配置文件来决定需要哪些插件,以及确保这些插件的存在 -- 根据配置、状态和工具的实际状态来生成变更 -- 通过加载每个插件的so文件并调用所需的接口来执行变更 - -Plugin engine通过调用以下模块来实现目标: - -### 2.1 配置管理器 - -[`configmanager`包](https://github.com/devstream-io/devstream/blob/main/internal/pkg/configmanager/configmanager.go#L23)中的模型结构体用来表示顶级配置的结构。 - -### 2.2 插件管理器 - -[`pluginmanager`包](https://github.com/devstream-io/devstream/blob/main/internal/pkg/pluginmanager/manager.go)负责根据配置下载必要的插件。 - -如果本地已存在所需版本的插件,则不会再次下载。 - -### 2.3 状态管理器 - -[`statemanager`包](https://github.com/devstream-io/devstream/blob/main/internal/pkg/statemanager/manager.go)是用来管理“状态”的,即哪些变更已经成功完成,哪些失败。 - -`statemanager`将状态存储在[`backend`](https://github.com/devstream-io/devstream/blob/main/internal/pkg/backend/backend.go)中。 - -### 2.4 后端管理器 - -[`backend`包](https://github.com/devstream-io/devstream/tree/main/internal/pkg/backend)是“后端”管理器,在这里,“后端”指的是状态的实际存储。目前,DevStream已经支持本地和远程(与AWS S3兼容)状态。 - -## 3 插件 - -一个 _plugin_ 实现了上述的预定义接口。 - -它执行的包括"创建"、"读取"、"更新"和"删除"等操作。 - -要开发一个新的插件,请参阅[创建一个插件](dev/creating-a-plugin.zh.md)。 diff --git a/docs/development/dev/build.md b/docs/development/dev/build.md deleted file mode 100644 index 57bf0a56b..000000000 --- a/docs/development/dev/build.md +++ /dev/null @@ -1,17 +0,0 @@ -# Build - -```shell -cd path/to/devstream -make clean -make build -j8 # multi-threaded build -``` - -This builds everything: `dtm` and all the plugins. - -We also support the following build modes: - -- Build `dtm` only: `make build-core`. -- Build a specific plugin: `make build-plugin.PLUGIN_NAME`. Example: `make build-plugin.argocd`. -- Build all plugins: `make build-plugins -j8` (multi-threaded build.) - -See `make help` for more information. diff --git a/docs/development/dev/build.zh.md b/docs/development/dev/build.zh.md deleted file mode 100644 index f300887ed..000000000 --- a/docs/development/dev/build.zh.md +++ /dev/null @@ -1,17 +0,0 @@ -# 构建 - -```shell -cd path/to/devstream -make clean -make build -j8 # 多线程构建 -``` - -上述命令将构建所有内容: `dtm` 本身及所有插件。 - -项目也支持以下构建模式: - -- 只构建 `dtm`:`make build-core`。 -- 构建指定的插件:`make build-plugin.PLUGIN_NAME`。例如: `make build-plugin.argocd`。 -- 构建所有插件:`make build-plugins -j8` (多线程构建)。 - -使用 `make help` 获取更多信息。 diff --git a/docs/development/dev/creating-a-plugin.md b/docs/development/dev/creating-a-plugin.md deleted file mode 100644 index 9c41623ef..000000000 --- a/docs/development/dev/creating-a-plugin.md +++ /dev/null @@ -1,56 +0,0 @@ -# Creating a Plugin - -## 0 Thanks for Contributing! - -First, please read our [CONTRIBUTING](../../contributing_guide.md) doc. - -## 1 Scaffolding Automagically - -Run `dtm develop create-plugin --name=YOUR-PLUGIN-NAME` , and dtm will automatically generate the following file. - -> ### /cmd/plugin/YOUR-PLUGIN-NAME/main.go - -This is the only main entrance to the plugin code. - -You do not need to change this file. If you want, feel free to submit a pull request to change the [plugin template](https://github.com/devstream-io/devstream/blob/main/internal/pkg/develop/plugin/template/main.go) directly. - -> ### /docs/plugins/YOUR-PLUGIN-NAME.md - -This is the automatically generated plugin documentation. - -Although dtm is automagic, but it can’t read your mind. I’m afraid that you will have to write your own doc. - -> ### /internal/pkg/plugin/YOUR-PLUGIN-NAME/ - -Please fill in the main logic of the plugin here. - -You can check out our [Standard Go Project Layout](../project-layout.md) document for detailed instruction on the project layout. - -## 2 Interfaces - -### 2.1 Definition - -Each plugin needs to satisfy all the interfaces defined in the [plugin engine](https://github.com/devstream-io/devstream/blob/main/internal/pkg/pluginengine/plugin.go#L10). - -At the moment, there are 4 interfaces, which might be subject to change. Currently, the 4 interfaces are: - -- [`Create`](https://github.com/devstream-io/devstream/blob/main/internal/pkg/pluginengine/plugin.go#L12) -- [`Read`](https://github.com/devstream-io/devstream/blob/main/internal/pkg/pluginengine/plugin.go#L13) -- [`Update`](https://github.com/devstream-io/devstream/blob/main/internal/pkg/pluginengine/plugin.go#L14) -- [`Delete`](https://github.com/devstream-io/devstream/blob/main/internal/pkg/pluginengine/plugin.go#L16) - -### 2.2 Return Value - -`Create`, `Read`, and `Update` interfaces return two values `(statemanager.ResourceStatus, error)`; the first being the "state". - -`Delete` interface returns two values `(bool, error)`. It returns `(true, nil)` if there is no error; otherwise `(false, error)` will be returned. - -If no error occurred, the return value would be `(true, nil)`. Otherwise, the result would be `(false, error)`. - -## 3 How does plugin work? - -DevStream uses [go plugin](https://pkg.go.dev/plugin) to implement custom DevOps plugins. - -When you execute a command which calls any of the interfaces(`Create`, `Read`, `Update`, `Delete`), devstream's pluginengine will call the [`plugin.Lookup("DevStreamPlugin")` function](https://github.com/devstream-io/devstream/blob/38307894bbc08f691b2c5015366d9e45cc87970c/internal/pkg/pluginengine/plugin_helper.go#L28) to load the plugin, get the variable `DevStreamPlugin` that implements the ` DevStreamPlugin` interface, and then you can call the corresponding plugin logic functions. This is why it is not recommended to modify the `/cmd/plugin/YOUR-PLUGIN-NAME/main.go` file directly. - -Note: The `main()` in `/cmd/plugin/YOUR-PLUGIN-NAME/main.go` file will not be executed, it is only used to avoid the golangci-lint error. diff --git a/docs/development/dev/creating-a-plugin.zh.md b/docs/development/dev/creating-a-plugin.zh.md deleted file mode 100644 index 4d5250cb2..000000000 --- a/docs/development/dev/creating-a-plugin.zh.md +++ /dev/null @@ -1,54 +0,0 @@ -# 创建一个插件 - -## 0 感谢你的贡献! - -首先,请阅读我们的[贡献指引](../../contributing_guide.zh.md)文档。 - -## 1 自动建立新插件的代码框架 - -运行`dtm develop create-plugin --name=YOUR-PLUGIN-NAME` ,dtm将自动生成以下文件。 - -> ### /cmd/plugin/YOUR-PLUGIN-NAME/main.go - -这是该插件代码的唯一主要入口。 - -你不需要修改这个文件。如果你觉得自动生成的这个文件有问题,你可以创建一个PR来直接修改[模板](https://github.com/devstream-io/devstream/blob/main/internal/pkg/develop/plugin/template/main.go)。 - -> ### /docs/plugins/YOUR-PLUGIN-NAME.md - -这是自动生成的插件的文档。 - -虽然`dtm`的目的就是自动化,但它并不能魔法般的生成文档。你需要自己编写自己想要创建的这个插件的文档。 - -> ###/internal/pkg/plugin/YOUR-PLUGIN-NAME/ - -请在这里编写插件的主要逻辑。 - -可以查看我们的[Standard Go Project Layout](../project-layout.zh.md)文件,了解关于项目布局的详细说明。 - -## 2 接口 - -### 2.1 定义 - -每个插件都需要实现[pluginengine](https://github.com/devstream-io/devstream/blob/main/internal/pkg/pluginengine/plugin.go#L10)中定义的所有接口。 - -目前,有4个接口,可能会有变化。目前,这4个接口是。 - -- [`create`](https://github.com/devstream-io/devstream/blob/main/internal/pkg/pluginengine/plugin.go#L12) -- [`read`](https://github.com/devstream-io/devstream/blob/main/internal/pkg/pluginengine/plugin.go#L13) -- [`update`](https://github.com/devstream-io/devstream/blob/main/internal/pkg/pluginengine/plugin.go#L14) -- [`delete`](https://github.com/devstream-io/devstream/blob/main/internal/pkg/pluginengine/plugin.go#L16) - -### 2.2 返回值 - -`Create`, `Read` 和 `Update` 方法返回两个值 `(statemanager.ResourceStatus, error)`。第一个是 "状态",第二个是 "错误"。 - -`Delete` 接口返回两个值 `(bool, error)`。如果没有错误,它应当返回 `(true, nil)`;否则应返回 `(false, error)`。 - -## 3 插件是如何工作的? - -DevStream是使用[go plugin](https://pkg.go.dev/plugin)来实现自定义插件的。。 - -当你执行一个调用任何接口(`Create`, `Read`, `Update`, `Delete`)的命令时,DevStream的`pluginengine`会调用[`plugin.Lookup("DevStreamPlugin")`函数](https://github.com/devstream-io/devstream/blob/38307894bbc08f691b2c5015366d9e45cc87970c/internal/pkg/pluginengine/plugin_helper.go#L28)来加载插件,获得实现`DevStreamPlugin`接口的变量`DevStreamPlugin`,然后你就可以调用相应的插件接口。所以我们不建议你直接修改`/cmd/plugin/YOUR-PLUGIN-NAME/main.go`文件,因为该文件是根据接口定义自动生成好的。 - -注意:`/cmd/plugin/YOUR-PLUGIN-NAME/main.go`文件中的`main()`不会被执行,它只是用来避免 `golangci-lint` 错误。 diff --git a/docs/development/dev/dev-env-setup.md b/docs/development/dev/dev-env-setup.md deleted file mode 100644 index e807869bc..000000000 --- a/docs/development/dev/dev-env-setup.md +++ /dev/null @@ -1,119 +0,0 @@ -# Development Environment Setup - -OK. So you want to get started with Golang/Kubernetes development. You've come to the right place. Read on. - -## 1 Install Golang - -[Head to the official website](https://go.dev/) and click the "Download" button: - -![](../../images/golang-install.png) - -Make sure you select the correct package according to your operating system and processor: - -![](../../images/golang-download.png) - -For Apple macOS users, click the apple logo in the menu bar, and choose "About This Mac" to check your chip: - -![](../../images/about-this-mac.png) - ---- - -## 2 Install Kubernetes - -The easiest way to run a Kubernetes cluster locally is to run it in a Docker container. - -### 2.1 Install Docker - -[Head to the official website](https://www.docker.com/) and click the download button: - -![](../../images/docker-install.png) - -Again, please pay attention to the operating system and processor options. For Apple M1 mac users, choose the "Apple Chip" option. To check what processor you have, see the previous section in "About This Mac". - -_After installation, make sure Docker is up and running._ - -### 2.2 Install Minikube - -> Minikube is local Kubernetes, focusing on making it easy to learn and develop for Kubernetes. - -> All you need is Docker (or similarly compatible) container or a Virtual Machine environment, and Kubernetes is a single command away. - -_Note: there are other tools which can install a local K8s, such as `kind`, etc.; here we choose one of the most famous tools that is minikube as the demo._ - -First, go to the [official website of minikube](https://minikube.sigs.k8s.io/docs/start/), choose the right OS and architecture (again,) and download/install: - -![](../../images/minikube-install.png) - -Alternatively, if you are using [Homebrew](https://brew.sh/) (if you don't know what it is, ignore this line,) you can simply run `brew install minikube`. - -### 2.3 Install `kubectl` - -Go to [Kubernetes' official documentation website](https://kubernetes.io/docs/tasks/tools/) and follow the guide to install kubectl. Choose your operating system: - -![](../../images/kubectl-install.png) - -Again, for macOS users, if you are using Homebrew package manager, you can install kubectl with Homebrew: - -```bash -brew install kubectl -``` - -### 2.4 Start K8s - -Run: - -```bash -minikube start --driver=docker -``` - -> Note: if you would like to set Docker as the default driver for minikube, you can run: -> -> ```bash -> minikube config set driver docker -> ``` -> -> Then next time when you want to start minikube, you can simply run `minikube start` without the `--driver=docker` parameter. - -### 2.5 Check K8s Status - -Run `minikube status`, and you should get similar output: - -```shell -$ minikube status -minikube -type: Control Plane -host: Running -kubelet: Running -apiserver: Running -kubeconfig: Configured -``` - -Run `kubectl get node`, and you should get similar output: - -```shell -$ kubectl get node -NAME STATUS ROLES AGE VERSION -minikube Ready control-plane 55s v1.24.3 -``` - -OK, now you have Golang and Kubernetes ready locally. Start coding! - ---- - -## 3 Contribute to DevStream - -Run: - -```shell -git clone https://github.com/devstream-io/devstream.git -``` - -and start from there! - -For example, you can try a local build: - -```shell -make build -j10 VERSION=0.8.0 -``` - -Or, maybe you would like to have a go with it first? Check our [quickstart guide](../../quickstart.md). Happy hacking! diff --git a/docs/development/dev/dev-env-setup.zh.md b/docs/development/dev/dev-env-setup.zh.md deleted file mode 100644 index ca0498789..000000000 --- a/docs/development/dev/dev-env-setup.zh.md +++ /dev/null @@ -1,121 +0,0 @@ -# 开发环境搭建 - -好。你想开始搞点Golang/Kubernetes开发了。恭喜你,你来对地方了。请继续阅读。 - -## 1 安装Golang - -[前往Golang官网](https://go.dev/)并点击“下载”按钮: - -![](../../images/golang-install.png) - -根据你的操作系统和处理器来选择正确的软件包版本: - -![](../../images/golang-download.png) - -对于Apple macOS用户,点击菜单栏中的苹果标志,然后选择“关于本机”以查看您的芯片: - -![](../../images/about-this-mac.png) - ---- - -## 2 安装Kubernetes - -在本地跑一个Kubernetes集群最简单的方法是在Docker容器中。 - -### 2.1 安装Docker - -[前往Docker官网](https://www.docker.com/)点击下载按钮: - -![](../../images/docker-install.png) - -同样请注意操作系统和处理器选项。对于Apple M1 mac用户,选择“Apple Chip”选项。如果想确认你的处理器是Apple还是Intel,请参阅上一节中的“关于本机”部分。 - -_安装后,请确保Docker已启动并正在运行中。_ - -### 2.2 安装Minikube - -Minikube在本地搭建Kubernetes的一种简单的方式,让你可以不被安装K8s而困扰,从而可以专注于K8s的学习和开发。 - -你所需要的只是Docker容器或虚拟机环境,然后执行一个命令,你就可以拥有一个本地K8s集群了。 - -_注意:其实有很多种可以安装本地K8s的工具,如`kind`等;在这里,我们选择了最著名的工具之一minikube作为演示。_ - -首先,访问[minikube官网](https://minikube.sigs.k8s.io/docs/start/),选择正确的操作系统和架构,并下载/安装: - -![](../../images/minikube-install.png) - -或者,如果你在用[Homebrew](https://brew.sh/)的话(如果你不知道它是啥,请忽略)你可以通过运行`brew install minikube`来安装。 - -### 2.3 安装`kubectl` - -前往[Kubernetes的官方文档站点](https://kubernetes.io/docs/tasks/tools/)然后按照指南来安装kubectl。选择您的操作系统: - -![](../../images/kubectl-install.png) - -需要再次强调的是,对于macOS用户,如果你用Homebrew包管理器,那么你可以用brew装kubectl: - -```bash -brew install kubectl -``` - -### 2.4 启动K8s集群 - -运行: - -```bash -minikube start --driver=docker -``` - -> 注意:如果您想将 Docker 设置为 minikube 的默认驱动程序,您可以运行: -> -> ```bash -> minikube config set driver docker -> ``` -> -> 然后下次要启动 minikube 时,只需运行`minikube start`即可,就不用再传`--driver=docker`参数了。 - -### 2.5 检查K8s集群状态 - -运行`minikube status`,然后你可以得到类似的输出: - -```shell -$ minikube status -minikube -type: Control Plane -host: Running -kubelet: Running -apiserver: Running -kubeconfig: Configured -``` - -运行`kubectl get node`,然后你应该能看到如下的输出: - -```shell -$ kubectl get node -NAME STATUS ROLES AGE VERSION -minikube Ready control-plane 55s v1.24.3 -``` - -好,现在Golang和Kubernetes就都准备好啦,可以开始写代码啦! - ---- - -## 3 为DevStream贡献代码 - -执行: - -```shell -git clone https://github.com/devstream-io/devstream.git -``` - -然后从这儿开始! - -比如,你可以尝试一下本地编译DevStream: - -```shell -make build -j10 VERSION=0.8.0 -``` - -或者可能你想先试用一下DevStream?没问题,参考我们的[快速开始](../../quickstart.zh.md)文档! - -Happy hacking! diff --git a/docs/development/dev/lint.md b/docs/development/dev/lint.md deleted file mode 100644 index c46f532b2..000000000 --- a/docs/development/dev/lint.md +++ /dev/null @@ -1,13 +0,0 @@ -# Lint - -We use `golangci-lint` ([official website](https://golangci-lint.run/), [GitHub](https://github.com/golangci/golangci-lint)) for linting, which is a Go linters aggregator. - -It's also integrated with the GitHub Actions workflows. - -- The list of linters enabled by default is documented [here](https://golangci-lint.run/usage/linters/). -- It can be [integrated with IDE](https://golangci-lint.run/usage/integrations/) -- You can [run it locally](https://golangci-lint.run/usage/quick-start/). - -Besides, we also use the [Go Report Card](https://goreportcard.com/report/github.com/devstream-io/devstream). - -There is a badge like [![Go Report Card](https://goreportcard.com/badge/github.com/devstream-io/devstream)](https://goreportcard.com/report/github.com/devstream-io/devstream) in the main README.md. diff --git a/docs/development/dev/lint.zh.md b/docs/development/dev/lint.zh.md deleted file mode 100644 index f242ec3f1..000000000 --- a/docs/development/dev/lint.zh.md +++ /dev/null @@ -1,14 +0,0 @@ -# 代码检查 - -项目使用Go 的代码检查工具集`golangci-lint` ([official website](https://golangci-lint.run/), [GitHub](https://github.com/golangci/golangci-lint)) 进行代码格式检查。 - -也支持与 Github 工作流集成。 - -- 记录默认启用的检查项的 [文档](https://golangci-lint.run/usage/linters/) 。 -- 也可以和 [IDE](https://golangci-lint.run/usage/integrations/) 集成。 -- 也可以在 [本地运行](https://golangci-lint.run/usage/quick-start/) 。 - -除此之外,我们使用 [Go Report Card](https://goreportcard.com/report/github.com/devstream-io/devstream) ,为你的开源 Go 代码生成质量报告。 - -在项目的README.md中,有代码质量标志 [![Go Report Card](https://goreportcard.com/badge/github.com/devstream-io/devstream)](https://goreportcard.com/report/github.com/devstream-io/devstream) 。 - diff --git a/docs/development/dev/test.md b/docs/development/dev/test.md deleted file mode 100644 index 719e055c0..000000000 --- a/docs/development/dev/test.md +++ /dev/null @@ -1,48 +0,0 @@ -# Test - -## Unit Test - -Run all unit tests: - -```shell -# at the root of the repo -go test ./... -``` - -_Note: not all tests are strictly "unit" tests at the moment because some tests will actually rely on internet. Help wanted here :)_ - -## E2E(End-to-End) Test - -### GitHub Actions - -Our e2e test will run in GitHub Actions automatically when there is a change on the main branch. - -The definition of the GitHub Action is [here](https://github.com/devstream-io/devstream/blob/main/.github/workflows/e2e-test.yml), and the configuration files used in e2e tests are [here](https://github.com/devstream-io/devstream/tree/main/test/e2e/yaml). - -### Run E2E Test Locally - -We have a simple e2e test that tests the following plugins: - -- `repo-scaffolding` -- `github-actions` -- `argocd` -- `argocdapp` - -The template for the config file is located [here](https://github.com/devstream-io/devstream/blob/main/test/e2e/yaml/e2e-test-local.yaml). - -To run the e2e test locally, you will need docker up and running first. - -Then, set the following environment variables: - -- GITHUB_USER -- GITHUB_TOKEN -- DOCKERHUB_USERNAME -- IMAGE_REPO_PASSWORD - -And execute the following command: - -```shell -bash hack/e2e/e2e-run.sh -``` - -This test script will download kind/kubectl, start a K8s cluster as a docker container using kind, then execute dtm commands, check result, and clean up the environment. diff --git a/docs/development/dev/test.zh.md b/docs/development/dev/test.zh.md deleted file mode 100644 index 01bde20fd..000000000 --- a/docs/development/dev/test.zh.md +++ /dev/null @@ -1,45 +0,0 @@ -# 测试 - -## 单元测试 - -运行所有的单元测试: - -```shell -go test ./... -``` - -_注意:目前,不是所有的测试都是真正的“单元"测试,因为有些测试依赖于网络。热切期望你的帮助 : )_ - -## 端到端(End-to-End)测试 - -### GitHub Actions - -当代码库的主分支代码更新时,GitHub Actions 会自动运行端到端测试。 - -GitHub Action 工作流程的定义在[这里](https://github.com/devstream-io/devstream/blob/main/.github/workflows/e2e-test.yml),运行端到端测试时使用的 `dtm` 配置文件在[这里](https://github.com/devstream-io/devstream/tree/main/test/e2e/yaml)。 - -### 本地运行端到端测试 - -目前,我们编写了针对以下插件的简单端到端测试: - -- `repo-scaffolding` -- `github-actions` -- `argocd` -- `argocdapp` - -本地运行端到端测试的配置模板在[这里](https://github.com/devstream-io/devstream/blob/main/test/e2e/yaml/e2e-test-local.yaml)。 - -在测试前,请先确保 Docker 已经启动,并设置以下环境变量: - -- GITHUB_USER -- GITHUB_TOKEN -- DOCKERHUB_USERNAME -- IMAGE_REPO_PASSWORD - -然后执行: - -```shell -bash hack/e2e/e2e-run.sh -``` - -测试脚本将会下载 kind/kubectl,启动一个 K8s 容器,并执行 `dtm` 命令,检查运行结果,最后清理环境。 diff --git a/docs/development/docs-contribution/mkdocs.md b/docs/development/docs-contribution/mkdocs.md deleted file mode 100644 index 44bc3eb41..000000000 --- a/docs/development/docs-contribution/mkdocs.md +++ /dev/null @@ -1,89 +0,0 @@ -# Creating a Documentation for DevStream - -## Background - -1. We use [readthedocs](https://readthedocs.org/) to host our documentation. You do not need to know too much more about it in order to create a doc for DevStream, though. -1. Readthedocs supports both [`sphinx`](https://docs.readthedocs.io/en/stable/intro/getting-started-with-sphinx.html) and [`mkdocs`](https://docs.readthedocs.io/en/stable/intro/getting-started-with-mkdocs.html) to build the doc site; we use `mkdocs` at the moment. If you meet any issues, the official doc of `mkdocs` [here](https://www.mkdocs.org/) is a good place to start. -1. In our mkdocs setup, we use the `material` theme. More info on "material": - - [website](https://squidfunk.github.io/mkdocs-material/) - - [docs](https://squidfunk.github.io/mkdocs-material/getting-started/) - - [GitHub repo](https://github.com/squidfunk/mkdocs-material) -1. We also use two plugins for mkdocs: - - [search](https://squidfunk.github.io/mkdocs-material/setup/setting-up-site-search/) - - [mkdocs-i18n](https://pypi.org/project/mkdocs-i18n/) - -## Prerequisites - -- Python3 (`mkdocs` is a Python thing) -- pip3 (to install dependencies) - -Suggested setup for macOS users: - -- [use brew to setup Python](https://docs.brew.sh/Homebrew-and-Python) -- `pip3 install -r docs/requirements.txt` - -## Docs Root - -The root folder of `mkdocs` is at `/`. - -The main config is `/mkdocs.yml`, and the docs folder is `/docs`. - -## i18n (Internationalization) - -Currently, we support two languages: -- en -- zh - -It's worth noting that the "search" function doesn't work for "zh" (a limitation of `mkdocs`' search function.) - -For each English document, there is a Chinese translation in a separate file. - -If the English document's filename is `doc_name.md`, there should also be a file named `doc_name.zh.md`. To create a Chinese translation, put the translation into `doc_name.zh.md`. This file is the translation of `doc_name.md` (English). - -## Create a New Documentation - -To create new documentation, do the following: - -- Create `doc_name.md` and `doc_name.zh.md` in the `/docs` folder. You can put them under a subfolder if necessary. Refer to the current directory structure and use your best judgment to decide the best place for that new doc. -- Write the content of the doc. You can choose to write only the English doc or the Chinese doc; you don't have to (but it's highly recommended if you can) write documentation in both languages. -- In most cases, you don't need to think about the navigation menu which is the table of content of the whole doc website. But If you need to customize the navigation menu, you can refer to [Setting up Navigation](#setting-up-navigation). - -## Setting up Navigation - -If you want to customize the navigation menu, you can update `nav:` section in `mkdocs.yaml`. We support wildcards and [subdirectory cross-link](https://oprypin.github.io/mkdocs-literate-nav/reference.html#subdirectory-cross-link). For example: - -```yaml -nav: - - DTM Commands Explained in Depth: - - commands/autocomplete*.md - - commands/*.md - - Plugins: - - plugins/plugins-list*.md - - plugins/*.md - - Best Practices: best-practices/ - - 'contributing_guide*.md' - - 'contributor_ladder*.md' -``` - -- Normally, 'contributing_guide*.md' will be expanded to 'contributing_guide.md' and 'contributing_guide.zh.md' -- If you create documentation in the `commands/`, `plugins/`, and `best-practices/` directories, you will not need to update the `nav`. - -If you want to know more about the configuration of navigation, please refer to [Configure Pages and Navigation](https://www.mkdocs.org/user-guide/writing-your-docs/#configure-pages-and-navigation) and [Literate Nav Syntax](https://oprypin.github.io/mkdocs-literate-nav/) - -## Review Your Change Locally - -Run: - -```sh -# at the root of the repo -pip3 install -r docs/requirements.txt -mkdocs serve -``` - -Review your changes before submitting a PR. - -## Recommended Resources - -- [Markdown Guide](https://www.markdownguide.org/) -- [Grammarly](https://app.grammarly.com/) (for writting English documents) -- [Title Case Converter](https://www.titlecase.com/) diff --git a/docs/development/docs-contribution/mkdocs.zh.md b/docs/development/docs-contribution/mkdocs.zh.md deleted file mode 100644 index 664ba2183..000000000 --- a/docs/development/docs-contribution/mkdocs.zh.md +++ /dev/null @@ -1,88 +0,0 @@ -# 为DevStream创建文档 - -## 背景 - -1. 我们使用[readthedocs](https://readthedocs.org/)托管文档,不过你并不需要对readthedocs了解很多,即可为DevStream的文档做出贡献。 -1. Readthedocs支持[`sphinx`](https://docs.readthedocs.io/en/stable/intro/getting-started-with-sphinx.html)和[`mkdocs`](https://docs.readthedocs.io/en/stable/intro/getting-started-with-mkdocs.html)来构建文档站点;我们选择了`mkdocs`。如果你遇到任何问题,请参阅[`mkdocs`的官方文档](https://www.mkdocs.org/)。 -1. 我们使用了`mkdocs`的`material`主题。有关`material`主题的更多信息请参见: - - [网站](https://squidfunk.github.io/mkdocs-material/) - - [文档](https://squidfunk.github.io/mkdocs-material/getting-started/) - - [GitHub 仓库](https://github.com/squidfunk/mkdocs-material) -1. 我们还使用了`mkdocs`的两个插件: - - [search](https://squidfunk.github.io/mkdocs-material/setup/setting-up-site-search/) - - [mkdocs-i18n](https://pypi.org/project/mkdocs-i18n/) - -## 前置条件 - -- Python3(`mkdocs`是基于Python的) -- pip3(安装依赖项) - -建议macOS用户进行如下操作: - -- [使用Brew安装Python](https://docs.brew.sh/Homebrew-and-Python) -- `pip3 install -r docs/requirements.txt` - -## 文档根目录 - -`mkdocs`的根文件夹位于`/`。 - -主配置是根目录`/`下的`mkdocs.yml`,docs文件夹是`/docs`。 - -## i18n(国际化) - -目前,我们支持两种语言: - -- 英文 -- 简体中文 - -值得注意的是,搜索功能不适用于简体中文(`mkdocs`搜索功能的限制)。 - -对于每个英文文档,在单独的文件中都有中文翻译。 如果英文文档的文件名是`doc_name.md`,那么应该还有一个名为`doc_name.zh.md`的文件。要创建中文文档,请将中文翻译内容放入`doc_name.zh.md`。该文件是`doc_name.md`(英文)的翻译。 - -## 创建一个新文档 - -要创建新文档,请执行以下操作: - -- 在`/docs`文件夹中创建`doc_name.md`和`doc_name.zh.md`。如有必要,你可以将它们放在恰当的子文件夹下。参考当前目录结构来确定适合该文档的最佳路径。 -- 编写文档的内容。你可以选择只写英文文档或中文文档;你不必用两种语言编写文档;当然如果你希望展示你中英双语的实力的话,我们建议你两种语言的文档同时编写,一并提交。 -- 大多数情况下,你不需要考虑导航菜单,它是整个文档网站的目录。但是如果需要自定义导航菜单,可以参考[设置导航](https://github.com/devstream-io/devstream/blob/main/docs/development/docs-contribution/mkdocs.md#setting-up-navigation)。 - -## 设置导航 - -如果要自定义导航菜单,可以更新`mkdocs.yaml`中的`nav:`部分,支持通配符和子目录链接。例如: - -``` -nav: - - DTM Commands Explained in Depth: - - commands/autocomplete*.md - - commands/*.md - - Plugins: - - plugins/plugins-list*.md - - plugins/*.md - - Best Practices: best-practices/ - - 'contributing_guide*.md' - - 'contributor_ladder*.md' -``` - -- 通常,`contributing_guide*.md`指代了`contributing_guide.md`和`contributing_guide.zh.md`两个文件,分别是英文和中文文档; -- 如果在`commands/`, `plugins/`, `best-practices/`目录中创建文档,不需要更新`nav`。 - -如果想了解更多关于导航的配置,请参阅[配置页面与导航](https://www.mkdocs.org/user-guide/writing-your-docs/#configure-pages-and-navigation)和[导航语法](https://oprypin.github.io/mkdocs-literate-nav/)。 - -## 在本地查看你的更改 - -运行: - -```sh -# 在repo的根目录下 -pip3 install -r docs/requirements.txt -mkdocs serve -``` - -请在提交PR之前在本地确认你的更改。 - -## 推荐的工具和阅读材料 - -- [Markdown指南](https://www.markdownguide.org/) -- [Grammarly](https://app.grammarly.com/)(用于编写英文文档) -- [标题大小写转换](https://www.titlecase.com/) diff --git a/docs/development/docs-contribution/translation.md b/docs/development/docs-contribution/translation.md deleted file mode 100644 index 4afd1b037..000000000 --- a/docs/development/docs-contribution/translation.md +++ /dev/null @@ -1,6 +0,0 @@ -# Document Translation - -The DevStream documentation is usually written in English and then translated to Chinese. -In this article, I am going to talk to you about how to translate an article. -But since the target language is Chinese and the target reader is also a Chinese-speaking reader, I will express it in Chinese. -Please switch to the Chinese version for more information. diff --git a/docs/development/docs-contribution/translation.zh.md b/docs/development/docs-contribution/translation.zh.md deleted file mode 100644 index 3f95a7d91..000000000 --- a/docs/development/docs-contribution/translation.zh.md +++ /dev/null @@ -1,23 +0,0 @@ -# 文档翻译 - -一般情况下,DevStream 文档使用英文来写,然后翻译成其他语言,目前主要是中文。本文将介绍文档翻译工作的一些规则以及注意事项。 - -## 规则 - -我们以英文文档为主,所以新增文档的时候尽可能先写英文,然后再通过翻译的方式补充中文版本,避免出现部分文档有英文没中文,部分文档有中文没英文的混乱局面。 - -文档翻译以意译为主,句子表达要求符合中国人的说话习惯,看起来通顺自然。翻译过程中如果发现相应英文文档不够详细,也必须保留不详细的内容,不要在翻译的过程中创作。 -换言之,所有的不详细、错误等如果希望更正,都应该先保证先改英文版,再更新中文版,避免出现英文版落后的情况。 - -如果你希望补充一个中文文档,并且不想写对应的英文版本,请先开一个 issue 来记录你的想法,如果合理,社区会寻找一位擅长英文的贡献者来帮助你在中文文档完成的同时补充相应的英文版本文档,确保英文版本文档永远是最新最全面的; -如果你想翻译某一篇英文文档,也请先开一个 issue,等得管理员确认并回复后再开始翻译工作; - -## 注意事项 - -1. 所有英文标点需要转换成对应的中文标点,比如“,、.、;、:”等对应需要改成“,、。、;、:”; -2. 专有名词如果翻译之后更难理解,则不翻译,比如 Pod 不要翻译成“豆荚”或者“容器组”;DevOps 不要翻译成“开发运维”;k8s 里的 Service、Deployment 等都不需要翻译; -3. 中文文档以第二人称为主,部分场景也可以使用“我们”,不需要敬语。比如“you”翻译成“你”而不是“您”; -4. 中文句子中带英文单词时,单词左右的空格可有可无,但是要全文统一,不能有的地方加空格,有的地方不加空格。比如本文出现的英文单词左右一律都加了空格; -5. 不要引入多余的空格或空行,也不要丢失必要的空行,比如标题和正文之间需要有且只有1个空行。 - -上述注意事项只是初版,如果你有更多的建议,欢迎提交一个 Pull Request 来一起进一步完善。 diff --git a/docs/development/git-workflow/branch-and-release.md b/docs/development/git-workflow/branch-and-release.md deleted file mode 100644 index 1d700c821..000000000 --- a/docs/development/git-workflow/branch-and-release.md +++ /dev/null @@ -1,41 +0,0 @@ -# Branch and Version - -## 1. Branch Name and Version Number - -- We use [SemVer](https://semver.org) to specify version numbers; -- We only use two types of branches: - - `main` branch -> The main branch and integration branch, all prs should be merged into the `main` branch first; - - `release` branch -> The release branch, the main purpose of introducing the `release` branch is to release patch versions on non-latest versions. - -## 2. Which Branch Do We Release from - -Due to historical reasons, we only maintained the `main` branch in the early days, so the correspondence between branches and versions is divided into two cases: - -| Branch | Version | -| :---------: | :---------:| -| main | <= v0.4.0 | -| release-x.y | >= v0.5.0 | - -## 3. Correspondence between Branch Name and Version Number - -Start version `v0.5.0`, we decided to release from a specific `release` branch. The name rule of the release branch is `release-x.y`, corresponding to the version `vx.y.n`, where n is the name of the patch version number. See the following table: - -| Branch | Version | -| :---------: | :---------:| -| release-0.5 | v0.5.0 | -| release-0.6 | v0.6.0, v0.6.1, ... | - -E.g. - -- The first version released on the `release-0.5` branch is `v0.5.0`; -- When a bug was found in the `v0.5.0` version. The commit(s) corresponding to the bugfix should be merged from the `main` branch to the `release-0.5` branch through `cherry-pick`, and then continue to release the `v0.5.1` version. - -## 4. Release Cycle - -Starting with `v0.5.0` and until `v1.0`, we maintain a cadence of one minor release per month. For example, if `v0.5.0` is released this month, then the version to be released next month is `v0.6.0`. - -## 5. How Long Will a Version be Supported - -**Before the `v1.0` release, we only maintained the latest version. **For example, after `v0.5.0` is released, before `v0.6.0` is released, we will merge all bugfixes into `release-0.5` branch by `cherry-pick`, and in `release-0.5`, the `v0.5.n` patch version is continuously released. When the `v0.6.0` version is released, the `release-0.5` branch will no longer be updated. - -**Starting from the `v1.0` version, we will switch to releasing a minor version every three months and maintain a total of three minor versions.** That means the maintenance cycle for each minor release is **3 x 3 = 9** months. For example, if `v1.0.0` is released in **January 2023**, then `v1.1.0` will be released in **April 2023**, and `v1.2.0` will be released in **July 2023**. Within 9 months after the release of each minor version, the bugfixes will continue to be merged, and the corresponding patch version will continue to be released. diff --git a/docs/development/git-workflow/branch-and-release.zh.md b/docs/development/git-workflow/branch-and-release.zh.md deleted file mode 100644 index 7fcc26ca4..000000000 --- a/docs/development/git-workflow/branch-and-release.zh.md +++ /dev/null @@ -1,42 +0,0 @@ -# 分支和版本 - -## 1. 分支名称和版本号 - -- 我们使用 [SemVer](https://semver.org) 来指定版本号; -- 我们只使用两种分支: - - `main` 分支 -> 主分支和集成分支, 所有pr都应该先合并到 `main` 分支中; - - `release` 分支 -> 发布分支, 引入 `release` 分支的主要目的是在non-latest版本上发布补丁版本。 - -## 2. 我们从哪个分支发布 - -由于历史原因,早期我们只维护了`main`分支,所以分支与版本的对应分为两种情况: - -| Branch | Version | -| :---------: | :---------:| -| main | <= v0.4.0 | -| release-x.y | >= v0.5.0 | - -## 3. 分支名称与版本号的对应关系 - -`v0.5.0`版本开始,我们决定从特定的 `release` 分支发布。发布分支的命名规则是`release-x.y`,对应版本`vx.y.n`,其中n是补丁版本号的名称。见下表: - -| Branch | Version | -| :---------: | :---------:| -| release-0.5 | v0.5.0 | -| release-0.6 | v0.6.0, v0.6.1, ... | - -例如 - -- 在 `release-0.5` 分支上发布的第一个版本是 `v0.5.0`; -- When a bug was found in the `v0.5.0` version. 与 bugfix 对应的提交应该通过 `cherry-pick` 从 `main` 分支合并到 `release-0.5` 分支, 然后继续发布`v0.5.1`版本。 - -## 4. 发布周期 - -从 `v0.5.0` 到 `v1.0`,我们保持每月发布一个次要版本的节奏。例如,如果本月发布了`v0.5.0`,那么下个月发布的版本是`v0.6.0`。 - -## 5. 一个版本支持多久 - -**在 `v1.0` 发布之前,我们只维护最新版本。** 例如, 在发布`v0.5.0`后,`v0.6.0` 发布之前, 我们会将所有的bugfix合并到 `release-0.5` 的`cherry-pick` 分支中, 并且在 `release-0.5` 中 `v0.5.n` 补丁版本会不断发布.当 `v0.6.0` 版本发布时, `release-0.5` 将不再更新。 - -**从 `v1.0` 版本开始,我们将改为每三个月发布一次次要版本,共维护三个次要版本。** 这意味着每个次要版本的维护周期为 **3 x 3 = 9** 月。例如,如果 `v1.0.0` 在 **2023 年 1 月** 发布, 那么 `v1.1.0` 将在**2023 年 4 月** 发布, `v1.2.0` 将在 **2023 年 7月发布** 。 每个次要版本发布后 9 个月内, bugfixes 会继续合并,相应的补丁版本会继续发布。 - diff --git a/docs/development/git-workflow/commit-messages.md b/docs/development/git-workflow/commit-messages.md deleted file mode 100644 index 3bd71d336..000000000 --- a/docs/development/git-workflow/commit-messages.md +++ /dev/null @@ -1,31 +0,0 @@ -# Commit Messages - -We try our best to follow the [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/#summary) guidelines. - -TL;DR: The commit message should be structured as follows: - -``` -[optional scope]: -[optional body] -[optional footer(s)] -``` - -where "type" can be: - -- `feat`: implements a feature -- `fix`: patches a bug -- `BREAKING CHANGE`: a breaking change. Or append `!` at the end of "feat" or "fix", like `feat!` and `fix!` -- other types are allowed, for example: `build`, `chore`, `ci`, `docs`, `style`, `refactor`, `perf`, `test` - -Both "body" and "footer" are optional; BREAKING CHANGE can be addressed both in the title as well as in the footer. Some examples: - -- `feat: send an email to the customer when a product is shipped` -- `feat!: send an email to the customer when a product is shipped` -- `feat(api): send an email to the customer when a product is shipped` -- `feat(api)!: send an email to the customer when a product is shipped` -- `BREAKING CHANGE: send an email to the customer when a product is shipped` -- ``` - feat!: send an email to the customer when a product is shipped - A detailed description in the body. - BREAKING CHANGE: readdressing the breaking change in the footer. - ``` diff --git a/docs/development/git-workflow/commit-messages.zh.md b/docs/development/git-workflow/commit-messages.zh.md deleted file mode 100644 index e1e09c078..000000000 --- a/docs/development/git-workflow/commit-messages.zh.md +++ /dev/null @@ -1,31 +0,0 @@ -# Commit 信息规范 -我们尽最大的努力遵守[conventional commits](https://www.conventionalcommits.org/zh-hans/v1.0.0/#概述)规范。 - -TL;DR:提交信息应当结构如下: - -``` -[optional scope]: -[optional body] -[optional footer(s)] -``` - -其中的"type"可以是以下内容: - -- `feat`: 实现了一个功能,如例1 -- `fix`: 修复一个错误 -- `BREAKING CHANGE`: 一项重大更改。或者在`feat`或`fix`的结尾处添加`!`,像`feat!`和`fix!`,如例2和例4 -- 其他类型同样被允许,例如:`build`, `chore`, `ci`, `docs`, `style`, `refactor`, `perf`, `test` - -"body"和"footer"都是可选项;重大改变既可以写在标题里,如例5;又可以写在脚注里,如例6。举例如下: - -1. `feat: send an email to the customer when a product is shipped` -2. `feat!: send an email to the customer when a product is shipped` -3. `feat(api): send an email to the customer when a product is shipped` -4. `feat(api)!: send an email to the customer when a product is shipped` -5. `BREAKING CHANGE: send an email to the customer when a product is shipped` -6. ``` - feat!: send an email to the customer when a product is shipped - A detailed description in the body. - BREAKING CHANGE: readdressing the breaking change in the footer. - ``` - diff --git a/docs/development/git-workflow/git-workflow.md b/docs/development/git-workflow/git-workflow.md deleted file mode 100644 index 5950800bd..000000000 --- a/docs/development/git-workflow/git-workflow.md +++ /dev/null @@ -1,77 +0,0 @@ -# Development Workflow with Git - -This document shows the workflow of how to develop DevStream with Git. - -## Step 1 - Fork the repo - -1. Visit the DevStream repo: [https://github.com/devstream-io/devstream](https://github.com/devstream-io/devstream); -2. Click the `Fork` button to create a fork of the DevStream. - -## Step 2 - Clone the repo - -1. Define some basic environment variables - -Please set the appropriate values according to your actual environment. - -```sh -export WORKING_PATH="~/gocode" -export USER="daniel-hutao" -export PROJECT="devstream" -export ORG="devstream-io" -``` - -2. Create your clone locally - -```sh -mkdir -p ${WORKING_PATH} -cd ${WORKING_PATH} -# You can also use the url: git@github.com:${USER}/${PROJECT}.git -# if your ssh configuration is proper -git clone https://github.com/${USER}/${PROJECT}.git -cd ${PROJECT} - -git remote add upstream https://github.com/${ORG}/${PROJECT}.git -# Never push to upstream locally -git remote set-url --push upstream no_push -``` - -3. Confirm the remotes you've set is make sense - -Execute `git remote -v` and you'll see output like below: - -```sh -origin git@github.com:daniel-hutao/devstream.git (fetch) -origin git@github.com:daniel-hutao/devstream.git (push) -upstream https://github.com/devstream-io/devstream (fetch) -upstream no_push (push) -``` - -## Step 3 - Keep your branch in sync - -You will often need to update your local code to keep in sync with upstream - -```sh -git fetch upstream -git checkout main -git rebase upstream/main -``` - -## Step 4 - Coding - -First, you need to pull a new branch, the name is according to your own taste. - -```sh -git checkout -b feat-xxx -``` - -Then start coding. - -## Step 5 - Commit & Push - -```sh -git add -git commit -s -m "some description here" -git push -f origin feat-xxx -``` - -Then you can create a `pr` on GitHub. diff --git a/docs/development/git-workflow/git-workflow.zh.md b/docs/development/git-workflow/git-workflow.zh.md deleted file mode 100644 index af9e4c318..000000000 --- a/docs/development/git-workflow/git-workflow.zh.md +++ /dev/null @@ -1,75 +0,0 @@ -# Git协作工作流 - -这篇文档是关于如何通过GitHub参与DevStream开发的流程。 - -## 第一步 - Fork 仓库 -1. 打开项目仓库: https://github.com/devstream-io/devstream ; -2. 点击 `Fork` 按钮,从DevStream创建一个fork。 - -## 第二步 - Clone 仓库 -1. 定义一些基础的环境变量 - -请根据你的实际情况来设置值。 -```sh -export WORKING_PATH="~/gocode" -export USER="daniel-hutao" -export PROJECT="devstream" -export ORG="devstream-io" -``` - -2. Clone 仓库到你本地 -```sh -mkdir -p ${WORKING_PATH} -cd ${WORKING_PATH} -# You can also use the url: git@github.com:${USER}/${PROJECT}.git -# if your ssh configuration is proper -git clone https://github.com/${USER}/${PROJECT}.git -cd ${PROJECT} - -git remote add upstream https://github.com/${ORG}/${PROJECT}.git -# Never push to upstream locally -git remote set-url --push upstream no_push -``` - -3. 确认你的远程设置是正确的 - -执行`git remote -v` 这个命令,你将看到如下输出: -```sh -origin git@github.com:daniel-hutao/devstream.git (fetch) -origin git@github.com:daniel-hutao/devstream.git (push) -upstream https://github.com/devstream-io/devstream (fetch) -upstream no_push (push) -``` - -## 第三步 - 分支代码保持同步更新 - -你经常需要更新你的本地代码,以便与上游保持同步。 -```sh -git fetch upstream -git checkout main -git rebase upstream/main -``` - -## 第四步 - 编码 - -首先,你需要拉一个新的分支,名字根据你自己的喜好而定。 - -```sh -git checkout -b feat-xxx -``` - -然后开始编码吧。 - -## 第五步 - 提交&推送 - -```sh -git add -git commit -s -m "some description here" -git push -f origin feat-xxx -``` - -然后你就可以在GitHub上创建一个`pr`。 - -## 附录 - -也欢迎阅读我们的博客-[如何参与开源项目 - 细说 GitHub 上的 PR 全过程](https://blog.devstream.io/posts/open-a-pr-in-github/),了解更多 GitHub 协作流程,如解决冲突等。 diff --git a/docs/development/git-workflow/good-first-issues.md b/docs/development/git-workflow/good-first-issues.md deleted file mode 100644 index cf27066ce..000000000 --- a/docs/development/git-workflow/good-first-issues.md +++ /dev/null @@ -1,54 +0,0 @@ -# Good First Issues - -Want to contribute to DevStream (or open-source in general), but not sure where to begin? - -Browse [good first issues](https://github.com/devstream-io/devstream/labels/good%20first%20issue) to start! - -## The "good first issue" Label - -Items marked with the “good first issue” label are intended for _first-time_ contributors. - -### For Contributors - -After a contributor has completed one (or two) "good first issue" items, it's recommended to move on to the "[help wanted](../../contributing_guide.md#find-an-issue)" label, saving the remaining "good first issue" items for other new contributors. - -### For Reviewers - -- Keep an eye on pull requests for "good first issue" and guide the contributors through the [pull request process](../../contributing_guide.md#pull-request-lifecycle). -- Let them know what the next step is. For example: - - move on to another good first issue if they are not completely confident yet - - find "help wanted" issues - - join the community [Slack channel](https://cloud-native.slack.com/archives/C03LA2B8K0A), etc. -- Proactively call out when there is a problem, and provide information on how to fix it. - -All of these make new contributors feel welcome and valued and assure them that they will have an extra level of help with their first contribution. - -_Please make sure that new contributors should not be left to find a reviewer, ping for reviews or bump, understand why the CLA([Contributor License Agreement](https://en.wikipedia.org/wiki/Contributor_License_Agreement))/DCO([Developer Certificate of Origin](https://developercertificate.org/))_ check failed, identify that their build failed due to a flake, etc._ - -## Criteria for "good first issue" Items - -A good test for "good first issue" is: that a new contributor should be able to claim and address the issue, submitting an acceptable pull request without requiring more help. - -Part of an issue's suitability comes from the nature of the issue itself, but the rest is determined by how much context you provide in the issue so that the new contributor can be successful. With that in mind, please do provide as much detail as possible. - -- No Barrier to entry: a new contributor can tackle without advanced setup, or domain knowledge. -- Provides context: if background knowledge is required, this should be explicitly mentioned, and a list of suggested readings included. Do not apply the label to an issue without first editing it to add context. -- Solution explained: the recommended solution is clearly described in the issue. -- Gives examples: link to examples of similar implementations so new contributors have a reference guide for their changes. -- Identifies relevant code: the relevant code and tests to be changed are linked in the issue. -- Ready to test: there are existing tests that can be modified, or existing test cases suitable for copy-pasting. If the area of code doesn't have tests, before labeling the issue, add a test. This prep often makes a great help wanted task! - -## Building a Passionate Community - -If you make someone feel like a part of our community, that it's safe to ask questions, that people will let them know the rules/norms, that their contributions are helpful and appreciated... they will stick around! - -To make our community as welcoming as possible, the following action items are suggested: - -- Encourage new contributors to seek help on the appropriate Slack channels, introduce them, and include them in your conversations. -- Invite them to your project's meetings, introduce them when they attend, and give them a chance to participate. -- Give credit to new contributors so that others get to know them, "Hey, would someone help give a second LGTM on @newperson's first PR on chocolate bunnies?". Mention their work in [Slack](https://cloud-native.slack.com/archives/C03LA2B8K0A) or during a meeting, thank them on Twitter, etc. -- Use all the emojis in your approval or LGTM comment. 💖 🚀 -- Acknowledge and thank them for submitting their first pull request and then let them know that you are here to help. -- Suggest a related help wanted so that can build up experience in an area. -- People are more likely to continue contributing when they know what to expect, what is an acceptable way to ask people for a review and nudge things along when a pull request is stalled. Demonstrate how your project works by helping move their first pull request along. -- If you have time, let the contributor know that they can DM you with questions that they aren’t yet comfortable asking the wider group. diff --git a/docs/development/git-workflow/good-first-issues.zh.md b/docs/development/git-workflow/good-first-issues.zh.md deleted file mode 100644 index df5495755..000000000 --- a/docs/development/git-workflow/good-first-issues.zh.md +++ /dev/null @@ -1,48 +0,0 @@ -# Good First Issues 中文文档 -想参与 DevStream(或任何开源项目),但不知道从哪开始? - -点开 [good first issues](https://github.com/devstream-io/devstream/labels/good%20first%20issue) 列表,开始你的贡献吧! - -## 关于 Good First Issues 标签 -该标签意味着对应的 issue 只能被 _第一次参与贡献的人_ 认领。 - -### 对于贡献者们 -在贡献者完成一个(或两个)“good first issue” 后,建议完成带有“help wanted”标签的 issue,将 "good first issue" 留给其他的新贡献者。 - -### 对于审阅者们 - -- 请密切关注带有 "good first issue" 标签的 PR, 并指导其完成 [Pull Request 流程](https://docs.devstream.io/en/latest/contributing_guide/#pull-request-lifecycle.zh)。 -- 请让他们知道下一步该做什么,如: - - 如果他们不够自信,觉得无法胜任,请向他们推荐更合适的 good first issue。 - - 继续参与 "help wanted" issues 。 - - 参与社区交流,如 [Slack 频道](https://cloud-native.slack.com/archives/C03LA2B8K0A)。 -- 若贡献者的代码有问题,应主动指出,并提供修复所需的相关信息(文字、链接、图片、伪代码等)。 - -所有的行为,都应该让新的贡献者感受到受欢迎与被重视,获得价值感,并向他们保证,他们将在第一次贡献中获得额外的帮助。 - -_请确保不应让贡献者主动去找审阅者,或者出现贡献者频繁催促才能联系到审阅者的情况;了解CLA([贡献者许可协议](https://en.wikipedia.org/wiki/Contributor_License_Agreement))/ DCO([开发者原创证书](https://developercertificate.org/)) 检查失败的原因,确定他们的构建为何失败,等等。_ - -### Good First Issue 的选取标准 -一个可行的定义是:新的贡献者有能力宣布并解决此 issue,提交可接受(合并)的 Pull Request,而不需要过多的帮助。 - -决定一个 issue 能否成为 good first issue,除了 issue 自身的因素外,还取决于你在 issue 中留了多少上下文信息,以增大新的贡献者的参与成功率。所以,在创建一个 issue 的时候,请尽可能地提供更多细节。 - -- 无参与障碍:新的参与者可以不用在安装复杂环境、复杂设置,以及不需要拥有丰富的领域知识的情况下参与进来。 -- 提供上下文:如果需要背景知识,应明确提及,包括建议的阅读列表。在未完成上下文的编辑前,请不要为 issue 打 good first issue 标签。 -- 解决方案说明:如果有推荐的解决方案,应在 issue 内清晰地阐述。 -- 给出示例:链接到类似实现的示例,以便新贡献者获得更多的参考。 -- 标识相关代码:应在 issue 中链接相关的代码与测试。 -- 准备测试:应该有相关的类似的测试存在,以便贡献者可以复制代码或者简单修改一下,写出自己的测试代码。如果这个模块没有测试代码可参考,在打 good first issue 标签前,请添加测试。这种准备工作通常是一项很棒的帮助性任务! - -## 建立充满激情的社区 -如果你让大家认同自己是社区的一部分,所有人都可以自如地提问,社区会告诉他们各种规则与惯例,以及告诉他们的贡献是有帮助的并赞赏他们,他们会愿意留在社区! - -为了让我们的社区更受欢迎,为了让更多的人愿意参与我们的社区,我们建议: - -- 鼓励新的贡献者在合适的 Slack 频道或微信群寻求帮助,介绍他们,并给他们一个参与的机会。 -- 提高新的贡献者的声望以便其他人了解他们。"Hey, 有人想审阅一下这个 @新贡献者 的 PR 并给他个 LGTM(Looks good to me, 指认可此次 PR) 吗?以及,在 Slack、微信群、社区会议时提及他们的贡献,或者在其他社交媒体上等等。 -- 尽可能地多地在 LGTM时,使用表情。如 💖 🚀 -- 确认并感谢他们提交了第一个拉取请求,以及告诉他们,你将尽可能提供帮助。 -- 推荐相关领域的 "help wanted" 以帮助他们能某个领域积累经验。 -- 当人们知道接下来会发生什么时,他们更有可能继续做出贡献。例如,什么样的要求审核的方式是可接受的,以及如何在 PR 停滞时推动事情的发展。通过帮助他们完成第一次 PR 以展示项目是如何运作的。 -- 如果你有时间,请让贡献者知道,如果他们不愿意在群内咨询问题的时候,也可以直接私聊你询问。 diff --git a/docs/development/git-workflow/help-wanted.md b/docs/development/git-workflow/help-wanted.md deleted file mode 100644 index ceb1552a8..000000000 --- a/docs/development/git-workflow/help-wanted.md +++ /dev/null @@ -1,16 +0,0 @@ -# Help Wanted Issues - -These issues should be suitable for someone who has either contributed to the project before, or an experienced developer who is comfortable navigating a new codebase. - -Items marked with the "help wanted" label should meet the following standards: - -- Medium to Low Priority -- Low barrier to entry: this should be harder than "good first issue," but still, the difficulty should be limited to a reasonable level. -- Tractable for new contributors. Documentation on how that type of change should be made should already exist. -- Clearly defined and agreed upon and does not require further discussions. Point it out if that area of code is untested and requires new fixtures. When possible, point to existing code that serves as an example of how to implement the change. -- API/CLI behavior is decided and included in the original issue, for example: "The new command syntax is dtm xxx NAME [--some-flag] [--timeout 5m]", with expected input validations, output, and error handling defined. - -Tips for creating help wanted issues: - -- Only select issues that aren't in key pathways, or must be done quickly: you don't want to put effort into grooming these issues, then end up having to do it yourself because it must be done soon. -- Keep them up-to-date: regularly review these issues, and make sure that they haven't already been implemented, aren't necessary anymore, and the suggested solution or design is still appropriate. diff --git a/docs/development/git-workflow/help-wanted.zh.md b/docs/development/git-workflow/help-wanted.zh.md deleted file mode 100644 index 0bbdbf292..000000000 --- a/docs/development/git-workflow/help-wanted.zh.md +++ /dev/null @@ -1,16 +0,0 @@ -# Help Wanted Issues 中文文档 - -这类 issue 既适用于新贡献者,也适用于一个想尝试探索项目其他模块的有经验的贡献者。 - -带有 "help wanted" 标签的 issue 需要满足以下标准: - -- 此任务在项目中属于中低优先级。 -- 低参与阻碍。这应该比 “good first issue" 难一些,但它的难度仍应被限定在合理的范围内。 -- 对于新贡献者来说易驾驭。项目中应该存在如何进行此类修改的文档。 -- 任务清楚明确,不需要进一步的讨论。如果该区域代码没有被测试过,或者需要新的 bugfix,应当指出。可能的话,给出一些相似的代码例子,作为参考。 -- API/CLI 的具体行为表现应当在原始的 issue 中被决定附在 issue 中。例如:”这个新的命令的语法格式是:dtm xxx NAME [--some-flag] [--timeout 5m]"。并附上期望的输入参数校验、输出、错误处理方式。 - -关于创建 "help wanted issue" 的小提示: - -- 只为那些不在项目规划的关键路径上的 issue 打上此标签,或者不需要马上完成的:如果你不希望把精力放在培养这些 issue 上,却因为它马上要立即完成,最后只能自己做的话。 -- 时刻更新他们。定期复核这些 issue,保证他们还没有被实现、非紧急,以及 issue 上的推荐的解决策略或方案设计仍是合适的。 diff --git a/docs/development/git-workflow/reviewing.md b/docs/development/git-workflow/reviewing.md deleted file mode 100644 index 310a77bbf..000000000 --- a/docs/development/git-workflow/reviewing.md +++ /dev/null @@ -1,70 +0,0 @@ -# Code Reviewing Guide - -This document covers who may review pull requests for this project, and provides guidance on how to perform code reviews that meet our community standards and code of conduct. All reviewers must read this document and agree to follow the project review guidelines. Reviewers who do not follow these guidelines may have their privileges revoked. - -[howto]: https://contribute.cncf.io/maintainers/github/templates/recommended/reviewing/ - -## The Reviewer Role - -The reviewer role is distinct from the maintainer role. - -Reviewers can approve a pull request but they cannot merge it. A maintainer handles final approval and merging the pull request. - -## Values - -All reviewers must abide by the [Code of Conduct](https://github.com/devstream-io/devstream/blob/main/CODE_OF_CONDUCT.md) and are also protected by it. A reviewer should not tolerate poor behavior and is encouraged to report any behavior that violates the Code of Conduct. All of our values listed above are distilled from our Code of Conduct. - -Below are concrete examples of how it applies to code review specifically: - -### Inclusion - -Be welcoming and inclusive. You should proactively ensure that the author is successful. While any particular pull request may not ultimately be merged, overall we want people to have a great experience and be willing to contribute again. Answer the questions they didn't know to ask or offer concrete help when they appear stuck. - -### Sustainability - -Avoid burnout by enforcing healthy boundaries. Here are some examples of how a reviewer is encouraged to act to take care of themselves: - -- Authors should meet baseline expectations when submitting a pull request, such as writing tests. -- If your availability changes, you can step down from a pull request and have someone else assigned. -- If interactions with an author are not following code of conduct, close the PR and raise it up with your Code of Conduct committee or point of contact. It's not your job to coax people into behaving. - -### Trust - -Be trustworthy. During a review, your actions both build and help maintain the trust that the community has placed in this project. Below are examples of ways that we build trust: - -- **Transparency** - If a pull request won't be merged, clearly say why and close it. If a pull request won't be reviewed for a while, let the author know so they can set expectations and understand why it's blocked. -- **Integrity** - Put the project's best interests ahead of personal relationships or company affiliations when deciding if a change should be merged. -- **Stability** - Only merge when then change won't negatively impact project stability. It can be tempting to merge a pull request that doesn't meet our quality standards, for example when the review has been delayed, or because we are trying to deliver new features quickly, but regressions can significantly hurt trust in our project. - -## Process - -- A PR might be automatically assigned according to the OWNERS file. If not, any reviewer can assign the pull request, and set specific labels. -- The project uses GitHub actions for automation, but currently, no extra automation/bots are used for assigning PRs to reviewers. -- The reviewer should wait for automated checks to pass before reviewing. -- Docs/automation related critical changes require reviews from maintainers. Minor tweaks which are clear enough don't require maintainers' review. When in doubt, ask the maintainers to double-check. -- GitHub Actions checks must pass before merging, with the only exception being the commit message lint, which can be improved by squashing and re-editing the message. -- When a PR is stuck, reviewers should either reach out to the contributor to help to move things forward, or report the status to the maintainers. -- In general, it's not recommended for reviewers to commit changes directly to the pull request. Exceptions are: when the original author has abandoned the pull request; the original author is a new contributor and might need help. -- Maintainers can merge their own pull requests after it has been reviewed. -- Maintainers can merge pull requests without review in times of great need. - -## Checklist - -Below are a set of common questions that apply to all pull requests: - -- [ ] Is this PR targeting the correct branch? -- [ ] Does the commit message provide an adequate description of the change? -- [ ] Does the commit message follow the [conventional commit messages specification](https://www.conventionalcommits.org/en/v1.0.0/)? -- [ ] Does the affected code have corresponding tests? -- [ ] Are the changes documented, not just with inline documentation, but also with conceptual documentation such as an overview of a new feature, or task-based documentation like a tutorial? Consider if this change should be announced on DevStream's blog. -- [ ] Does this introduce breaking changes that would require an announcement or bumping the major version? - -## Reading List - -Reviewers are encouraged to read the following articles for help with common reviewer tasks: - -- [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) -- [Project Layout](https://docs.devstream.io/en/latest/development/project-layout/) -- [The Art of Closing: How to closing an unfinished or rejected pull request](https://blog.jessfraz.com/post/the-art-of-closing/) -- [Kindness and Code Reviews: Improving the Way We Give Feedback](https://product.voxmedia.com/2018/8/21/17549400/kindness-and-code-reviews-improving-the-way-we-give-feedback) -- [Code Review Guidelines for Humans: Examples of good and back feedback](https://phauer.com/2018/code-review-guidelines/#code-reviews-guidelines-for-the-reviewer) diff --git a/docs/development/git-workflow/reviewing.zh.md b/docs/development/git-workflow/reviewing.zh.md deleted file mode 100644 index adf726a3e..000000000 --- a/docs/development/git-workflow/reviewing.zh.md +++ /dev/null @@ -1,72 +0,0 @@ -# 代码审查(review)指南 - -本文将介绍谁可以审查此项目的拉取请求(pull requests),并介绍如何如何在代码审查的时候符合 DevStream 社区的标准和代码准则(code of conduct)。 -所有审查者(reviewer)必须阅读本文档并同意遵守项目审查准则。不遵守这些准则的审查者相应的的审查权限可能会被收回。 - -[howto]: https://contribute.cncf.io/maintainers/github/templates/recommended/reviewing/ - -## 审阅者角色 - -审阅者角色不同于维护者(maintainer)角色。 - -审阅者可以赞同(approve)拉取请求,但是没有合并(merge)权限。维护者有赞同权限和拉取请求合入权限。 - -## 价值观 - -所有审查者都必须遵守[代码准则](https://github.com/devstream-io/devstream/blob/main/CODE_OF_CONDUCT.md)并受其保护。 -审查者不应容忍不良行为,并且被鼓励及时报告任何违反代码准则的行为。我们提到的所有价值观均提炼自我们的代码准则。 - -下面是如何基于代码准则开展代码审查工作的具体例子: - -### 包容性 - -你应该保持热情与包容,尽量确保作者的代码被成功合入。当某一个特定的拉取请求没法被合入的时候,不管怎么说我们希望他们能够有一个好的体验并且愿意再次提交贡献。因此为我们应该尽量解答他们的疑问,在他们被阻塞的时候提供相应的帮助。 - -### 可持续性 - -通过一些强制的边界定义来避免懈怠。以下是一些具体的例子,审查者被鼓励做到这些: - -- 作者提交的一个拉取请求应该满足基线期望,比如编写一些测试代码。 -- 如果没有精力继续参与审查工作,你可以退出一个拉取请求的审查工作,并指派给其他审查者。 -- 如果作者的行为不符合代码准则,请关闭PR,并告知行为准则委员会或相关人员。你不用去说服他。 - -### 信任 - -审查工作需要让人信服,你的行为既在构建也在帮助社区呵护项目中的"信任"。以下是我们建立信任的一些方式示例: - -- **透明** - 如果拉取请求不会合并,请清楚地说明原因并关闭它。如果拉取请求在一段时间内不会被审查,请告知作者,以便他们调整期望值并了解被阻塞的原因 -- **诚信** - 在决定是否应合并变更时,将项目的最佳利益置于个人关系或公司隶属关系之前。 -- **稳定** - 仅当更改不会对项目稳定性产生负面影响时,才会合并。合并不符合我们质量标准的拉取请求有时候可能很有诱惑,例如,当审查已经延期了一些时间,或者因为我们试图快速交付新功能,但回归可能会严重损害项目的"信任"。 - -## 流程 - -- 拉取请求可能会根据 OWNERS 文件自动分配。如果没有,任何审阅者都可以认领拉取请求的审查工作,并设置相应的标签。 -- 项目用了 GitHub actions 来实现一些自动化,但目前没有使用额外的自动化/bot等将拉取请求自动分配给审查者。 -- 审查者应等待自动化的 ci 检查通过后再进行审查工作。 -- 文档/自动化相关的重要变更需要维护者进行审查。足够清晰的小调整不需要维护者的审查。如有疑问,请维护人员二次确认。 -- GitHub Actions 检查通过后才能合入拉取请求。唯一的例外是提交消息(commit message)检查失败,这可以通过 squash 操作和重新编辑来修复。 -- 当拉取请求阻塞的时候,审查者应该主动联系贡献者帮助推进工作,或者向维护者报告状态。 -- 通常,不建议审查者将更改直接提交到拉取请求。例外情况是:当原作者放弃拉取请求时;原作者是新的贡献者并且可能需要帮助。 -- 维护者可以在审查完成后合并他们自己的拉取请求。 -- 维护人员可以在必要的时候合并没有经过审查的拉取请求。 - -## 检查清单 - -以下是一组适用于所有拉取请求的常见问题: - -- [ ] 这个拉取请求是否指向正确的分支? -- [ ] 提交消息是否提供了足够的更改描述? -- [ ] 提交消息是否遵循[提交消息规范](https://www.conventionalcommits.org/en/v1.0.0/)? -- [ ] 相关代码是否包含相应的测试? -- [ ] 更改是否有相应的文档,不仅是直接相关的文档,比如有新特性的时候是否提供了相关的概览等概念介绍文档或者相应的基于任务的渐进式教程?思考是否应该在 DevStream 的博客上宣布此变更。 -- [ ] 这是否是一个不先前兼容的版本,从而需要发布一些通知或者发布一个新的版本? - -## 阅读列表 - -我们鼓励审稿人阅读以下文章,这会对常见的审查工作有所帮助: - -- [常规提交](https://www.conventionalcommits.org/en/v1.0.0/) -- [项目结构](https://docs.devstream.io/en/latest/development/project-layout.zh/) -- [关闭的艺术:如何关闭未完成或被拒绝的拉取请求](https://blog.jessfraz.com/post/the-art-of-closing/) -- [善意和代码审查:改进我们反馈的方式](https://product.voxmedia.com/2018/8/21/17549400/kindness-and-code-reviews-improving-the-way-we-give-feedback) -- [代码审查指南:良好反馈和反向反馈的示例](https://phauer.com/2018/code-review-guidelines/#code-reviews-guidelines-for-the-reviewer) diff --git a/docs/development/project-layout.md b/docs/development/project-layout.md deleted file mode 100644 index 9d0f29572..000000000 --- a/docs/development/project-layout.md +++ /dev/null @@ -1,101 +0,0 @@ -# Project Layout - -See [`Standard Go Project Layout`](https://github.com/golang-standards/project-layout) for additional background information. - -More about naming and organizing packages as well as other code structure recommendations: - -* [GopherCon EU 2018: Peter Bourgon - Best Practices for Industrial Programming](https://www.youtube.com/watch?v=PTE4VJIdHPg) -* [GopherCon Russia 2018: Ashley McNamara + Brian Ketelsen - Go best practices.](https://www.youtube.com/watch?v=MzTcsI6tn-0) -* [GopherCon 2017: Edward Muller - Go Anti-Patterns](https://www.youtube.com/watch?v=ltqV6pDKZD8) -* [GopherCon 2018: Kat Zien - How Do You Structure Your Go Apps](https://www.youtube.com/watch?v=oL6JBUk6tj0) - -A Chinese Post about Package-Oriented-Design guidelines and Architecture layer - -* [面向包的设计和架构分层](https://github.com/danceyoung/paper-code/blob/master/package-oriented-design/packageorienteddesign.md) - -## Directories - -### `/cmd` - -Main applications for this project. - -The directory name for each application should match the name of the executable you want to have (e.g., `/cmd/myapp`). - -Don't put a lot of code in the application directory. If you think the code can be imported and used in other projects, then it should live in the `/pkg` directory. If the code is not reusable or if you don't want others to reuse it, put that code in the `/internal` directory. You'll be surprised what others will do, so be explicit about your intentions! - -It's common to have a small `main` function that imports and invokes the code from the `/internal` and `/pkg` directories and nothing else. - -### `/internal` - -Private application and library code. This is the code you don't want others importing in their applications or libraries. Note that this layout pattern is enforced by the Go compiler itself. See the Go 1.4 [`release notes`](https://golang.org/doc/go1.4#internalpackages) for more details. Note that you are not limited to the top level `internal` directory. You can have more than one `internal` directory at any level of your project tree. - -You can optionally add a bit of extra structure to your internal packages to separate your shared and non-shared internal code. It's not required (especially for smaller projects), but it's nice to have visual clues showing the intended package use. Your actual application code can go in the `/internal/app` directory (e.g., `/internal/app/myapp`) and the code shared by those apps in the `/internal/pkg` directory (e.g., `/internal/pkg/myprivlib`). - -### `/pkg` - -Library code that's ok to use by external applications (e.g., `/pkg/mypubliclib`). Other projects will import these libraries expecting them to work, so think twice before you put something here :-) Note that the `internal` directory is a better way to ensure your private packages are not importable because it's enforced by Go. The `/pkg` directory is still a good way to explicitly communicate that the code in that directory is safe for use by others. The [`I'll take pkg over internal`](https://travisjeffery.com/b/2019/11/i-ll-take-pkg-over-internal/) blog post by Travis Jeffery provides a good overview of the `pkg` and `internal` directories and when it might make sense to use them. - -It's also a way to group Go code in one place when your root directory contains lots of non-Go components and directories making it easier to run various Go tools (as mentioned in these talks: [Best Practices for Industrial Programming](https://www.youtube.com/watch?v=PTE4VJIdHPg) from GopherCon EU 2018, [GopherCon 2018: Kat Zien - How Do You Structure Your Go Apps](https://www.youtube.com/watch?v=oL6JBUk6tj0) and [GoLab 2018 - Massimiliano Pippi - Project layout patterns in Go](https://www.youtube.com/watch?v=3gQa1LWwuzk)). - -It's ok not to use it if your app project is really small and where an extra level of nesting doesn't add much value (unless you really want to :-)). Think about it when it's getting big enough and your root directory gets pretty busy (especially if you have a lot of non-Go app components). - -The `pkg` directory origins: The old Go source code used to use `pkg` for its packages and then various Go projects in the community started copying the pattern (see [`this`](https://twitter.com/bradfitz/status/1039512487538970624) Brad Fitzpatrick's tweet for more context). - -### `/vendor` - -Application dependencies (managed manually or by your favorite dependency management tool like the new built-in [`Go Modules`](https://github.com/golang/go/wiki/Modules) feature). The `go mod vendor` command will create the `/vendor` directory for you. Note that you might need to add the `-mod=vendor` flag to your `go build` command if you are not using Go 1.14 where it's on by default. - -Don't commit your application dependencies if you are building a library. - -Note that since [`1.13`](https://golang.org/doc/go1.13#modules) Go also enabled the module proxy feature (using [`https://proxy.golang.org`](https://proxy.golang.org) as their module proxy server by default). Read more about it [here](https://blog.golang.org/module-mirror-launch) to see if it fits all of your requirements and constraints. If it does, then you won't need the `vendor` directory at all. - -## Common Application Directories - -### `/hack` - -The [`/hack`](https://github.com/devstream-io/devstream/blob/main/hack/README.md) directory contains many scripts that ensure continuous development of DevStream. - -### `/build` - -Packaging and Continuous Integration. - -Put your cloud (AMI), container (Docker), OS (deb, rpm, pkg) package configurations and scripts in the `/build/package` directory. - -Put your CI (travis, circle, drone) configurations and scripts in the `/build/ci` directory. Note that some of the CI tools (e.g., Travis CI) are very picky about the location of their config files. Try putting the config files in the `/build/ci` directory linking them to the location where the CI tools expect them (when possible). - -### `/test` - -Additional external test apps and test data. Feel free to structure the `/test` directory anyway you want. For bigger projects it makes sense to have a data subdirectory. For example, you can have `/test/data` or `/test/testdata` if you need Go to ignore what's in that directory. Note that Go will also ignore directories or files that begin with "." or "_", so you have more flexibility in terms of how you name your test data directory. - -## Other Directories - -### `/docs` - -Design and user documents (in addition to your godoc generated documentation). - -### `/examples` - -Examples for your applications and/or public libraries. - -## Directories You Shouldn't Have - -### `/src` - -Some Go projects do have a `src` folder, but it usually happens when the devs came from the Java world where it's a common pattern. If you can help yourself try not to adopt this Java pattern. You really don't want your Go code or Go projects to look like Java :-) - -Don't confuse the project level `/src` directory with the `/src` directory Go uses for its workspaces as described in [`How to Write Go Code`](https://golang.org/doc/code.html). The `$GOPATH` environment variable points to your (current) workspace (by default it points to `$HOME/go` on non-windows systems). This workspace includes the top level `/pkg`, `/bin` and `/src` directories. Your actual project ends up being a sub-directory under `/src`, so if you have the `/src` directory in your project the project path will look like this: `/some/path/to/workspace/src/your_project/src/your_code.go`. Note that with Go 1.11 it's possible to have your project outside of your `GOPATH`, but it still doesn't mean it's a good idea to use this layout pattern. - - -## Badges - -* [Go Report Card](https://goreportcard.com/) - It will scan your code with `gofmt`, `go vet`, `gocyclo`, `golint`, `ineffassign`, `license` and `misspell`. Replace `github.com/golang-standards/project-layout` with your project reference. - - [![Go Report Card](https://goreportcard.com/badge/github.com/golang-standards/project-layout?style=flat-square)](https://goreportcard.com/report/github.com/golang-standards/project-layout) - -* [pkg.go.dev](https://pkg.go.dev) - pkg.go.dev is a new destination for Go discovery & docs. You can create a badge using the [badge generation tool](https://pkg.go.dev/badge). - - [![PkgGoDev](https://pkg.go.dev/badge/github.com/golang-standards/project-layout)](https://pkg.go.dev/github.com/golang-standards/project-layout) - -* Release - It will show the latest release number for your project. Change the github link to point to your project. - - [![Release](https://img.shields.io/github/release/golang-standards/project-layout.svg?style=flat-square)](https://github.com/golang-standards/project-layout/releases/latest) diff --git a/docs/development/project-layout.zh.md b/docs/development/project-layout.zh.md deleted file mode 100644 index e1f54406d..000000000 --- a/docs/development/project-layout.zh.md +++ /dev/null @@ -1,100 +0,0 @@ -# 项目组织结构 - -参见 [`标准 Go 项目布局`](https://github.com/golang-standards/project-layout/blob/master/README_zh.md) 以了解更多背景信息。 - -更多关于命名、包的组织,以及其他代码结构的建议如下: - -* [GopherCon EU 2018: Peter Bourgon - Best Practices for Industrial Programming](https://www.youtube.com/watch?v=PTE4VJIdHPg) -* [GopherCon Russia 2018: Ashley McNamara + Brian Ketelsen - Go best practices.](https://www.youtube.com/watch?v=MzTcsI6tn-0) -* [GopherCon 2017: Edward Muller - Go Anti-Patterns](https://www.youtube.com/watch?v=ltqV6pDKZD8) -* [GopherCon 2018: Kat Zien - How Do You Structure Your Go Apps](https://www.youtube.com/watch?v=oL6JBUk6tj0) - -一篇关于面向包的设计和架构分层的中文文章: - -* [面向包的设计和架构分层](https://github.com/danceyoung/paper-code/blob/master/package-oriented-design/packageorienteddesign.md) - -## 目录 - -### `/cmd` - -存放项目的主要程序。 - -每个程序的目录名称应与期望的最终的可执行文件的名称相匹配(例如 `/cmd/myapp`)。 - -不要在此目录中放大量代码。如果你认为这些代码可以被复用,那么它应该在`/pkg`目录中。如果代码不能被复用,或者你不希望它被别人复用,那么请把这些代码放在`/internal`目录下。你往往无法预测别人会基于你的代码做什么,所以请明确自己的意图,并将代码放到合适的位置。 - -通常会有一个`main`函数,导入并调用`/internal`和`/pkg`目录中的代码,而不是其他函数。 - -### `/internal` - -存放私有程序和库代码。这里是你不希望别人在其应用程序或库中导入的代码。请注意,这种项目组织结构是由 Go 编译器强制执行的。更多细节请参见 Go 1.4 [`release notes`](https://golang.org/doc/go1.4#internalpackages) 。并不是只能在根目录放置`internal`,任何级别的目录下都可以建立`internal`目录。 - -你可以选择性的给你的 internal 包添加一些额外的结构,以分离共享和非共享的内部代码。这不是必须的(特别是对于较小的项目),但可以使包的用途一目了然。应用程序相关代码可以放在`/internal/app`目录下(例如 `/internal/app/myapp`),被其他程序复用的代码可以放在`/internal/pkg`目录下(例如 `/internal/pkg/myprivlib`)。 - -### `/pkg` - -存放可以被外部应用程序使用的库代码(例如 `/pkg/mypubliclib`)。 其他项目会 import 这些库,并且希望它们能够正常运作。所以在你把代码放在这里之前请三思而后行 :-) 请注意,`internal`目录是确保你的私有包不被他人导入的更好的方法,因为它是由 Go 强制执行的。 `/pkg`目录是一个很好的方式,用来明确表达该目录中的代码可以被他人安全使用。Travis Jeffery 发表的[`I'll take pkg over internal`](https://travisjeffery.com/b/2019/11/i-ll-take-pkg-over-internal/)博文介绍了`pkg`和`internal`,以及它们的使用场景。 - -当你的根目录包含大量的非 Go 组件和目录时,它也可以作为一种将 Go 代码分组在同一个地方的方法,以更容易运行各种 Go 工具(正如在这些会谈中提到的:来自 GopherCon EU 2018 的[Best Practices for Industrial Programming](https://www.youtube.com/watch?v=PTE4VJIdHPg)、[GopherCon 2018: Kat Zien - How Do You Structure Your Go Apps](https://www.youtube.com/watch?v=oL6JBUk6tj0)以及[GoLab 2018 - Massimiliano Pippi - Project layout patterns in Go](https://www.youtube.com/watch?v=3gQa1LWwuzk))。 - -如果你的项目非常小,额外的嵌套层次并没有什么意义,除非你真的想这样做:-),完全可以不用它。建议在你的项目越来越大、根目录变得杂乱、特别是当你有很多非 Go 应用组件的时候,再考虑使用 `/pkg`。 - -`pkg`目录的由来:旧的 Go 源代码曾经使用`pkg`来存放包,然后社区中的各种 Go 项目开始复制这种模式(更多背景详见[Brad Fitzpatrick 的 tweet](https://twitter.com/bradfitz/status/1039512487538970624) )。 - -### `/vendor` - -存放程序依赖(手动管理或通过依赖管理工具,如新的内置的[`Go Modules`](https://github.com/golang/go/wiki/Modules)特性)。`go mod vendor`命令将为你创建`/vendor`目录。请注意,如果你的 Go 版本大于等于 1.14,`go build`命令默认开启`mod=vendor`;否则可能要手动开启。 - -如果你正在编写一个库,请不要提交程序的依赖关系。 - -请注意,自从[`1.13`](https://golang.org/doc/go1.13#modules) 版本 Go 也启用了模块代理功能(默认使用[`https://proxy.golang.org`](https://proxy.golang.org)作为其模块代理服务器)。 详情可参阅[这里](https://blog.golang.org/module-mirror-launch),看看它是否符合你所有的要求。如果符合,就不需要`vendor`目录。 - -## 通用程序目录 - -### `/hack` - -[`/hack`](https://github.com/devstream-io/devstream/blob/main/hack/README.md)目录包含许多脚本,确保 DevStream 的持续发展。 - -### `/build` - -打包(构建)和持续集成。 - -把你的云(AMI)、容器(Docker)和操作系统(deb、rpm、pkg)软件包配置和脚本放在`/build/package`目录下。 - -把你的 CI(travis, circle, drone)配置和脚本放在`/build/ci`目录下。请注意,一些 CI 工具(例如Travis CI)对配置文件的位置非常挑剔。可以试着把配置文件放在`/build/ci`目录中,再链接到 CI 工具所期望的位置(如果可能的话)。 - -### `/test` - -额外的外部测试程序和测试数据。你可以随心所欲地构建`/test`目录。对于较大的项目,很有必要建立一个数据子目录是。例如,如果你需要 Go 忽略该目录中的内容,可以创建`/test/data`或`/test/testdata`目录。注意,Go 也会忽略以"."或"_"开头的目录与文件,所以你可以灵活地命名测试数据目录。 - -## 其他目录 - -### `/docs` - -存放设计和用户文件(除 godoc 生成的文件)。 - -### `/examples` - -存放应用程序与库的示例。 - -## 不应该存在的目录 - -### `/src` - -有些 Go 项目确实有一个`src`文件夹,但这通常是由于开发人员之前是 Java 程序员,这在 Java 中是一种常见的模式。但请尽可能不要采用这种 Java 模式,不要把你的 Go 代码或 Go 项目写成 Java 的样子:-) - -不要把项目级的`/src`目录与[`如何编写Go代码`](https://golang.org/doc/code.html)中描述的 Go 用于其工作区的`/src`目录混淆。`$GOPATH`环境变量指向你的(当前)工作空间(在非 Windows 系统上默认指向`$HOME/go`)。这个工作空间包括顶层的`/pkg`、`/bin`和`/src`目录。你的实际项目其实是`/src`下的一个子目录,所以如果你的项目中有`/src`目录,项目路径看起来会是这样:`/some/path/to/workspace/src/your_project/src/your_code.go`。请注意,在 Go 1.11中,你的项目有可能在`GOPATH`之外,但这不意味着使用这种项目组织结构是一个好主意。 - -## 徽章 - -* [Go Report Card](https://goreportcard.com/) - 它会采用`gofmt`、`go vet`、`gocyclo`、`golint`、`ineffassign`、`license`和`misspell`来扫描你的代码。记得把 `github.com/golang-standards/project-layout`替换为你的项目地址。 - - [![Go Report Card](https://goreportcard.com/badge/github.com/golang-standards/project-layout?style=flat-square)](https://goreportcard.com/report/github.com/golang-standards/project-layout) - -* [pkg.go.dev](https://pkg.go.dev) - pkg.go.dev 是一个新的 Go 语言的 package 和 module 的文档中心。你可以用以下方法创建一个[徽章](https://pkg.go.dev/badge)。 - - [![PkgGoDev](https://pkg.go.dev/badge/github.com/golang-standards/project-layout)](https://pkg.go.dev/github.com/golang-standards/project-layout) - -* Release - 它将显示你的项目的最新版本号,改变 github 链接以指向你的项目。 - - [![Release](https://img.shields.io/github/release/golang-standards/project-layout.svg?style=flat-square)](https://github.com/golang-standards/project-layout/releases/latest) diff --git a/docs/images/about-this-mac.png b/docs/images/about-this-mac.png deleted file mode 100644 index 61f9e91cc..000000000 Binary files a/docs/images/about-this-mac.png and /dev/null differ diff --git a/docs/images/architecture-overview.png b/docs/images/architecture-overview.png deleted file mode 100644 index 8d5ec7eca..000000000 Binary files a/docs/images/architecture-overview.png and /dev/null differ diff --git a/docs/images/config_state_resource.png b/docs/images/config_state_resource.png deleted file mode 100644 index 9acae0211..000000000 Binary files a/docs/images/config_state_resource.png and /dev/null differ diff --git a/docs/images/docker-install.png b/docs/images/docker-install.png deleted file mode 100644 index ea95c0d72..000000000 Binary files a/docs/images/docker-install.png and /dev/null differ diff --git a/docs/images/gitops-a.png b/docs/images/gitops-a.png deleted file mode 100644 index e3cdabeca..000000000 Binary files a/docs/images/gitops-a.png and /dev/null differ diff --git a/docs/images/gitops-b.png b/docs/images/gitops-b.png deleted file mode 100644 index c17318970..000000000 Binary files a/docs/images/gitops-b.png and /dev/null differ diff --git a/docs/images/gitops-workflow.png b/docs/images/gitops-workflow.png deleted file mode 100644 index 45bd62258..000000000 Binary files a/docs/images/gitops-workflow.png and /dev/null differ diff --git a/docs/images/golang-download.png b/docs/images/golang-download.png deleted file mode 100644 index 63075366a..000000000 Binary files a/docs/images/golang-download.png and /dev/null differ diff --git a/docs/images/golang-install.png b/docs/images/golang-install.png deleted file mode 100644 index e142280ca..000000000 Binary files a/docs/images/golang-install.png and /dev/null differ diff --git a/docs/images/icon-color.svg b/docs/images/icon-color.svg deleted file mode 100644 index a5624ca34..000000000 --- a/docs/images/icon-color.svg +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/docs/images/kubectl-install.png b/docs/images/kubectl-install.png deleted file mode 100644 index f4d80ec57..000000000 Binary files a/docs/images/kubectl-install.png and /dev/null differ diff --git a/docs/images/minikube-install.png b/docs/images/minikube-install.png deleted file mode 100644 index 1912a01c3..000000000 Binary files a/docs/images/minikube-install.png and /dev/null differ diff --git a/docs/images/quickstart.png b/docs/images/quickstart.png deleted file mode 100644 index a5cdb6eb3..000000000 Binary files a/docs/images/quickstart.png and /dev/null differ diff --git a/docs/images/repo-scaffolding.png b/docs/images/repo-scaffolding.png deleted file mode 100644 index 44ff8922b..000000000 Binary files a/docs/images/repo-scaffolding.png and /dev/null differ diff --git a/docs/images/wechat-group-qr-code.png b/docs/images/wechat-group-qr-code.png deleted file mode 100644 index b5ac1c79b..000000000 Binary files a/docs/images/wechat-group-qr-code.png and /dev/null differ diff --git a/docs/includes/glossary.md b/docs/includes/glossary.md deleted file mode 100644 index 30febdf0f..000000000 --- a/docs/includes/glossary.md +++ /dev/null @@ -1,19 +0,0 @@ -*[dtm]: The commandline tool of DevStream (short for DevsTreaM). -*[Config]: A set of YAML files serving as DevStream's configuration. -*[配置]: 由一个或一组 YAML 文件组成,定义了 DevStream 需要的所有信息。 -*[Plugin]: DevStream uses core-plugin architecture, where the core is a state machine and each plugin handles the CRUD and integration of a certain DevOps tool. -*[Plugins]: DevStream uses core-plugin architecture, where the core is a state machine and each plugin handles the CRUD and integration of a certain DevOps tool. -*[插件]: DevStream 使用 core-plugin 架构,core 用作状态机,插件负责管理 DevOps 工具的 CRUD。 -*[Tool]: A type of DevStream config, corresponding to a DevStream plugin, which does the CRUD and integration of a certain DevOps tool. The concept of Tool is used mainly in the context of Config. -*[Tools]: A type of DevStream config, corresponding to a DevStream plugin, which does the CRUD and integration of a certain DevOps tool. The concept of Tool is used mainly in the context of Config. -*[工具]: DevStream 配置的一种类型。每个工具(Tool)对应了一个 DevStream 插件,它可以安装、配置或集成一些 DevOps 工具。工具这一概念主要用在配置中。 -*[App]: A type of DevStream config, corresponding to a real-world application, for which different tools such as CI/CD can be easily configured. -*[Apps]: A type of DevStream config, corresponding to a real-world application, for which different tools such as CI/CD can be easily configured. -*[应用]: DevStream 配置的一种类型,对应现实中的应用程序,使用应用这种类型的配置可以简化例如 CI/CD 等工具的配置。 -*[PipelineTemplate]: A CI/CD pipeline definition which can be refered to by App. -*[PipelineTemplates]: A CI/CD pipeline definition which can be refered to by App. -*[流水线模板]: CI/CD 流水线的定义,可被应用所引用。 -*[Output]: Each Tool can have some Output (in the format of a key/value map), so that other tools can refer to it as their input. -*[输出]: 每个工具(Tool)可能会有些输出,这是一个 map,可以在配置其他工具时引用 -*[State]: Records the current status of your DevOps platform defined and created by DevStream. -*[状态]: 记录了当前 DevOps 工具链的状态,包括每个工具的配置以及当下状态。 diff --git a/docs/index.md b/docs/index.md deleted file mode 100644 index ed25e798e..000000000 --- a/docs/index.md +++ /dev/null @@ -1,41 +0,0 @@ -# DevStream: Your DevOps Toolchain Manager - -## What is DevStream? - -DevStream is an open-source DevOps toolchain manager that is of the developers, by the developers, for the developers. - -Discover the DevOps practice that suits you best. DevStream will take care of the rest. - -TL;DR: simply watch our demo: - -
- -
- -## What Does DevStream Do - -- Kickoff DevOps in 5 minutes: tired of investigating and integrating all the DevOps tools? Get your open-source, customized DevOps toolchain up and running with one command. -- Build your DevOps toolchain like Lego: boost team productivity by plugging in, replacing, or upgrading the tool of your choice with minimal operational overhead. Unleash the full potential of your toolchain with best practices. -- Embrace new opportunities: say goodbye to vendor lock-in. Never let your toolchain become your bottleneck. Feel the pulse of new DevOps tools, methodologies, and practices. - -## Why Use DevStream - -- Too much effort maintaining every piece of DevOps tool? -- Too challenging (if not impossible) to track who changed what? -- Too much hassle replacing some of your DevOps tools that you would choose "if it ain't broke, don't fix it"? -- Small team, many tools, and way too few DevOps engineers to fully implement your pipelines? - -Worry no more. DevStream got you covered. - -## Under the Hood - -- DevOps toolchain as code: centrally manage your toolchain with version-controlled code, and keep track of everything. -- Core-Plugin architecture: no reinventing the wheel. Reuse or even create a plugin, and connect any tools you like with DevStream. -- Best practice templates: choose our recommended best-practice toolchain template that suits your needs. Implement built-in best practices and share yours with the world. - -## Useful Links - -- [GitHub](https://github.com/devstream-io/devstream) -- [Website](https://www.devstream.io) -- [Blog](https://blog.devstream.io) -- [Community](https://www.devstream.io/community/) diff --git a/docs/index.zh.md b/docs/index.zh.md deleted file mode 100644 index 39cada768..000000000 --- a/docs/index.zh.md +++ /dev/null @@ -1,45 +0,0 @@ -# DevStream:你的 DevOps 工具链管理器 - -## 什么是 DevStream? - -DevStream 是一个开源的 DevOps 工具链管理器,因开发者而生,由开发者开发,为开发者服务。 - -探索最适合你的 DevOps 实践,DevStream 将负责剩余的工作。 - -TL;DR:只需观看以下演示: - -
- -
- -!!! hint "提示" - - 跳转到B站观看清晰度更高。 - -## DevStream 做了什么? - -- 5分钟内启动 DevOps:你是否厌倦了调研并整合所有的 DevOps 组件?现在只需一个命令就能启动并运行你的开源、定制的 DevOps 工具链。 -- 像乐高积木一样构建你的 DevOps 工具链:通过插拔、替换或升级你选择的组件,以最低的开销提高团队生产力;利用最佳实践释放你的工具链的全部潜力。 -- 拥抱新的机遇:告别服务商的限制;不要让你的工具链成为你的瓶颈;感受新 DevOps 工具、方法论和实践的脉搏跳动。 - -## 为什么使用 DevStream? - -- 维护 DevOps 组件的工作量太大? -- 追踪项目状态改变过于复杂? -- 更换 DevOps 组件太麻烦了,你会选择 "如果没坏就不要修"? -- 小团队,大组件,没有 DevOps 工程师来完美实现你的项目流水线? - -无需担心,DevStream 将解决这一切。 - -## 深度揭秘 DevStream - -- DevOps 工具链即代码:使用版本控制下的代码集中管理你的工具链,并追踪一切。 -- Core-Plugin 架构:无需重新造轮子,而是复用甚至创建一个插件,用 DevStream 连接你喜欢的任何组件。 -- 最佳实践模板:选择推荐适合你的最佳实践工具链模板。实现自带的最佳实践,并与世界分享你的最佳实践。 - -## 链接 - -- [GitHub](https://github.com/devstream-io/devstream) -- [官网](https://www.devstream.io) -- [博客](https://blog.devstream.io) -- [社区](https://www.devstream.io/community/) diff --git a/docs/install.md b/docs/install.md deleted file mode 100644 index e79e29fbd..000000000 --- a/docs/install.md +++ /dev/null @@ -1,62 +0,0 @@ -# Installation - -!!! note - Currently supported operating systems and chip architectures: - - 1. Darwin/arm64 - 2. Darwin/amd64 - 3. Linux/amd64 - -## 1 Install dtm binary with script - -In your working directory, run: - -```shell -sh -c "$(curl -fsSL https://download.devstream.io/download.sh)" -``` - -This will download the corresponding `dtm` binary to your working directory according to your OS and chip architecture, and grant the binary execution permission. - -!!! note "Optional" - - Move `dtm` to a place which is in your PATH. For example: `mv dtm /usr/local/bin/`. - -## 2 Download manually from the GitHub Release page - -You could find the latest version of `dtm` on the [Release](https://github.com/devstream-io/devstream/releases/) page and click Download. - -If your browser isn't working properly, "curl" is also a good option: - -```shell -# Version v0.10.3, OS type linux and CPU arch amd64 need to be modified as needed -$ curl -o dtm https://download.devstream.io/v0.10.3/dtm-linux-amd64 -``` - -Note that there are multiple versions of `dtm` available, so you will need to choose the correct version for your operating system and chip architecture. Once downloaded locally, you can choose to rename it, move it to the directory containing `$PATH` and give it executable permissions, for example, on Linux you can do this by running the following command. - -```shell -mv dtm-linux-amd64 /usr/local/bin/dtm -chmod +x dtm -``` - -Then you can verify that the permissions and version of dtm are correct with the following command. - -```shell -$ dtm version -0.10.3 -``` - -## 3 Install dtm with [asdf](https://asdf-vm.com/) - -```shell -# Plugin -asdf plugin add dtm -# Show all installable versions -asdf list-all dtm -# Install specific version -asdf install dtm latest -# Set a version globally (on your ~/.tool-versions file) -asdf global dtm latest -# Now dtm commands are available -dtm help -``` diff --git a/docs/install.zh.md b/docs/install.zh.md deleted file mode 100644 index 40596eb3d..000000000 --- a/docs/install.zh.md +++ /dev/null @@ -1,62 +0,0 @@ -# 安装 - -!!! note "提示" - 当前 dtm 发行版支持如下操作系统与 CPU 架构组合: - - 1. Darwin/arm64 - 2. Darwin/amd64 - 3. Linux/amd64 - -## 1 用脚本安装 - -进入你的工作目录,运行: - -```shell -sh -c "$(curl -fsSL https://download.devstream.io/download.sh)" -``` - -这个命令会根据你的操作系统和芯片架构下载对应的 `dtm` 二进制文件到你的工作目录中,并赋予二进制文件执行权限。 - -!!! note "可选" - - 将 dtm 移动到包含于 PATH 的目录下,比如 `mv dtm /usr/local/bin/`。 - -## 2 从 GitHub Release 页面手动下载安装 - -在 [Release](https://github.com/devstream-io/devstream/releases/) 页面找到当前最新版本 `dtm`,然后点击下载。 - -如果你不方便使用浏览器下载,也可以选择使用 curl: - -```shell -# 版本号 v0.10.3,系统类型 linux 和 CPU 架构 amd64 根据需要灵活修改 -$ curl -o dtm https://download.devstream.io/v0.10.3/dtm-linux-amd64 -``` - -需要注意的是当前 `dtm` 提供了多个版本,你需要根据操作系统和芯片架构选择自己需要的正确版本。下载到本地后,你可以选择将其重命名,移入包含在"$PATH"的目录里并赋予其可执行权限,比如在 Linux 上你可以执行如下命令完成这些操作: - -```shell -mv dtm-linux-amd64 /usr/local/bin/dtm -chmod +x dtm -``` - -接着你可以通过如下命令验证 dtm 的权限以及版本等是否正确: - -```shell -$ dtm version -0.10.3 -``` - -## 3 用 [asdf](https://asdf-vm.com/) 安装 - -```shell -# 安装 dtm 插件 -asdf plugin add dtm -# 列出所有可用版本 -asdf list-all dtm -# 安装特定版本 -asdf install dtm latest -# 设置全局版本 (在 ~/.tool-versions 文件中) -asdf global dtm latest -# 现在你就能使用 dtm 了 -dtm help -``` diff --git a/docs/plugins/argocdapp.md b/docs/plugins/argocdapp.md deleted file mode 100644 index 8561d3041..000000000 --- a/docs/plugins/argocdapp.md +++ /dev/null @@ -1,102 +0,0 @@ -# argocdapp Plugin - -This plugin creates an [Argo CD Application](https://argo-cd.readthedocs.io/en/stable/core_concepts/) custom resource. - -**Notes:** - -- Argo CD itself must have been already installed before the usage of this plugin. - To install Argo CD, use the [helm-installer plugin](./helm-installer/argocd.md). - Or you can use both plugins(argocd+argocdapp) at the same time. - See [GitOps Toolchain](../use-cases/gitops/2-gitops-tools.md) for more info. -- Currently, only the Helm chart is supported when creating the Argo CD application. - -## Usage - -The following content is an example of the "tool file". - -For more information on the main config, the tool file and the var file of DevStream, see [Core Concepts Overview](../core-concepts/overview.md) and [DevStream Configuration](../core-concepts/config.md). - -```yaml ---8<-- "argocdapp.yaml" -``` - -### Automatically Create Helm Configuration - -This plugin can push helm configuration automatically when your `source.path` helm config does not exist so that you can use this plugin with helm configured already. For example: - -```yaml ---- -tools: -- name: go-webapp-argocd-deploy - plugin: argocdapp - dependsOn: ["repo-scaffolding.golang-github"] - options: - app: - name: hello - namespace: argocd - destination: - server: https://kubernetes.default.svc - namespace: default - source: - valuefile: values.yaml - path: charts/go-hello-http - repoURL: https://github.com/devstream-io/testrepo.git - imageRepo: - url: http://test.barbor.com/library - user: test_owner - tag: "1.0.0" -``` - -This config will push the default [helm config](https://github.com/devstream-io/dtm-pipeline-templates/tree/main/argocdapp/helm)](https://github.com/devstream-io/dtm-pipeline-templates/tree/main/argocdapp/helm) to repo [testrepo](https://github.com/devstream-io/testrepo.git), and the generated config will use the image `http://test.barbor.com/library/test_owner/hello:1.0.0` as the initial image for Helm. - -## Use Together with the `repo-scaffolding` Plugin - -This plugin can be used together with the `repo-scaffolding` plugin (see document [here](./repo-scaffolding.md).) - -For example, you can first use `repo-scaffolding` to bootstrap a Golang repo, then use this plugin to set up basic GitHub Actions CI workflows. In this scenario: - -- This plugin can specify `repo-scaffolding` as a dependency, so that the dependency is first satisfied before executing this plugin. -- This plugin can refer to `repo-scaffolding`'s output to reduce copy/paste human error. - -See the example below: - -```yaml ---- -tools: -- name: repo-scaffolding - instanceID: golang-github - options: - destinationRepo: - owner: [[ githubUsername ]] - org: "" - name: [[ repoName ]] - branch: [[ defaultBranch ]] - scmType: github - vars: - imageRepo: "[[ dockerhubUsername ]]/[[ repoName ]]" - sourceRepo: - org: devstream-io - name: dtm-scaffolding-golang - scmType: github -- name: go-webapp-argocd-deploy - plugin: argocdapp - dependsOn: ["repo-scaffolding.golang-github"] - options: - app: - name: hello - namespace: argocd - destination: - server: https://kubernetes.default.svc - namespace: default - source: - valuefile: values.yaml - path: charts/go-hello-http - repoURL: ${{repo-scaffolding.golang-github.outputs.repoURL}} -``` - -In the example above: - -- We put `repo-scaffolding.golang-github` as dependency by using the `dependsOn` keyword. -- We used `repo-scaffolding.golang-github`'s output as input for the `github-actions` plugin. - -Pay attention to the `${{ xxx }}` part in the example. `${{ TOOL_NAME.PLUGIN.outputs.var}}` is the syntax for using an output. diff --git a/docs/plugins/argocdapp.zh.md b/docs/plugins/argocdapp.zh.md deleted file mode 100644 index c1f2a680e..000000000 --- a/docs/plugins/argocdapp.zh.md +++ /dev/null @@ -1,45 +0,0 @@ -# argocdapp 插件 - -该插件会创建一个在 Kubernetes 上 [Argo CD Application](https://argo-cd.readthedocs.io/en/stable/core_concepts/) 的自定义资源。 - -**注意:** - -- 在使用该插件前需要先安装 Argocd CD。你可以使用 [helm-installer 插件](./helm-installer/argocd.md) 来安装它。 -- 目前该插件只支持 Helm chart 的配置方式。 - -## 用例 - -以下内容是该插件的示例配置文件。 - -```yaml ---8<-- "argocdapp.yaml" -``` - -### 自动创建 Helm 配置 - -如果你不想要自己创建 Helm 配置,该插件支持把 Devstream 提供的默认 Helm 配置上传到 `source.path` 的配置路径上,这样你就可以直接使用该插件。配置示例如下: - -```yaml ---- -tools: -- name: go-webapp-argocd-deploy - plugin: argocdapp - dependsOn: ["repo-scaffolding.golang-github"] - options: - app: - name: hello - namespace: argocd - destination: - server: https://kubernetes.default.svc - namespace: default - source: - valuefile: values.yaml - path: charts/go-hello-http - repoURL: https://github.com/devstream-io/testrepo.git - imageRepo: - url: http://test.barbor.com/library - user: test_owner - tag: "1.0.0" -``` - -这个示例配置将会把 [Helm 配置](https://github.com/devstream-io/dtm-pipeline-templates/tree/main/argocdapp/helm) 上传到 [testrepo](https://github.com/devstream-io/testrepo.git) 仓库中,生成的 Helm 配置会使用 `http://test.barbor.com/library/test_owner/hello:1.0.0` 作为 Helm 应用的启动镜像。 \ No newline at end of file diff --git a/docs/plugins/ci-generic.md b/docs/plugins/ci-generic.md deleted file mode 100644 index 0668ca315..000000000 --- a/docs/plugins/ci-generic.md +++ /dev/null @@ -1,63 +0,0 @@ -# ci-generic Plugin - -This plugin installs `CI` script in `GitLib`/`GitHub` repo from a local or remote url. - -## Usage - -The following content is an example of the "tool file". - -For more information on the main config, the tool file and the var file of DevStream, see [Core Concepts Overview](../core-concepts/overview.md) and [DevStream Configuration](../core-concepts/config.md). - -``` yaml ---8<-- "ci-generic.yaml" -``` - -**Notes:** - -- `projectRepo` config option represents codebase location; for more info, you can refer to [SCM Config](./scm-option.md). -- `ci.localPath` and `ci.remoteURL` can't be empty at the same time. -- if your `projectRepo.scmType` is `gitlab`, the `ci.type` is not allowed to be `github-actions`. -- if your `projectRepo.scmType` is `github`, the `ci.type` is not allowed to be `gitlab-ci`. - -## Example - -### Local WorkFlows With Github - -```yaml -tools: -- name: ci-generic - instanceID: test-github - options: - ci: - localPath: workflows - type: github - projectRepo: - owner: devstream - org: "" - name: test-repo - branch: main - scmType: github -``` - -This config will put local workflows directory to GitHub repo's .github/workflows directory. - -### Remote Jenkinsfile With Gitlab - -```yaml -tools: -- name: ci-generic - instanceID: test-gitlab - options: - ci: - remoteURL : https://raw.githubusercontent.com/DeekshithSN/Jenkinsfile/inputTest/Jenkinsfile - type: jenkins - projectRepo: - owner: root - org: "" - name: test-repo - branch: main - scmType: gitlab - baseURL: http://127.0.0.1:30000 -``` - -This config will put files from [remote](https://raw.githubusercontent.com/DeekshithSN/Jenkinsfile/inputTest/Jenkinsfile)](https://raw.githubusercontent.com/DeekshithSN/Jenkinsfile/inputTest/Jenkinsfile) to GitLab repo. diff --git a/docs/plugins/ci-generic.zh.md b/docs/plugins/ci-generic.zh.md deleted file mode 100644 index 08a75fdc9..000000000 --- a/docs/plugins/ci-generic.zh.md +++ /dev/null @@ -1,111 +0,0 @@ -# ci-generic 插件 - -这个插件可以基于本地或者远程的文件在 GitLab/GitHub 安装 CI 配置 - -## 用例 - -下面的配置文件展示的是"tool file"的内容。 - -关于更多关于DevStream的主配置、tool file、var file的信息,请阅读[核心概念概览](../core-concepts/overview.zh.md)和[DevStream配置](../core-concepts/config.zh.md). - -``` yaml ---8<-- "ci-generic.yaml" -``` - -**注意事项:** - -- `projectRepo` 配置字段用于表示代码仓库的配置信息,具体配置可查看[SCM配置项](./scm-option.zh.md) -- `ci.configContents` 和 `ci.configLocation` 不能同时为空。 -- 如果你配置了 `projectRepo.scmType` 为 `github`,那 `ci.type` 就不能是 `gitlab-ci`。 -- 如果你配置了 `projectRepo.scmType` 为 `gitlab`,那 `ci.type` 就不能是 `github-actions`。 - -## 示例 - -### 使用本地的 Workflows 目录 - -```yaml -tools: -- name: ci-generic - instanceID: test-github - options: - ci: - configLocation: workflows - type: github - projectRepo: - owner: devstream - org: "" - name: test-repo - branch: main - scmType: github -``` - -这个配置将会把本地当前运行环境下的 workflows 目录放置于 GitHub 的 `.github/workflows` 目录。 - -### 使用 HTTP 获取远程的CI文件 - -```yaml -tools: -- name: ci-generic - instanceID: test-gitlab - options: - ci: - configLocation : https://raw.githubusercontent.com/DeekshithSN/Jenkinsfile/inputTest/Jenkinsfile - type: jenkins - projectRepo: - owner: root - org: "" - name: test-repo - branch: main - scmType: gitlab - baseURL: http://127.0.0.1:30000 -``` - -这个配置将会把[URL](https://raw.githubusercontent.com/DeekshithSN/Jenkinsfile/inputTest/Jenkinsfile) 中的 Jenkinsfile 文件置于 GitLab 的仓库。 - -### 使用Github仓库中的CI文件 -```yaml -tools: -- name: ci-generic - instanceID: test-gitlab - options: - ci: - configLocation : git@github.com:devstream-io/devstream.git//staging/dtm-jenkins-pipeline-example/general - type: jenkins - projectRepo: - owner: root - org: "" - name: test-repo - branch: main - scmType: gitlab - baseURL: http://127.0.0.1:30000 -``` - -这个配置将会搜索[devstream 仓库](https://github.com/devstream-io/devstream)下的staging/dtm-jenkins-pipeline-example/general 目录,获取到目录下的 Jenkinsfile,置于 gitlab 仓库内。 - -### 在Devstream中直接配置CI文件 - -```yaml -tools: -- name: ci-generic - instanceID: test-gitlab - options: - ci: - configContents: - pr.yaml: | -name: GitHub Actions Demo -run-name: ${{ github.actor }} is testing out GitHub Actions 🚀 -on: [push] -jobs: - Explore-GitHub-Actions: - runs-on: ubuntu-latest - steps: - - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event." - projectRepo: - owner: test-user - org: "" - name: test-repo - branch: main - scmType: github -``` - -这个配置将会在用户的Github仓库`test-user/test-repo`下创建`.github/workflows/pr.yaml`文件。 diff --git a/docs/plugins/devlake-config.md b/docs/plugins/devlake-config.md deleted file mode 100644 index 8365b2a98..000000000 --- a/docs/plugins/devlake-config.md +++ /dev/null @@ -1,9 +0,0 @@ -# devlake-config plugin - -TODO(dtm): Add your document here. - -## Usage - -``` yaml ---8<-- "devlake-config.yaml" -``` diff --git a/docs/plugins/devlake-config.zh.md b/docs/plugins/devlake-config.zh.md deleted file mode 100644 index c6496fb55..000000000 --- a/docs/plugins/devlake-config.zh.md +++ /dev/null @@ -1,9 +0,0 @@ -# devlake-config 插件 - -TODO(dtm): 在这里添加文档. - -## 用例 - -``` yaml ---8<-- "devlake-config.yaml" -``` diff --git a/docs/plugins/github-actions.md b/docs/plugins/github-actions.md deleted file mode 100644 index f1128ed35..000000000 --- a/docs/plugins/github-actions.md +++ /dev/null @@ -1,14 +0,0 @@ -# github-actions plugin - -This plugin is used to create `Github Workflows` in your project. - -## Usage - -``` yaml ---8<-- "github-actions.yaml" -``` - -**Notes:** - -- `scm` config option represents codebase location; for more info, you can refer to [SCM Config](./scm-option.md). -- The `pipeline` config option controls `CI` stages; you can refer to [Pipeline Config](./pipeline.md) for more info. diff --git a/docs/plugins/github-actions.zh.md b/docs/plugins/github-actions.zh.md deleted file mode 100644 index e02a0976c..000000000 --- a/docs/plugins/github-actions.zh.md +++ /dev/null @@ -1,16 +0,0 @@ -# github-actions 插件 - -_该插件用于在项目中创建 Github Action Workflows。_ - -## 用例 - -下面的内容是一个示例配置文件用于创建 Github Workflows: - -``` yaml ---8<-- "github-actions.yaml" -``` - -**注意:** - -- `scm` 配置字段用于表示代码仓库的配置信息,具体配置可查看[SCM配置项](./scm-option.zh.md)。 -- `pipeline` 选项项用于控制 `CI` 流程中的各个阶段,具体配置可查看文档[pipline配置项](pipeline.zh.md)。 \ No newline at end of file diff --git a/docs/plugins/gitlab-ce-docker.md b/docs/plugins/gitlab-ce-docker.md deleted file mode 100644 index cca158893..000000000 --- a/docs/plugins/gitlab-ce-docker.md +++ /dev/null @@ -1,87 +0,0 @@ -# gitlab-ce-docker Plugin - -This plugin installs [GitLab](https://about.gitlab.com/) CE(Community Edition) on Docker. - -_NOTICE: currently, this plugin support Linux only._ - -## Background - -GitLab officially provides an image [gitlab-ce](https://registry.hub.docker.com/r/gitlab/gitlab-ce). We can use this image to start a container: - -```shell -docker run --detach \ - --hostname gitlab.example.com \ - --publish 443:443 --publish 80:80 --publish 22:22 \ - --name gitlab \ - --restart always \ - --volume $GITLAB_HOME/config:/etc/gitlab \ - --volume $GITLAB_HOME/logs:/var/log/gitlab \ - --volume $GITLAB_HOME/data:/var/opt/gitlab \ - --shm-size 256m \ - gitlab/gitlab-ce:rc -``` - -The variable `$GITLAB_HOME` here pointing to the directory where the configuration, logs, and data files will reside. - -We can set this variable by the `export` command: - -```shell -export GITLAB_HOME=/srv/gitlab -``` - -The GitLab container uses host mounted volumes to store persistent data: - -| Local location |Container location | Usage | -| --------------------- | ----------------- | ------------------------------------------ | -| `$GITLAB_HOME/data` | `/var/opt/gitlab` | For storing application data | -| `$GITLAB_HOME/logs` | `/var/log/gitlab` | For storing logs | -| `$GITLAB_HOME/config` | `/etc/gitlab` | For storing the GitLab configuration files | - -So, we can customize the following configurations: - -1. hostname -2. host port -3. persistent data path -4. docker image tag - -## Configuration - -Note: -1. the user you are using must be `root` or in the `docker` group; -2. `https` isn't supported for now. - -The following content is an example of the "tool file". - -For more information on the main config, the tool file and the var file of DevStream, see [Core Concepts Overview](../core-concepts/overview.md) and [DevStream Configuration](../core-concepts/config.md). - -```yaml ---8<-- "gitlab-ce-docker.yaml" -``` - -## Some Commands That May Help - -- clone code - -```shell -export hostname=YOUR_HOSTNAME -export username=YOUR_USERNAME -export project=YOUR_PROJECT_NAME -``` - -1. ssh - -```shell -# port is 22 -git clone git@${hostname}/${username}/${project}.git -# port is not 22, 2022 as a sample -git clone ssh://git@${hostname}:2022/${username}/${project}.git -``` - -2. http - -```shell -# port is 80 -git clone http://${hostname}/${username}/${project}.git -# port is not 80, 8080 as a sample -git clone http://${hostname}:8080/${username}/${project}.git -``` diff --git a/docs/plugins/gitlab-ce-docker.zh.md b/docs/plugins/gitlab-ce-docker.zh.md deleted file mode 100644 index ddd286345..000000000 --- a/docs/plugins/gitlab-ce-docker.zh.md +++ /dev/null @@ -1,85 +0,0 @@ -# gitlab-ce-docker 插件 - -这个插件用于以 Docker 的方式安装 [GitLab](https://about.gitlab.com/) CE(社区版)。 - -_注意:目前本插件仅支持 Linux。_ - -## 背景知识 - -GitLab 官方提供了 [gitlab-ce](https://registry.hub.docker.com/r/gitlab/gitlab-ce) 镜像,通过这个镜像我们可以实现类似这样的命令来启动一个 GitLab 容器: - -```shell -docker run --detach \ - --hostname gitlab.example.com \ - --publish 443:443 --publish 80:80 --publish 22:22 \ - --name gitlab \ - --restart always \ - --volume $GITLAB_HOME/config:/etc/gitlab \ - --volume $GITLAB_HOME/logs:/var/log/gitlab \ - --volume $GITLAB_HOME/data:/var/opt/gitlab \ - --shm-size 256m \ - gitlab/gitlab-ce:rc -``` - -其中 $GITLAB_HOME 表示的是本地存储卷路径,比如我们可以通过 export 命令来设置这个变量: - -```shell -export GITLAB_HOME=/srv/gitlab -``` - -在上述命令中,我们可以看到这个容器使用了3个存储卷,含义分别如下: - -| 本地路径 | 容器内路径 | 用途 | -| --------------------- | ----------------- | ----------------- | -| `$GITLAB_HOME/data` | `/var/opt/gitlab` | 保存应用数据 | -| `$GITLAB_HOME/logs` | `/var/log/gitlab` | 保存日志 | -| `$GITLAB_HOME/config` | `/etc/gitlab` | 保存 GitLab 配置文件 | - -在此基础上,我们可以自定义如下一些配置: - -1. hostname -2. 本机端口 -3. 存储卷路径 -4. 镜像版本 - -## 配置 - -注意: -1. 你使用的用户必须是 `root` 或者在 `docker` 用户组里; -2. 目前暂不支持 `https` 方式访问 GitLab。 - -下面的配置文件展示的是"tool file"的内容。 - -关于更多关于DevStream的主配置、tool file、var file的信息,请阅读[核心概念概览](../core-concepts/overview.zh.md)和[DevStream配置](../core-concepts/config.zh.md). - -```yaml ---8<-- "gitlab-ce-docker.yaml" -``` - -## 一些可能有用的命令 - -- 克隆项目 - -```shell -export hostname=YOUR_HOSTNAME -export username=YOUR_USERNAME -export project=YOUR_PROJECT_NAME -``` - -1. ssh 方式 - -```shell -# port is 22 -git clone git@${hostname}/${username}/${project}.git -# port is not 22, 2022 as a sample -git clone ssh://git@${hostname}:2022/${username}/${project}.git -``` - -2. http 方式 - -```shell -# port is 80 -git clone http://${hostname}/${username}/${project}.git -# port is not 80, 8080 as a sample -git clone http://${hostname}:8080/${username}/${project}.git -``` diff --git a/docs/plugins/gitlab-ce-docker/gitlab-ce-images.txt b/docs/plugins/gitlab-ce-docker/gitlab-ce-images.txt deleted file mode 100644 index 7759ed737..000000000 --- a/docs/plugins/gitlab-ce-docker/gitlab-ce-images.txt +++ /dev/null @@ -1,2 +0,0 @@ -##harbor-images -gitlab/gitlab-ce:rc diff --git a/docs/plugins/gitlab-ci.md b/docs/plugins/gitlab-ci.md deleted file mode 100644 index 274f8ff52..000000000 --- a/docs/plugins/gitlab-ci.md +++ /dev/null @@ -1,22 +0,0 @@ -# gitlab-ci Plugin - -This plugin creates GitLab CI workflow. - -It downloads a template of your choice, renders it with provided parameters, and creates a GitLab CI file for your repo. - -## Usage - -The following content is an example of the "tool file". - -For more information on the main config, the tool file, and the var file of DevStream, see [Core Concepts Overview](../core-concepts/overview.md) and [DevStream Configuration](../core-concepts/config.md). - -Plugin config example: - -```yaml ---8<-- "gitlab-ci.yaml" -``` - -**Notes:** - -- `scm` config option represents codebase location; for more info, you can refer to [SCM Config](./scm-option.md). -- The `pipeline` config option controls `CI` stages; you can refer to [Pipeline Config](./pipeline.md) for more info. diff --git a/docs/plugins/gitlab-ci.zh.md b/docs/plugins/gitlab-ci.zh.md deleted file mode 100644 index b13bb78f4..000000000 --- a/docs/plugins/gitlab-ci.zh.md +++ /dev/null @@ -1,15 +0,0 @@ -# gitlab-ci 插件 - -该插件用于在使用 gitlab 的项目仓库中创建 Gitlab CI 并运行对应项目的 gitlab runner。 - -### 配置项 -下面的内容是一个示例配置文件用于创建 Gitlab CI: - -``` yaml ---8<-- "gitlab-ci.yaml" -``` - -**注意:** - -- `scm` 配置字段用于表示代码仓库的配置信息,具体配置可查看[SCM配置项](./scm-option.zh.md)。 -- `pipeline` 选项项用于控制 `CI` 流程中的各个阶段,具体配置可查看文档[pipline配置项](pipeline.zh.md)。 diff --git a/docs/plugins/harbor-docker.md b/docs/plugins/harbor-docker.md deleted file mode 100644 index 025209692..000000000 --- a/docs/plugins/harbor-docker.md +++ /dev/null @@ -1,13 +0,0 @@ -# harbor-docker Plugin - -// TODO(daniel-hutao): I'll add the docs here after Chinese docs be stable. - -## Usage - -The following content is an example of the "tool file". - -For more information on the main config, the tool file and the var file of DevStream, see [Core Concepts Overview](../core-concepts/overview.md) and [DevStream Configuration](../core-concepts/config.md). - -``` yaml ---8<-- "harbor-docker.yaml" -``` diff --git a/docs/plugins/harbor-docker.zh.md b/docs/plugins/harbor-docker.zh.md deleted file mode 100644 index 15f1ca95c..000000000 --- a/docs/plugins/harbor-docker.zh.md +++ /dev/null @@ -1,16 +0,0 @@ -# harbor-docker 插件 - -TODO(daniel-hutao): -- 支持 docker-compose 安装配置 -- 支持 https -- 健壮性逻辑:判断机器上没有正在运行的 harbor - -## 用例 - -下面的配置文件展示的是"tool file"的内容。 - -关于更多关于DevStream的主配置、tool file、var file的信息,请阅读[核心概念概览](../core-concepts/overview.zh.md)和[DevStream配置](../core-concepts/config.zh.md). - -``` yaml ---8<-- "harbor-docker.yaml" -``` diff --git a/docs/plugins/helm-installer/argocd.md b/docs/plugins/helm-installer/argocd.md deleted file mode 100644 index 0718c6bff..000000000 --- a/docs/plugins/helm-installer/argocd.md +++ /dev/null @@ -1,26 +0,0 @@ -# Install Argo CD with DevStream - -## InstanceID Prefix - -The `instanceID` prefix must be `argocd`, the minimum tools configuration example: - -```yaml -tools: -- name: helm-installer - instanceID: argocd -``` - -## Default Configs - -| key | default value | description | -| ---------------- | ------------------------------------ | ------------------------------------------------ | -| chart.chartPath | "" | local chart path | -| chart.chartName | argo/argo-cd | chart name | -| chart.version | "" | chart version | -| chart.timeout | 10m | this config will wait 10 minutes to deploy Argo CD | -| chart.upgradeCRDs | true | default update CRD config | -| chart.releaseName | argocd | helm release name | -| chart.namespace | argocd | namespace where helm to deploy | -| chart.wait | true | whether to wait until installation is complete | -| repo.url | https://argoproj.github.io/argo-helm | helm official repo address | -| repo.name | argo | helm repo name | diff --git a/docs/plugins/helm-installer/argocd.zh.md b/docs/plugins/helm-installer/argocd.zh.md deleted file mode 100644 index d2cb41c9e..000000000 --- a/docs/plugins/helm-installer/argocd.zh.md +++ /dev/null @@ -1,26 +0,0 @@ -# 使用 DevStream 部署 Argo CD - -## 前缀匹配 - -`instanceID` 的前缀需要是 `argocd`,最小化 tools 配置示例: - -```yaml -tools: -- name: helm-installer - instanceID: argocd -``` - -## 默认配置 - -| 配置项 | 默认值 | 描述 | -| ---- | ---- | ---- | -| chart.chartPath | "" | 本地 chart 包路径 | -| chart.chartName | argo/argo-cd | chart 包名称 | -| chart.version | "" | chart 包版本 | -| chart.timeout | 10m | helm install 的超时时间 | -| chart.upgradeCRDs | true | 是否更新 CRDs(如果有) | -| chart.releaseName | argocd | helm 发布名称 | -| chart.namespace | argocd | 部署的命名空间 | -| chart.wait | true | 是否等待部署完成 | -| repo.url | https://argoproj.github.io/argo-helm | helm 仓库地址 | -| repo.name | argo | helm 仓库名 | diff --git a/docs/plugins/helm-installer/artifactory.md b/docs/plugins/helm-installer/artifactory.md deleted file mode 100644 index 3c8f3578c..000000000 --- a/docs/plugins/helm-installer/artifactory.md +++ /dev/null @@ -1,59 +0,0 @@ -# Install Artifactory with DevStream - -## InstanceID Prefix - -The `instanceID` prefix must be `artifactory`, the minimum tools configuration example: - -```yaml -tools: -- name: helm-installer - instanceID: artifactory -``` - -## Default Configs - -| key | default value | description | -| ---- | ---- | ---- | -| chart.chartPath | "" | local chart path | -| chart.chartName | jfrog/artifactory | chart name | -| chart.timeout | 10m | this config will wait 10 minutes to deploy | -| chart.releaseName | artifactory | helm release name | -| chart.upgradeCRDs | true | default update CRD config | -| chart.wait | true | whether to wait until installation is complete | -| chart.namespace | artifactory | namespace where helm to deploy | -| repo.url | https://charts.jfrog.io | offical helm repo address | -| repo.name | jfrog | helm repo name | - -## Test/Local Dev Environment - -If you want to **test the plugin locally**, The following `valuesYaml` configuration can be used - -```yaml -valuesYaml: | - artifactory: - service: - type: NodePort - nodePort: 30002 - nginx: - enabled: false -``` - -In this configuration - -- Postgresql dependencies are automatically created. -- local disks on machines in the cluster are defaulted used for data mounting. -- Using `nodePort` to expose service, You can access `artifactory` by domain `http://{{k8s node IP}}:30002`. The default account name and password are admin/password (please replace the default account password in the production environment). - -## Production Environment - -### External Storage - -- PostgreSQL: Set the `database.url` to Postgresql's address. More info can be found in [Config](https://www.jfrog.com/confluence/display/JFROG/Configuring+the+Database). - -### Disk Storage - -You can set `customVolumes` and `customVolumeMounts` for this service. More info can be found in [Config](https://www.jfrog.com/confluence/display/JFROG/Configuring+the+Filestore). - -### Network Config - -This plugin support `Ingress`, `ClusterIP`, `NodePort` and `LoadBalancer` , You can give choice to your needs. diff --git a/docs/plugins/helm-installer/artifactory.zh.md b/docs/plugins/helm-installer/artifactory.zh.md deleted file mode 100644 index 3bc0c9d48..000000000 --- a/docs/plugins/helm-installer/artifactory.zh.md +++ /dev/null @@ -1,60 +0,0 @@ -# 使用 DevStream 部署 Artifactory - -## 前缀匹配 - -`instanceID` 的前缀需要是 `artifactory`,最小化 tools 配置示例: - -```yaml -tools: -- name: helm-installer - instanceID: artifactory -``` - -## 默认配置 - -| key | default value | description | -| ---- | ---- | ---- | -| chart.chartPath | "" | 本地 chart 包路径 | -| chart.chartName | jfrog/artifactory | chart 名称 | -| chart.version | "" | chart 版本 | -| chart.timeout | 10m | 等待部署成功的时间 | -| chart.upgradeCRDs | true | 默认更新 CRD 配置(如果存在的话) | -| chart.releaseName | artifactory | helm 发布名称 | -| chart.namespace | artifactory | helm 部署的命名空间名称 | -| chart.wait | true | 是否等待部署完成 | -| repo.url | https://charts.jfrog.io | helm 官方仓库地址 | -| repo.name | jfrog | helm 仓库名 | - -## 测试环境 - -如果你想在**本地测试插件**, 可以使用如下 `valuesYaml` 配置。 - -```yaml -valuesYaml: | - artifactory: - service: - type: NodePort - nodePort: 30002 - nginx: - enabled: false -``` - -在该配置下 - -- helm 会自动创建依赖的 Postgresql; -- 数据挂载的磁盘默认会使用集群上机器的本地磁盘; -- 通过 `NodePort` 对外暴露服务,可使用 `http://{{k8s 节点ip}}:30002` 域名来访问,默认账号名密码为 admin/password (生产环境请替换默认账号密码)。 - -## 生产环境 - -### 外部存储 - -- PostgreSQL:设置 `database.url` 来设置数据库地址,具体配置可参考 [Config](https://www.jfrog.com/confluence/display/JFROG/Configuring+the+Database) 中的选项。 - -### 磁盘存储 - -可以设置 `customVolumes` 和 `customVolumeMounts` 来配置挂载磁盘,具体配置可参考 [Config](https://www.jfrog.com/confluence/display/JFROG/Configuring+the+Filestore)。 - -### 网络层配置 - -该插件支持 `Ingress`, `ClusterIP`, `NodePort`, `LoadBalancer` 对外暴露的模式,可以基于需求进行选择。 diff --git a/docs/plugins/helm-installer/devlake.md b/docs/plugins/helm-installer/devlake.md deleted file mode 100644 index 7cdcc1c79..000000000 --- a/docs/plugins/helm-installer/devlake.md +++ /dev/null @@ -1,26 +0,0 @@ -# Install DevLake with DevStream - -## InstanceID Prefix - -The `instanceID` prefix must be `devlake`, the minimum tools configuration example: - -```yaml -tools: -- name: helm-installer - instanceID: devlake -``` - -## Default Configs - -| key | default value | description | -| ---------------- | ------------------------------------ | ------------------------------------------------ | -| chart.chartPath | "" | local chart path | -| chart.chartName | "devlake/devlake | chart name | -| chart.version | "" | chart version | -| chart.timeout | 10m | this config will wait 10 minutes to deploy DevLake | -| chart.upgradeCRDs | true | default update CRD config | -| chart.releaseName | devlake | helm release name | -| chart.namespace | devlake | namespace where helm to deploy | -| chart.wait | true | whether to wait until installation is complete | -| repo.url | https://apache.github.io/incubator-devlake-helm-chart | helm official repo address | -| repo.name | devlake | helm repo name | diff --git a/docs/plugins/helm-installer/devlake.zh.md b/docs/plugins/helm-installer/devlake.zh.md deleted file mode 100644 index d2a36f1b7..000000000 --- a/docs/plugins/helm-installer/devlake.zh.md +++ /dev/null @@ -1,26 +0,0 @@ -# 使用 DevStream 部署 DevLake - -## 前缀匹配 - -`instanceID` 的前缀需要是 `devlake`,最小化 tools 配置示例: - -```yaml -tools: -- name: helm-installer - instanceID: devlake -``` - -## 默认配置 - -| 配置项 | 默认值 | 描述 | -| ---- | ---- | ---- | -| chart.chartPath | "" | 本地 chart 包路径 | -| chart.chartName | devlake/devlake | chart 包名称 | -| chart.version | "" | chart 包版本 | -| chart.timeout | 10m | helm install 的超时时间 | -| chart.upgradeCRDs | true | 是否更新 CRDs(如果有) | -| chart.releaseName | devlake | helm 发布名称 | -| chart.namespace | devlake | 部署的命名空间 | -| chart.wait | true | 是否等待部署完成 | -| repo.url | https://apache.github.io/incubator-devlake-helm-chart | helm 仓库地址 | -| repo.name | devlake | helm 仓库名 | diff --git a/docs/plugins/helm-installer/harbor.md b/docs/plugins/helm-installer/harbor.md deleted file mode 100644 index 6a43b19ca..000000000 --- a/docs/plugins/helm-installer/harbor.md +++ /dev/null @@ -1,149 +0,0 @@ -# Install Harbor with DevStream - -## 1 Prerequisites - -- An existing Kubernetes cluster, version > 1.10 -- StorageClass - -If you are sure you already have a StorageClass configured for your K8s cluster, you can [_skip this section and move on to the next_](#2-harbor-architecture). - -If you are uncertain about StorageClass, here's a bit more explanation: - -> Depending on the installation method, your Kubernetes cluster may be deployed with an existing StorageClass marked as default. This default StorageClass is then used to dynamically provision storage for PersistentVolumeClaims that do not require any specific storage class. See [PersistentVolumeClaim documentation](https://kubernetes.io/docs/concepts/storage/persistent-volumes/#persistentvolumeclaims) for details. - -Examples: - -- For local clusters created by `minikube,` there is already a default standard StorageClass using hostpath. -- For local clusters created by `kind,` there is a default standard StorageClass using rancher.io/local-path. -- For K8s-as-a-Service in public cloud providers, it's highly likely that a default StorageClass is created. For example, for AWS EKS, the default is gp2, using AWS EBS. Note that the pre-installed default StorageClass may not fit well with your expected workload; for example, it might provision storage that is too expensive. If this is the case, you can either change the default StorageClass or disable it thoroughly to avoid the dynamic provisioning of storage. For more information on this topic, see [the official doc here](https://kubernetes.io/docs/tasks/administer-cluster/change-default-storage-class/). - -## 2 Harbor Architecture - -![Harbor Architecture](../harbor/ha.png) - -## 3 Using the Harbor Plugin with DevStream - -### 3.1 Quickstart - -For a local testing and developing purpose, we can deploy Harbor quickly using the minimal config as follows: - -```yaml -tools: -- name: helm-installer - instanceID: harbor-001 - dependsOn: [ ] - options: - valuesYaml: | - externalURL: http://127.0.0.1 - expose: - type: nodePort - tls: - enabled: false - chartmuseum: - enabled: false - notary: - enabled: false - trivy: - enabled: false -``` - -After running `dtm apply`, we can see the following resources in the "harbor" namespace: - -- **Deployment** (`kubectl get deployment -n harbor`) - -Most Harbor-related services run as Deployments: - -```shell -NAME READY UP-TO-DATE AVAILABLE AGE -harbor-core 1/1 1 1 2m56s -harbor-jobservice 1/1 1 1 2m56s -harbor-nginx 1/1 1 1 2m56s -harbor-portal 1/1 1 1 2m56s -harbor-registry 1/1 1 1 2m56s -``` - -- **StatefulSet** (`kubectl get statefulset -n harbor`) - -Harbor depends on Postgres and Redis, which are deployed as StatefulSets. Notice that these dependencies are not deployed to a production-ready level with highly-availability and redundancy. - -```shell -NAME READY AGE -harbor-database 1/1 3m40s -harbor-redis 1/1 3m40s -``` - -- **Service** (`kubectl get service -n harbor`) - -By default, Harbor is exposed on port 30002 as type NodePort: - -```shell -NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE -harbor NodePort 10.99.177.6 80:30002/TCP 4m17s -harbor-core ClusterIP 10.106.220.239 80/TCP 4m17s -harbor-database ClusterIP 10.102.102.95 5432/TCP 4m17s -harbor-jobservice ClusterIP 10.98.5.49 80/TCP 4m17s -harbor-portal ClusterIP 10.105.115.5 80/TCP 4m17s -harbor-redis ClusterIP 10.104.100.167 6379/TCP 4m17s -harbor-registry ClusterIP 10.106.124.148 5000/TCP,8080/TCP 4m17s -``` - -- **PersistentVolumeClaim** (`kubectl get pvc -n harbor`) - -Harbor requires a few volumes, including volumes for Postgres and Redis: - -```shell -NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE -data-harbor-redis-0 Bound pvc-5b6b5eb4-c40d-4f46-8f19-ff3a8869e56f 1Gi RWO standard 5m12s -database-data-harbor-database-0 Bound pvc-d7ccaf1f-c450-4a16-937a-f55ad0c7c18d 1Gi RWO standard 5m12s -harbor-jobservice Bound pvc-9407ef73-eb65-4a56-8720-a9ddbcb76fef 1Gi RWO standard 5m13s -harbor-registry Bound pvc-34a2b88d-9ff2-4af4-9faf-2b33e97b971f 5Gi RWO standard 5m13s -``` - -- **PersistentVolume** (`kubectl get pv`) - -For a quick start (for example, with a local kind/minikube cluster,) we don't have to configure the StorageClass; so the resources are created with the default StorageClass: - -```shell -pvc-34a2b88d-9ff2-4af4-9faf-2b33e97b971f 5Gi RWO Delete Bound harbor/harbor-registry standard 5m22s -pvc-5b6b5eb4-c40d-4f46-8f19-ff3a8869e56f 1Gi RWO Delete Bound harbor/data-harbor-redis-0 standard 5m22s -pvc-9407ef73-eb65-4a56-8720-a9ddbcb76fef 1Gi RWO Delete Bound harbor/harbor-jobservice standard 5m22s -pvc-d7ccaf1f-c450-4a16-937a-f55ad0c7c18d 1Gi RWO Delete Bound harbor/database-data-harbor-database-0 standard 5m22s -``` - -In this example, our default StorageClass is(`kubectl get storageclass`): - -```shell -NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE -standard (default) k8s.io/minikube-hostpath Delete Immediate false 20h -``` - -### 3.2 Using Harbor - -We can forward the port of the Harbor service and log in: - -```shell -kubectl port-forward -n harbor service/harbor 8080:80 -``` - -![Harbor Login](../harbor/login.png) - -And the default login user/pwd is: `admin/Harbor12345`. You will see the dashboard after a successful login: - -![Harbor Dashboard](../harbor/dashboard.png) - -### 3.3 Default Config - -The `harbor` plugin provides default values for many options: - -| key | default value | description | -| ---- | ---- | ---- | -| chart.chartPath | "" | local chart path | -| chart.chartName | harbor/harbor | helm chart name | -| chart.version | "" | chart version | -| chart.timeout | 10m | timeout for helm install | -| chart.upgradeCRDs | true | update CRDs or not (if any) | -| chart.releaseName | harbor | helm release name | -| chart.namespace | harbor | namespace | -| chart.wait | true | wait till deployment finishes | -| repo.url | https://helm.goharbor.io | helm repo URL | -| repo.name | harbor | helm repo name | diff --git a/docs/plugins/helm-installer/harbor.zh.md b/docs/plugins/helm-installer/harbor.zh.md deleted file mode 100644 index b8c2f73e3..000000000 --- a/docs/plugins/helm-installer/harbor.zh.md +++ /dev/null @@ -1,601 +0,0 @@ -# 使用 DevStream 部署 Harbor - -## 1、前置要求 - -**必须满足** - -- 有一个可用的 Kubernetes 集群,版本 1.10+ -- 配置好 StorageClass - -**可选满足** - -- 配置好 Ingress 控制器(如果需要使用 Ingress 暴露服务) - -## 2、部署架构 - -Harbor 本身并不关注如何实现存储高可用,所以 Harbor 通过 PVCs 的方式持久化数据。 -另外 Harbor 也不关注 PostgreSQL、Redis 的高可用方式部署,而是提供了对接外部 PostgreSQL 和 Redis 的能力。 - -Harbor 部署架构整体如下图所示(图片来自 Harbor 官网): - -![Harbor Architecture](../harbor/ha.png) - -## 3、开始部署 - -下文将介绍如何配置 `harbor` 插件,完成 Harbor 应用的部署。 - -> 说明:本文所使用的演示环境为一台 Linux 云主机,上面装有以 minikube 方式部署的单节点 k8s 集群。 - -minikube 方式部署的 k8s 集群自带一个默认的 StorageClass,另外部署 Ingress 控制器只需要执行 `minikube addons enable ingress` 命令即可。 -其他方式部署的 k8s 集群中如何配置 StorageClass 和 Ingress Controller,请查阅[ k8s 官方文档](https://kubernetes.io)。 - -你可以选择顺序阅读本文档,从而全面了解 `harbor` 插件的使用方法;也可以选择直接跳到下文"典型场景"小节,直接寻找你感兴趣的使用场景开始阅读。 - -### 3.1、快速开始 - -如果仅是用于开发、测试等目的,希望快速完成 Harbor 的部署,可以使用如下配置快速开始: - -```yaml title="config.yaml" -tools: -- name: helm-installer - instanceID: harbor-001 - dependsOn: [ ] - options: - valuesYaml: | - externalURL: http://127.0.0.1 - expose: - type: nodePort - tls: - enabled: false - chartmuseum: - enabled: false - notary: - enabled: false - trivy: - enabled: false -``` - -在成功执行 `dtm apply` 命令后,我们可以在 harbor 命名空间下看到下述主要资源: - -- **Deployment** (`kubectl get deployment -n harbor`) - -几乎所有 Harbor 相关服务都是以 Deployment 方式在运行: - -```shell -NAME READY UP-TO-DATE AVAILABLE AGE -harbor-core 1/1 1 1 2m56s -harbor-jobservice 1/1 1 1 2m56s -harbor-nginx 1/1 1 1 2m56s -harbor-portal 1/1 1 1 2m56s -harbor-registry 1/1 1 1 2m56s -``` - -- **StatefulSet** (`kubectl get statefulset -n harbor`) - -StatefulSet 资源对应的是 Harbor 所依赖的 PostgreSQL 和 Redis。 -换言之,当前部署方式会自动完成 PostgreSQL 和 Redis 的部署,但是同时需要注意 PostgreSQL 和 Redis 并不是高可用的: - -```shell -NAME READY AGE -harbor-database 1/1 3m40s -harbor-redis 1/1 3m40s -``` - -- **Service** (`kubectl get service -n harbor`) - -默认情况下 Harbor 通过 NodePort 方式暴露服务时,端口是 30002: - -```shell -NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE -harbor NodePort 10.99.177.6 80:30002/TCP 4m17s -harbor-core ClusterIP 10.106.220.239 80/TCP 4m17s -harbor-database ClusterIP 10.102.102.95 5432/TCP 4m17s -harbor-jobservice ClusterIP 10.98.5.49 80/TCP 4m17s -harbor-portal ClusterIP 10.105.115.5 80/TCP 4m17s -harbor-redis ClusterIP 10.104.100.167 6379/TCP 4m17s -harbor-registry ClusterIP 10.106.124.148 5000/TCP,8080/TCP 4m17s -``` - -- **PersistentVolumeClaim** (`kubectl get pvc -n harbor`) - -Harbor 所需要的存储卷有4个,其中也包括了 PostgreSQL 和 Redis 的存储: - -```shell -NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE -data-harbor-redis-0 Bound pvc-5b6b5eb4-c40d-4f46-8f19-ff3a8869e56f 1Gi RWO standard 5m12s -database-data-harbor-database-0 Bound pvc-d7ccaf1f-c450-4a16-937a-f55ad0c7c18d 1Gi RWO standard 5m12s -harbor-jobservice Bound pvc-9407ef73-eb65-4a56-8720-a9ddbcb76fef 1Gi RWO standard 5m13s -harbor-registry Bound pvc-34a2b88d-9ff2-4af4-9faf-2b33e97b971f 5Gi RWO standard 5m13s -``` - -- **PersistentVolume** (`kubectl get pv`) - -我们并没有配置 StorageClass,所以这里用的是集群内的 default StorageClass 完成的 pv 创建: - -```shell -pvc-34a2b88d-9ff2-4af4-9faf-2b33e97b971f 5Gi RWO Delete Bound harbor/harbor-registry standard 5m22s -pvc-5b6b5eb4-c40d-4f46-8f19-ff3a8869e56f 1Gi RWO Delete Bound harbor/data-harbor-redis-0 standard 5m22s -pvc-9407ef73-eb65-4a56-8720-a9ddbcb76fef 1Gi RWO Delete Bound harbor/harbor-jobservice standard 5m22s -pvc-d7ccaf1f-c450-4a16-937a-f55ad0c7c18d 1Gi RWO Delete Bound harbor/database-data-harbor-database-0 standard 5m22s -``` - -在当前演示环境里,default StorageClass 如下(`kubectl get storageclass`): - -```shell -NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE -standard (default) k8s.io/minikube-hostpath Delete Immediate false 20h -``` - -这个 StorageClass 对应的 Provisioner 会以 hostPath 的方式提供 pv。 - -到这里,我们就可以通过 http://127.0.0.1:3002 访问到 Harbor 登录页面了,如下: - -![Harbor Login](../harbor/login.png) - -默认登录账号/密码是 `admin/Harbor12345`。登录后,可以看到默认首页如下: - -![Harbor Dashboard](../harbor/dashboard.png) - -如果是在云主机上部署的 Harbor,可以通过 `kubectl port-forward` 命令来暴露服务: - -```shell -ip=YOUR_HOST_IP -kubectl port-forward -n harbor service/harbor --address=${ip} 80 -``` - -*注意:这里得使用主机真实网卡 ip,而我们在浏览器上输入的 ip 是云主机的公网 ip,两者并不一样。* - -### 3.2、默认配置 - -| 配置项 | 默认值 | 描述 | -| ---- | ---- | ---- | -| chart.chartPath | "" | 本地 chart 包路径 | -| chart.chartName | harbor/harbor | helm chart 包名称 | -| chart.timeout | 10m | helm install 的超时时间 | -| chart.upgradeCRDs | true | 是否更新 CRDs(如果有) | -| chart.releaseName | harbor | helm 发布名称 | -| chart.wait | true | 是否等待部署完成 | -| chart.namespace | harbor | 部署的命名空间 | -| repo.url | https://helm.goharbor.io | helm 仓库地址 | -| repo.name | harbor | helm 仓库名 | - -因此完整的配置文件应该是这样: - -### 3.3、持久化存储数据 - -前面我们已经看到了如果不指定 StorageClass,Harbor 会使用集群内的 default StorageClass。 -在当前演示环境中,default StorageClass 是通过 hostPath 方式提供 pv 的,因此我们可以看到 harbor-registry 所使用的 pv 配置大致如下: - -```yaml -apiVersion: v1 -kind: PersistentVolume -metadata: - name: pvc-34a2b88d-9ff2-4af4-9faf-2b33e97b971f -spec: - accessModes: - - ReadWriteOnce - capacity: - storage: 5Gi - claimRef: - apiVersion: v1 - kind: PersistentVolumeClaim - name: harbor-registry - namespace: harbor - hostPath: - path: /tmp/hostpath-provisioner/harbor/harbor-registry - persistentVolumeReclaimPolicy: Delete - storageClassName: standard - volumeMode: Filesystem -status: - phase: Bound -``` - -可见数据其实挂在到了主机的 `/tmp/hostpath-provisioner/harbor/harbor-registry` 目录下了。 - -Harbor 支持3种持久化存储数据配置方式: - -1. 配置 StorageClass(默认使用 default StorageClass); -2. 使用已经存在的 pvc(手动创建); -3. 对接 azure、gcs、s3、swift、oss 等实现镜像和 Charts 云端存储。 - -我们暂时只介绍第一种方式,也就是指定 StorageClass 实现存储数据持久化。 -registry、jobservice、chartmuseum、database、redis、trivy 等组件都可以单独指定 StorageClass。 -假设我们现在有一个新的 StorageClass 叫做 nfs,这时候要实现前面部署的 Harbor 所有数据持久化到外部 pv,我们可以这样配置: - -```yaml -tools: -- name: helm-installer - instanceID: harbor-001 - dependsOn: [ ] - options: - valuesYaml: | - persistence: - persistentVolumeClaim: - registry: - storageClass: "nfs" - accessMode: ReadWriteOnce - size: 5Gi - jobservice: - storageClass: "nfs" - accessMode: ReadWriteOnce - size: 1Gi - database: - storageClass: "nfs" - accessMode: ReadWriteOnce - size: 1Gi - redis: - storageClass: "nfs" - accessMode: ReadWriteOnce - size: 1Gi -``` - -### 3.4、服务暴露 - -Harbor 可以以 ClusterIP、LoadBalancer、NodePort 和 Ingress 等方式对外暴露服务。我们前面使用的就是 NodePort 方式: - -```yaml -tools: -- name: helm-installer - instanceID: harbor-001 - dependsOn: [ ] - options: - valuesYaml: | - externalURL: http://127.0.0.1 - expose: - type: nodePort -``` - -接下来我们再介绍一下如何使用 Ingress 方式暴露服务: - -```yaml -tools: -- name: helm-installer - instanceID: harbor-001 - dependsOn: [ ] - options: - valuesYaml: | - externalURL: http://core.harbor.domain - expose: - type: ingress - tls: - enabled: false - ingress: - hosts: - core: core.harbor.domain -``` - -注意:如果没有开启 TLS,这种方式暴露 Harbor 服务后 docker push/pull 命令必须带上端口。 - -### 3.5、PostgreSQL 和 Redis 高可用 - -Harbor 依赖 PostgreSQL 和 Redis 服务,默认情况下自动部署的 PostgreSQL 和 Redis 服务都是非高可用模式。 -换言之如果我们单独部署高可用的 PostgreSQL 和 Redis 服务,Harbor 是支持去对接外部 PostgreSQL 和 Redis 服务的。 - -TODO(daniel-hutao): 本节待细化 - -### 3.6、https 配置 - -TODO(daniel-hutao): 本节待细化 - -- 使用自签名证书 - 1. 将 `tls.enabled` 设置为 `true`,并编辑对应的域名 `externalURL`; - 2. 将 Pod `harbor-core` 中目录 `/etc/core/ca` 存储的自签名证书复制到自己的本机; - 3. 在自己的主机上信任该证书。 -- 使用公共证书 - 1. 将公共证书添加为密钥 (`Secret`); - 2. 将 `tls.enabled` 设置为 `true`,并编辑对应的域名 `externalURL`; - 3. 配置 `tls.secretName` 使用该公共证书。 - -## 4、典型场景 - -// TODO(daniel-hutao): 本节待补充 - -### 4.1、HTTP - -#### 4.1.1 HTTP + Registry + Internal Database + Internal Redis - -如果我们选择以如下方式部署 Harbor: - -- 协议:http -- 服务暴露:Ingress -- PostgreSQL 和 Redis:自动部署 - -域名和 StorageClass 可以灵活修改,这里以 core.harbor.domain 和 nfs 为例: - -```yaml -tools: -- name: helm-installer - instanceID: harbor-001 - dependsOn: [ ] - options: - valuesYaml: | - externalURL: http://core.harbor.domain - expose: - type: ingress - tls: - enabled: false - ingress: - hosts: - core: core.harbor.domain - chartmuseum: - enabled: false - notary: - enabled: false - trivy: - enabled: false - persistence: - persistentVolumeClaim: - registry: - storageClass: "nfs" - accessMode: ReadWriteOnce - size: 5Gi - jobservice: - storageClass: "nfs" - accessMode: ReadWriteOnce - size: 1Gi - database: - storageClass: "nfs" - accessMode: ReadWriteOnce - size: 1Gi - redis: - storageClass: "nfs" - accessMode: ReadWriteOnce - size: 1Gi -``` - -部署完成后,可以看到 Ingress 配置如下(`kubectl get ingress -n harbor): - -```shell -NAME CLASS HOSTS ADDRESS PORTS AGE -harbor-ingress nginx core.harbor.domain 192.168.49.2 80 2m8s -``` - -如果你的 DNS 服务器可以解析到这个域名,那么这时候就可以在浏览器里通过 http://core.harbor.domain 访问到 Harbor 了。 -如果是测试环境,我们也可以通过修改 hosts 配置来完成这个域名解析,比如: - -- 修改 hosts (`cat /etc/hosts | grep harbor`) - -```shell -192.168.49.2 core.harbor.domain -``` - -对于 minikube 部署的 k8s 集群,这里的 ip 可以通过 `minikube ip` 命令获取。 -如果是正式的 k8s 集群,这里的 ip 是 k8s 集群对外暴露的 vip,可能是一个节点 ip,也可能是一个 F5 上的 vip,或者 haproxy 等方式提供的 vip。 - -#### 4.1.2 HTTP + Registry + Chartmuseum + Internal Database + Internal Redis - -#### 4.1.3 HTTP + Registry + Chartmuseum + External Database + External Redis - -### 4.2、HTTPS - -#### 4.1.1 HTTPS + Registry + Internal Database + Internal Redis - -#### 4.1.2 HTTPS + Registry + Chartmuseum + Internal Database + Internal Redis - -#### 4.1.3 HTTPS + Registry + Chartmuseum + External Database + External Redis - -## 5、离线环境部署 - -// TODO(daniel-hutao): 本节内容近期将持续补充完善 - -### 5.1、Helm Chart 包 - -如果需要在离线环境部署 Harbor,你需要下载对应的 helm chart 包: - -```shell -helm repo add harbor https://helm.goharbor.io -helm repo update -helm search repo harbor -l -helm pull harbor/harbor --version=1.10.0 -``` - -这时你会得到一个 `harbor-1.10.0.tgz` 文件,你可以将其存放到一个合适的目录,比如 `~/devstream-test/harbor-1.10.0.tgz`,然后在配置文件就可以这样引用这个 chart 包了: - -```yaml -tools: -- name: helm-installer - instanceID: harbor-001 - dependsOn: [ ] - options: - chart: - chartPath: "~/devstream-test/harbor-1.10.0.tgz" -``` - -### 5.2、容器镜像 - -`harbor` 插件支持使用自定义容器镜像,你需要先在 valuesYaml 部分加上如下配置: - -```yaml -valuesYaml: | - nginx: - image: - repository: [[ imageRepo ]]/goharbor/nginx-photon - tag: v2.5.3 - portal: - image: - repository: [[ imageRepo ]]/goharbor/harbor-portal - tag: v2.5.3 - core: - image: - repository: [[ imageRepo ]]/goharbor/harbor-core - tag: v2.5.3 - jobservice: - image: - repository: [[ imageRepo ]]/goharbor/harbor-jobservice - tag: v2.5.3 - registry: - registry: - image: - repository: [[ imageRepo ]]/goharbor/registry-photon - tag: v2.5.3 - controller: - image: - repository: [[ imageRepo ]]/goharbor/harbor-registryctl - tag: v2.5.3 - chartmuseum: - image: - repository: [[ imageRepo ]]/goharbor/chartmuseum-photon - tag: v2.5.3 - trivy: - image: - repository: [[ imageRepo ]]/goharbor/trivy-adapter-photon - tag: v2.5.3 - notary: - server: - image: - repository: [[ imageRepo ]]/goharbor/notary-server-photon - tag: v2.5.3 - signer: - image: - repository: [[ imageRepo ]]/goharbor/notary-signer-photon - tag: v2.5.3 - database: - internal: - image: - repository: [[ imageRepo ]]/goharbor/harbor-db - tag: v2.5.3 - redis: - internal: - image: - repository: [[ imageRepo ]]/goharbor/redis-photon - tag: v2.5.3 - exporter: - image: - repository: [[ imageRepo ]]/goharbor/harbor-exporter - tag: v2.5.3 -``` - -这段配置中留了一个变量 `[[ imageRepo ]]`,你可以在[变量配置](../../core-concepts/config.zh.md)中定义这个变量,变量值设置成你的镜像仓库地址,例如: - -```yaml -imageRepo: harbor.example.com:9000 -``` - -当然,你需要保证需要的镜像都在你的镜像仓库中存在。 - -你可以下载[镜像列表文件](./harbor/harbor-images.txt), -然后借助["Image Pull Push"](https://raw.githubusercontent.com/devstream-io/devstream/main/hack/image-pull-push.sh)工具脚本来准备镜像。 - -```shell -curl -o harbor-images.txt https://raw.githubusercontent.com/devstream-io/devstream/main/docs/plugins/helm-installer/harbor/harbor-images.txt -curl -o image-pull-push.sh https://raw.githubusercontent.com/devstream-io/devstream/main/hack/image-pull-push.sh -chmod +x image-pull-push.sh -# 查看工具脚本的使用方法和注意事项等 -./image-pull-push.sh -h -# 设置镜像仓库地址,按需修改 -export IMAGE_REPO_ADDR=harbor.devstream.io -# 下载 harbor-images.txt 中所有镜像并保存到本地压缩包中 -./image-pull-push.sh -f harbor-images.txt -r ${IMAGE_REPO_ADDR} -s -# 从压缩包中 load 镜像并 push 到私有镜像仓库(如果镜像仓库需要登录,则需要先手动执行 docker login) -./image-pull-push.sh -f harbor-images.txt -r ${IMAGE_REPO_ADDR} -l -u -``` - -如果你还没有一个私有镜像仓库,可以参考[这篇文章](../../use-cases/reference/image-registry.zh.md)快速部署一个 Docker Registry。 - -### 5.3、参考配置 - -这时候我们需要指定本地 helm chart 包以及私有镜像仓库的镜像,所以整体的参考配置大致如下: - -```yaml ---- -# variable config -imageRepo: harbor.example.com:9000 - ---- -# plugin config -tools: -- name: helm-installer - instanceID: harbor-001 - dependsOn: [ ] - options: - chart: - chartPath: "~/devstream-test/harbor-1.10.0.tgz" - valuesYaml: | - externalURL: http://core.harbor.domain - expose: - type: ingress - tls: - enabled: false - ingress: - hosts: - core: core.harbor.domain - nginx: - image: - repository: [[ imageRepo ]]/goharbor/nginx-photon - tag: v2.5.3 - portal: - image: - repository: [[ imageRepo ]]/goharbor/harbor-portal - tag: v2.5.3 - core: - image: - repository: [[ imageRepo ]]/goharbor/harbor-core - tag: v2.5.3 - jobservice: - image: - repository: [[ imageRepo ]]/goharbor/harbor-jobservice - tag: v2.5.3 - registry: - registry: - image: - repository: [[ imageRepo ]]/goharbor/registry-photon - tag: v2.5.3 - controller: - image: - repository: [[ imageRepo ]]/goharbor/harbor-registryctl - tag: v2.5.3 - chartmuseum: - enabled: false - image: - repository: [[ imageRepo ]]/goharbor/chartmuseum-photon - tag: v2.5.3 - trivy: - enabled: false - image: - repository: [[ imageRepo ]]/goharbor/trivy-adapter-photon - tag: v2.5.3 - notary: - enabled: false - server: - image: - repository: [[ imageRepo ]]/goharbor/notary-server-photon - tag: v2.5.3 - signer: - image: - repository: [[ imageRepo ]]/goharbor/notary-signer-photon - tag: v2.5.3 - database: - internal: - image: - repository: [[ imageRepo ]]/goharbor/harbor-db - tag: v2.5.3 - redis: - internal: - image: - repository: [[ imageRepo ]]/goharbor/redis-photon - tag: v2.5.3 - exporter: - image: - repository: [[ imageRepo ]]/goharbor/harbor-exporter - tag: v2.5.3 - persistence: - persistentVolumeClaim: - registry: - storageClass: "nfs" - accessMode: ReadWriteOnce - size: 5Gi - jobservice: - storageClass: "nfs" - accessMode: ReadWriteOnce - size: 1Gi - database: - storageClass: "nfs" - accessMode: ReadWriteOnce - size: 1Gi - redis: - storageClass: "nfs" - accessMode: ReadWriteOnce - size: 1Gi -``` - -在这个参考配置里包含了全部可能用到的镜像,在部分组件不启用的情况下你完全可以移除相关的镜像配置项。不过保留在这里也不会有什么影响。 diff --git a/docs/plugins/helm-installer/harbor/dashboard.png b/docs/plugins/helm-installer/harbor/dashboard.png deleted file mode 100644 index 1915e1db1..000000000 Binary files a/docs/plugins/helm-installer/harbor/dashboard.png and /dev/null differ diff --git a/docs/plugins/helm-installer/harbor/ha.png b/docs/plugins/helm-installer/harbor/ha.png deleted file mode 100644 index 6f063c2bb..000000000 Binary files a/docs/plugins/helm-installer/harbor/ha.png and /dev/null differ diff --git a/docs/plugins/helm-installer/harbor/harbor-images.txt b/docs/plugins/helm-installer/harbor/harbor-images.txt deleted file mode 100644 index 8e166b8ce..000000000 --- a/docs/plugins/helm-installer/harbor/harbor-images.txt +++ /dev/null @@ -1,14 +0,0 @@ -##harbor-images -goharbor/nginx-photon:v2.5.3 -goharbor/harbor-portal:v2.5.3 -goharbor/harbor-core:v2.5.3 -goharbor/harbor-jobservice:v2.5.3 -goharbor/registry-photon:v2.5.3 -goharbor/harbor-registryctl:v2.5.3 -goharbor/chartmuseum-photon:v2.5.3 -goharbor/trivy-adapter-photon:v2.5.3 -goharbor/notary-server-photon:v2.5.3 -goharbor/notary-signer-photon:v2.5.3 -goharbor/harbor-db:v2.5.3 -goharbor/redis-photon:v2.5.3 -goharbor/harbor-exporter:v2.5.3 diff --git a/docs/plugins/helm-installer/harbor/login.png b/docs/plugins/helm-installer/harbor/login.png deleted file mode 100644 index 9bdd6f1e4..000000000 Binary files a/docs/plugins/helm-installer/harbor/login.png and /dev/null differ diff --git a/docs/plugins/helm-installer/helm-installer.md b/docs/plugins/helm-installer/helm-installer.md deleted file mode 100644 index 76b663c49..000000000 --- a/docs/plugins/helm-installer/helm-installer.md +++ /dev/null @@ -1,3 +0,0 @@ -# helm-installer Plugin - -//TODO(daniel-hutao): write later diff --git a/docs/plugins/helm-installer/helm-installer.zh.md b/docs/plugins/helm-installer/helm-installer.zh.md deleted file mode 100644 index 163fc7397..000000000 --- a/docs/plugins/helm-installer/helm-installer.zh.md +++ /dev/null @@ -1,131 +0,0 @@ -# helm-installer 插件 - -`helm-installer` 插件实现了比 `helm` 更加简单和容易上手的方式来快速部署提供了 Helm Chart 的应用。 - -## 1、快速开始 - -只需要一个最小化配置,你就可以快速使用默认配置部署一个 Helm Chart。你可以将如下配置内容保存到本地 config.yaml 文件中: - -```yaml -config: - state: - backend: local - options: - stateFile: devstream.state -tools: -- name: helm-installer - instanceID: argocd-001 -``` - -在这个配置文件里,和插件相关的配置 name 和 instanceID,前者表示你将使用 `helm-installer` 插件,后者表示插件实例名。 -请注意这个 instanceID 使用了 "argocd" 前缀,DevStream 会识别这个前缀,尝试寻找 Argo CD 应用对应的 Chart,并设置一系列默认值,然后开始部署。 - -你可以在 [Install Argo CD with DevStream](./argocd.zh.md) 中查看 DevStream 为你设置了哪些默认值。 - -接着执行如下命令开始部署: - -```sh -./dtm init -f config.yaml -./dtm apply -f config.yaml -y -``` - -## 2、插件介绍 - -`helm-installer` 插件的完整配置格式如下: - -```yaml -tools: -- name: helm-installer - instanceID: argocd-001 - dependsOn: [ ] - options: - repo: - name: "" - url: "" - chart: - chartPath: "" - chartName: "" - version: "" - namespace: "" - releaseName: "" - wait: true - timeout: 10m - upgradeCRDs: true - valuesYaml: "" -``` - -这里有一些细节需要注意,下述几个小节将详细为你介绍。 - -### 2.1、instanceID 使用技巧 - -instanceID 的前缀如果能够匹配到某个已经被支持的工具(详见文末列表),那么 DevStream 会为你设置一系列的默认值。 -比如 "argocd-001" 的前缀 "argocd-" 能够匹配到 "argocd",因此 Argo CD 的默认 Chart 配置会被应用,于是如下最小化配置: - -```yaml -tools: -- name: helm-installer - instanceID: argocd-001 -``` - -将会被 DevStream 直接补全成: - -```yaml -- name: helm-installer - instanceID: argocd-001 - dependsOn: [ ] - options: - repo: - name: "argo" - url: "https://argoproj.github.io/argo-helm" - chart: - chartPath: "" - chartName: "argo/argo-cd" - version: "" - namespace: "argocd" - releaseName: "argocd" - wait: true - timeout: 10m - upgradeCRDs: true - valuesYaml: "" -``` - -### 2.2、自定义 Chart 配置 - -如果你想使用自定义 Chart 的 values.yaml 配置,只需要将 values.yaml 的文件路径或者内容直接加到 helm-installer 插件配置 options 部分的 chart.valuesYaml 里。 -两种配置方式分别如下: - -- 使用本地 values.yaml 文件 - -```yaml -- name: helm-installer - instanceID: argocd-001 - dependsOn: [ ] - options: - valuesYaml: "./values.yaml" -``` - -- 直接使用 values.yaml 文件内容 - -```yaml -- name: helm-installer - instanceID: argocd-001 - dependsOn: [ ] - options: - valuesYaml: | - foo: bar -``` - -## 3、当前支持的工具列表 - -当前 DevStream 支持使用"极简配置"部署如下应用(也就是能够根据 instanceID 配置识别到 Chart 地址等信息,并设置一系列默认值,直接开始部署流程): - -- [Install Argo CD with DevStream](./argocd.zh.md) -- [Install Artifactory with DevStream](./artifactory.zh.md) -- [Install DevLake with DevStream](./devlake.zh.md) -- [Install Harbor with DevStream](./harbor.zh.md) -- [Install Jenkins with DevStream](./jenkins.zh.md) -- [Install Kube Prometheus with DevStream](./kube-prometheus.zh.md) -- [Install OpenLDAP with DevStream](./openldap.zh.md) -- [Install SonarQube with DevStream](./sonarqube.zh.md) -- [Install Tekton with DevStream](./tekton.zh.md) -- [Install Vault with DevStream](./vault.zh.md) diff --git a/docs/plugins/helm-installer/jenkins.md b/docs/plugins/helm-installer/jenkins.md deleted file mode 100644 index a955ae7dd..000000000 --- a/docs/plugins/helm-installer/jenkins.md +++ /dev/null @@ -1,33 +0,0 @@ -# Install Jenkins with DevStream - -//TODO(daniel-hutao): to be updated - -This plugin installs [Jenkins](https://jenkins.io) in an existing Kubernetes cluster using the Helm chart. - -It also installs [GitHub Pull Request Builder(ghprb)](https://plugins.jenkins.io/ghprb/) and [OWASP Markup Formatter](https://plugins.jenkins.io/antisamy-markup-formatter/) plugins. Then enable HTML parsing using OWASP Markup Formatter Plugin , useful with ghprb plugin. - -## Default Configs - -| key | default value | description | -| ---- | ---- | ---- | -| chart.chartPath | "" | local chart path | -| chart.chartName | jenkins/jenkins | chart name | -| chart.version | "" | chart version | -| chart.timeout | 5m | this config will wait 5 minutes to deploy | -| chart.upgradeCRDs | true | default update CRD config | -| chart.releaseName | jenkins | helm release name | -| chart.namespace | jenkins | namespace where helm to deploy | -| chart.wait | true | whether to wait until installation is complete | -| repo.url | https://charts.jenkins.io | helm official repo address | -| repo.name | jenkins | helm repo name | - -Please be sure to change the `storageClass` in the options of the config to an existing StorageClass. - -Currently, expect default configs all the parameters in the example above are mandatory. - -## Outputs - -This plugin has two outputs: - -- `jenkinsURL` (format: `hostname:port`, example: "localhost:8080") -- `jenkinsPasswordOfAdmin` diff --git a/docs/plugins/helm-installer/jenkins.zh.md b/docs/plugins/helm-installer/jenkins.zh.md deleted file mode 100644 index ff00aae70..000000000 --- a/docs/plugins/helm-installer/jenkins.zh.md +++ /dev/null @@ -1,432 +0,0 @@ -# 使用 DevStream 部署 Jenkins - -## 1、前置要求 - -**必须满足** - -- 有一个可用的 Kubernetes 集群,版本 1.10+ -- 配置好 StorageClass - -**可选满足** - -- 配置好 Ingress 控制器(如果需要使用 Ingress 暴露服务) - -如果你还没有准备好一个满足上述要求的 Kubernetes 集群,可以参考 [minikube 文档](https://minikube.sigs.k8s.io/docs/start/) 快速创建一个 Kubernetes 测试集群。 -在成功执行完 `minikube start` 命令后,假如需要启用 Ingress,可以通过 `minikube addons enable ingress` 命令完成 Ingress 控制器的启用。 -因为 minikube 方式部署的 Kubernetes 集群会自带一个名字为 standard 的 default StorageClass,所以当前集群满足上述全部前置要求。 - -## 2、开始部署 - -下文将介绍如何配置 `jenkins` 插件,完成 Jenkins 应用的部署。本文演示环境为一台有通过 minikube 方式部署的单节点 Kubernetes 集群的 Macbook/m1 电脑。 - -## 2.1、快速开始 - -如果仅是用于开发、测试等目的,希望快速完成 Jenkins 的部署,可以使用如下配置快速开始: - -```yaml -tools: -- name: helm-installer - instanceID: jenkins-001 - dependsOn: [ ] - options: - valuesYaml: | - serviceAccount: - create: true - name: jenkins - controller: - adminUser: "admin" - adminPassword: "changeme" - serviceType: NodePort - nodePort: 32000 -``` - -*注意:这个配置示例仅是 tool config,完整的 DevStream 配置文件还需要补充 core config 等内容,具体参考[这个文档](../../core-concepts/config.zh.md)。* - -在成功执行 `dtm apply` 命令后,我们可以在 jenkins 命名空间下看到下述主要资源: - -- **StatefulSet** (`kubectl get statefulset -n jenkins`) - -```shell -NAME READY AGE -jenkins 1/1 3m10s -``` - -- **Service** (`kubectl get service -n jenkins`) - -```shell -NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE -jenkins NodePort 10.103.31.213 8080:32000/TCP 3m30s -jenkins-agent ClusterIP 10.100.239.11 50000/TCP 3m30s -``` - -- **PersistentVolumeClaim** (`kubectl get pvc -n jenkins`) - -```shell -NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE -jenkins Bound pvc-f474b131-dea8-4ac3-886b-8549da2cad56 8Gi RWO standard 3m50s -``` - -- **PersistentVolume** (`kubectl get pv`) - -```shell -NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE -pvc-f474b131-dea8-4ac3-886b-8549da2cad56 8Gi RWO Delete Bound jenkins/jenkins standard 4m10s -``` - -前面我们提到过 Kubernetes 集群里需要有一个 StorageClass,当前 Jenkins 所使用的 pv 来自于集群中 default StorageClass: - -```shell -NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE -standard (default) k8s.io/minikube-hostpath Delete Immediate false 20h -``` - -到这里,我们就可以通过 NodePort 方式访问 Jenkins 登录页面了。但是由于我们的 Kubernetes 测试集群使用的是 minikube 方式部署, -而不是 kubeadm 这种直接在主机上部署 Kubernetes 相关组件的方式,所以这里还需要一步操作: - -- **服务暴露** (`minikube service jenkins -n jenkins`) - -```shell -|-----------|---------|-------------|---------------------------| -| NAMESPACE | NAME | TARGET PORT | URL | -|-----------|---------|-------------|---------------------------| -| jenkins | jenkins | http/8080 | http://192.168.49.2:32000 | -|-----------|---------|-------------|---------------------------| -🏃 Starting tunnel for service jenkins. -|-----------|---------|-------------|------------------------| -| NAMESPACE | NAME | TARGET PORT | URL | -|-----------|---------|-------------|------------------------| -| jenkins | jenkins | | http://127.0.0.1:65398 | -|-----------|---------|-------------|------------------------| -🎉 Opening service jenkins/jenkins in default browser... -❗ Because you are using a Docker driver on darwin, the terminal needs to be open to run it. -``` - -这时候 minikube 会自动打开浏览器,跳转到 http://127.0.0.1:65398 页面(如果没有自动跳转,可以手动打开浏览器,输入这个 url;注意:根据你的命令行输出内容修改 url 中的端口号): - -![Jenkins Login](../jenkins/login.png) - -- **登录** - -如果你浏览过前面我们使用的"最小化配置文件",肯定已经注意到了里面和用户名、密码相关的配置,没错,通过 admin/changeme 就可以登录 Jenkins 了! - -![Jenkins Dashboard](../jenkins/dashboard.png) - -最后,记得修改密码哦! - -### 2.2、默认配置 - -`jenkins` 插件的配置项多数都有默认值,具体默认值信息如下表: - -| 配置项 | 默认值 | 描述 | -| ---- | ---- | ---- | -| chart.chartPath | "" | 本地 chart 包路径 | -| chart.chartName | jenkins/jenkins | helm chart 包名称 | -| chart.version | "" | chart 包版本 | -| chart.timeout | 10m | helm install 的超时时间 | -| chart.upgradeCRDs | true | 是否更新 CRDs(如果有) | -| chart.releaseName | jenkins | helm 发布名称 | -| chart.namespace | jenkins | 部署的命名空间 | -| chart.wait | true | 是否等待部署完成 | -| repo.url | https://charts.jenkins.io | helm 仓库地址 | -| repo.name | jenkins | helm 仓库名 | - -### 2.3、持久化存储 - -前面"快速开始"中我们使用了 default StorageClass 来分配 pv 完成了 Jenkins 数据落到本地磁盘的过程。 -因此如果你的环境中有其他 StorageClass 可以支持 pv 数据落到远程存储,就可以通过如下配置来自定义 Jenkins 所使用的 StorageClass: - -```yaml -tools: -- name: helm-installer - instanceID: jenkins-001 - dependsOn: [ ] - options: - valuesYaml: | - serviceAccount: - create: true - name: jenkins - persistence: - storageClass: nfs - controller: - adminUser: "admin" - adminPassword: "changeme" - serviceType: NodePort - nodePort: 32000 -``` - -上述配置以 nfs StorageClass 为例,请记得将 `persistence.storageClass` 修改成你的环境中真实 StorageClass 的名字。 - -### 2.4、服务暴露 - -在"快速开始"中我们通过 NodePort 方式来暴露 Jenkins 服务。如果你想通过 Ingress 来暴露服务,可以这样配置: - -```yaml -tools: -- name: helm-installer - instanceID: jenkins-001 - dependsOn: [ ] - options: - valuesYaml: | - serviceAccount: - create: true - name: jenkins - persistence: - storageClass: "" - controller: - adminUser: "admin" - adminPassword: "changeme" - ingress: - enabled: true - hostName: jenkins.example.com -``` - -使用当前配置成功执行 `dtm apply` 命令后,可以看到环境里的 Ingress 资源如下: - -- **Ingress** (`kubectl get ingress -n jenkins`) - -```shell -NAMESPACE NAME CLASS HOSTS ADDRESS PORTS AGE -jenkins jenkins nginx jenkins.example.com 192.168.49.2 80 9m13s -``` - -至此,只要 DNS 服务器能够解析到域名 jenkins.example.com,那么你就可以通过这个域名来访问 Jenkins 了。 -当然,没有合适的 DNS 服务器的情况下,你也可以通过修改 hosts 记录来完成静态域名解析,将如下这行配置追加到 `/etc/hosts` 文件中: - -```shell -192.168.49.2 jenkins.example.com -``` - -### 2.5、推荐配置 - -// TODO(daniel-hutao): 继续细化 - -```yaml -tools: -- name: helm-installer - instanceID: jenkins-001 - dependsOn: [ ] - options: - valuesYaml: | - serviceAccount: - create: true - name: jenkins - persistence: - storageClass: "" - controller: - adminUser: "admin" - adminPassword: "changeme" - ingress: - enabled: true - hostName: jenkins.example.com - installPlugins: - - kubernetes:3600.v144b_cd192ca_a_ - - workflow-aggregator:581.v0c46fa_697ffd - - git:4.11.3 - - configuration-as-code:1512.vb_79d418d5fc8 - additionalPlugins: - # install "GitHub Pull Request Builder" plugin, see https://plugins.jenkins.io/ghprb/ for more details - - ghprb - # install "OWASP Markup Formatter" plugin, see https://plugins.jenkins.io/antisamy-markup-formatter/ for more details - - antisamy-markup-formatter - # Enable HTML parsing using OWASP Markup Formatter Plugin (antisamy-markup-formatter), useful with ghprb plugin. - enableRawHtmlMarkupFormatter: true - # Jenkins Configuraction as Code, refer to https://plugins.jenkins.io/configuration-as-code/ for more details - # notice: All configuration files that are discovered MUST be supplementary. They cannot overwrite each other'sconfiguration values. This creates a conflict and raises a ConfiguratorException. - JCasC: - defaultConfig: true -``` - -## 3、状态管理 - -DevStream 的默认状态文件为 devstream.state,可以通过配置文件中的 state.options 字段来自定义: - -```yaml -helm-installer_jenkins-001: - name: helm-installer - instanceID: jenkins-001 - dependsOn: [] - options: - valuesYaml: | - serviceAccount: - create: true - name: jenkins - controller: - adminUser: "admin" - ingress: - enabled: true - hostName: jenkins.example.com - resourceStatus: - outputs: - jenkins_url: http://jenkins.jenkins:8080 - valuesYaml: | - serviceAccount: - create: true - name: jenkins - controller: - adminUser: "admin" - ingress: - enabled: true - hostName: jenkins.example.com - workflows: | - statefulsets: - - name: jenkins - ready: true -``` - -其中 resource 部分保存的是资源实例的最新状态,也就是这部分: - -```yaml -outputs: - jenkins_url: http://jenkins.jenkins:8080 -valuesYaml: | - serviceAccount: - create: true - name: jenkins - controller: - adminUser: "admin" - ingress: - enabled: true - hostName: jenkins.example.com -workflows: | - statefulsets: - - name: jenkins - ready: true -``` - -换言之,目前 jenkins 插件关注的状态主要是自身 StatefulSet 资源状态和 valuesYaml 的配置,也就是在两种情况下会判定状态漂移,从而触发更新操作: - -1. StatefulSet 状态变更 -2. valuesYaml 部分配置变更 - -## 4、插件输出 - -在上一小节我们看到了 jenkins 插件的状态中保存了一个 outputs 字段,内容是 `jenkins_url: http://jenkins.jenkins:8080`, -所以其他插件的配置中可以通过`${{jenkins.default.outputs.jenkins_url}}` 的语法读取到 `http://jenkins.jenkins:8080`。 - -更多关于"插件输出"的内容,请阅读[这个文档](../../core-concepts/config.zh.md#42-output)。 - -## 5、离线环境部署 - -// TODO(daniel-hutao): 本节内容近期将持续补充完善 - -### 5.1、Helm Chart 包 - -如果需要在离线环境部署 Jenkins,你需要下载对应的 helm chart 包: - -```shell -helm repo add jenkins https://charts.jenkins.io -helm repo update -helm search repo jenkins -l -helm pull jenkins/jenkins --version=4.2.5 -``` - -这时你会得到一个 `jenkins-4.2.5.tgz` 文件,你可以将其存放到一个合适的目录,比如 `~/devstream-test/jenkins-4.2.5.tgz`,然后在配置文件就可以这样引用这个 chart 包了: - -```yaml -tools: -- name: helm-installer - instanceID: jenkins-001 - dependsOn: [ ] - options: - chart: - chartPath: "~/devstream-test/jenkins-4.2.5.tgz" -``` - -### 5.2、容器镜像 - -`jenkins` 插件支持使用自定义容器镜像,你需要先在 valuesYaml 部分加上如下配置: - -```yaml -valuesYaml: | - controller: - image: [[ imageRepo ]]/devstreamdev/jenkins - tag: 2.361.1-jdk11-dtm-0.1 - imagePullPolicy: "IfNotPresent" - sidecars: - configAutoReload: - image: [[ imageRepo ]]/kiwigrid/k8s-sidecar:1.15.0 - agent: - image: [[ imageRepo ]]/jenkins/inbound-agent - tag: 4.11.2-4 - backup: - image: - repository: [[ imageRepo ]]/maorfr/kube-tasks - tag: 0.2.0 -``` - -这段配置中留了一个变量 `[[ imageRepo ]]`,你可以在[变量配置](../../core-concepts/config.zh.md)中定义这个变量,变量值设置成你的镜像仓库地址,例如: - -```yaml -imageRepo: harbor.example.com:9000 -``` - -当然,你需要保证需要的镜像都在你的镜像仓库中存在。 - -你可以下载[镜像列表文件](./jenkins/jenkins-images.txt), -然后借助["Image Pull Push"](https://raw.githubusercontent.com/devstream-io/devstream/main/hack/image-pull-push.sh)工具脚本来准备镜像。 - -```shell -curl -o jenkins-images.txt https://raw.githubusercontent.com/devstream-io/devstream/main/docs/plugins/helm-installer/jenkins/jenkins-images.txt -curl -o image-pull-push.sh https://raw.githubusercontent.com/devstream-io/devstream/main/hack/image-pull-push.sh -chmod +x image-pull-push.sh -# 查看工具脚本的使用方法和注意事项等 -./image-pull-push.sh -h -# 设置镜像仓库地址,按需修改 -export IMAGE_REPO_ADDR=harbor.devstream.io -# 下载 harbor-images.txt 中所有镜像并保存到本地压缩包中 -./image-pull-push.sh -f jenkins-images.txt -r ${IMAGE_REPO_ADDR} -s -# 从压缩包中 load 镜像并 push 到私有镜像仓库(如果镜像仓库需要登录,则需要先手动执行 docker login) -./image-pull-push.sh -f jenkins-images.txt -r ${IMAGE_REPO_ADDR} -l -u -``` - -如果你还没有一个私有镜像仓库,可以参考[这篇文章](../../use-cases/reference/image-registry.zh.md)快速部署一个 Docker Registry。 - -### 5.3、参考配置 - -可能你已经注意到前面的[镜像列表](./jenkins/jenkins-images.txt)里有一个 DevStream 自定义镜像 `devstreamdev/jenkins:2.361.1-jdk11-dtm-0.1`, -在这个镜像里 DevStream 为离线部署场景做了增强,所以对应的配置文件我们也需要做一些调整,如下: - -```yaml ---- -# variable config -imageRepo: harbor.example.com:9000 - ---- -tools: -- name: helm-installer - instanceID: jenkins-001 - dependsOn: [ ] - options: - chart: - chartPath: "~/devstream-test/jenkins-4.2.5.tgz" - # custom configuration. You can refer to [Jenkins values.yaml](https://github.com/jenkinsci/helm-charts/blob/main/charts/jenkins/values.yaml) - valuesYaml: | - serviceAccount: - create: true - name: jenkins - controller: - image: [[ imageRepo ]]/devstreamdev/jenkins - tag: 2.361.1-jdk11-dtm-0.1 - imagePullPolicy: "IfNotPresent" - sidecars: - configAutoReload: - image: [[ imageRepo ]]/kiwigrid/k8s-sidecar:1.15.0 - adminUser: "admin" - adminPassword: "changeme" - ingress: - enabled: true - hostName: jenkins.example.com - # Enable HTML parsing using OWASP Markup Formatter Plugin (antisamy-markup-formatter), useful with ghprb plugin. - enableRawHtmlMarkupFormatter: true - # Jenkins Configuraction as Code, refer to https://plugins.jenkins.io/configuration-as-code/ for more details - # notice: All configuration files that are discovered MUST be supplementary. They cannot overwrite each other's configuration values. This creates a conflict and raises a ConfiguratorException. - JCasC: - defaultConfig: true - agent: - image: [[ imageRepo ]]/jenkins/inbound-agent - tag: 4.11.2-4 - backup: - image: - repository: [[ imageRepo ]]/maorfr/kube-tasks - tag: 0.2.0 -``` diff --git a/docs/plugins/helm-installer/jenkins/dashboard.png b/docs/plugins/helm-installer/jenkins/dashboard.png deleted file mode 100644 index ebd5ee5a7..000000000 Binary files a/docs/plugins/helm-installer/jenkins/dashboard.png and /dev/null differ diff --git a/docs/plugins/helm-installer/jenkins/jenkins-images.txt b/docs/plugins/helm-installer/jenkins/jenkins-images.txt deleted file mode 100644 index bd0945efc..000000000 --- a/docs/plugins/helm-installer/jenkins/jenkins-images.txt +++ /dev/null @@ -1,5 +0,0 @@ -##jenkins-images -devstreamdev/jenkins:2.361.1-jdk11-dtm-0.2 -kiwigrid/k8s-sidecar:1.15.0 -jenkins/inbound-agent:4.11.2-4 -maorfr/kube-tasks:0.2.0 diff --git a/docs/plugins/helm-installer/jenkins/login.png b/docs/plugins/helm-installer/jenkins/login.png deleted file mode 100644 index 3a1984942..000000000 Binary files a/docs/plugins/helm-installer/jenkins/login.png and /dev/null differ diff --git a/docs/plugins/helm-installer/kube-prometheus.md b/docs/plugins/helm-installer/kube-prometheus.md deleted file mode 100644 index a55259410..000000000 --- a/docs/plugins/helm-installer/kube-prometheus.md +++ /dev/null @@ -1,26 +0,0 @@ -# Install kube-prometheus with DevStream - -## InstanceID Prefix - -The `instanceID` prefix must be `kube-prometheus`, the minimum tools configuration example: - -```yaml -tools: -- name: helm-installer - instanceID: kube-prometheus -``` - -## Default Configs - -| key | default value | description | -| ---------------- | ------------------------------------ | ------------------------------------------------ | -| chart.chartPath | "" | local chart path | -| chart.chartName | prometheus-community/kube-prometheus-stack | chart name | -| chart.version | "" | chart version | -| chart.timeout | 10m | this config will wait 10 minutes to deploy Argo CD | -| chart.upgradeCRDs | true | default update CRD config | -| chart.releaseName | prometheus | helm release name | -| chart.namespace | prometheus | namespace where helm to deploy | -| chart.wait | true | whether to wait until installation is complete | -| repo.url | https://prometheus-community.github.io/helm-charts | helm official repo address | -| repo.name | prometheus-community | helm repo name | diff --git a/docs/plugins/helm-installer/kube-prometheus.zh.md b/docs/plugins/helm-installer/kube-prometheus.zh.md deleted file mode 100644 index 9b43aa03b..000000000 --- a/docs/plugins/helm-installer/kube-prometheus.zh.md +++ /dev/null @@ -1,27 +0,0 @@ -# 使用 DevStream 部署 kube-prometheus - -## 前缀匹配 - -`instanceID` 的前缀需要是 `kube-prometheus`,最小化 tools 配置示例: - -```yaml -tools: -- name: helm-installer - instanceID: kube-prometheus -``` - -## 默认配置 - -| 配置项 | 默认值 | 描述 | -| ---- | ---- | ---- | -| chart.chartPath | "" | 本地 chart 包路径 | -| chart.chartName | prometheus-community/kube-prometheus-stack | chart 包名称 | -| chart.version | "" | chart 包版本 | -| chart.timeout | 10m | helm install 的超时时间 | -| chart.upgradeCRDs | true | 是否更新 CRDs(如果有) | -| chart.releaseName | prometheus | helm 发布名称 | -| chart.namespace | prometheus | 部署的命名空间 | -| chart.wait | true | 是否等待部署完成 | -| repo.url | https://prometheus-community.github.io/helm-charts | helm 仓库地址 | -| repo.name | prometheus-community | helm 仓库名 | - diff --git a/docs/plugins/helm-installer/openldap.md b/docs/plugins/helm-installer/openldap.md deleted file mode 100644 index f7ee74428..000000000 --- a/docs/plugins/helm-installer/openldap.md +++ /dev/null @@ -1,169 +0,0 @@ -# Install OpenLDAP with DevStream - -## InstanceID Prefix - -The `instanceID` prefix must be `openldap`, the minimum tools configuration example: - -```yaml -tools: -- name: helm-installer - instanceID: openldap -``` - -### Default Configs - -| key | default value | description | -| ---- | ---- | ---- | -| chart.chartPath | "" | local chart path | -| chart.chartName | helm-openldap/openldap-stack-ha | community chart name | -| chart.version | "" | chart version | -| chart.timeout | 10m | this config will wait 5 minutes to deploy | -| chart.releaseName | openldap | helm release name | -| chart.upgradeCRDs | true | default update CRD config | -| chart.namespace | openldap | namespace where helm to deploy | -| chart.wait | true | whether to wait until installation is complete | -| repo.url | https://jp-gouin.github.io/helm-openldap/ | helm repo address | -| repo.name | helm-openldap | helm repo name | - -## Description of Key Fields in `valuesYaml` - -- `replicaCount`: The default value is 3, for the convenience of local testing, the above example is set to 1 -- `service.type`: The default value is `ClusterIP`, if you have services outside the Kubernetes cluster that require ldap integration, the value preferably be set to `NodePort`, so that services outside the Kubernetes cluster can access the ldap service via `ldap://ip:389` instead of `ldap://openldap.openldap-openldap-stack-ha:389` -- `adminPassword`: Use your own custom password -- `configPassword`: Use your own custom password -- `ltb-passwd`: Ingress of the Ltb-Passwd service by which you can modify your password. If you need this service, you can set `ltb-passwd.enabled` to `true`. -- `phpldapadmin.ingress`: Ingress of Phpldapadmin service by which you can manage your ldap service. If you wish to expose the service to the Internet, you can change the `phpldapadmin.ingress.enabled` to `true` and configure your own domain name - -## Post-installation Operations - -Once the installation is complete, you can manage ldap service through **phpldapadmin**. For local testing, you can access the service through port forwarding. The commands are as follows. - -```bash -kubectl port-forward svc/openldap-phpldapadmin 8080:80 -n openldap -``` - -Now you can now access the phpldapadmin service on your browser via http://127.0.0.1:8080 - -If you have not changed the default values in the above example, its account will be **cn=admin,dc=devstream,dc=org** and password will be **Not@SecurePassw0rd**. - -**Note**: If you're familiar with OpenLDAP, then you don't need to continue reading the tutorial below, you can just go ahead and integrate ldap for your service. - -### Importing Your Data - -The following is a sample file, if you have changed the above configuration, remember to replace `dc=devstream,dc=org` with your own. - -``` -dn: cn=admin,dc=devstream,dc=org -cn: admin -objectclass: organizationalRole - -dn: ou=Group,dc=devstream,dc=org -cn: Group -objectclass: organizationalRole -ou: Group - -# confluence organizationalUnit -dn: ou=confluence,ou=Group,dc=devstream,dc=org -objectclass: organizationalUnit -objectclass: top -ou: confluence - -# confluence administrators group -dn: cn=confluence-administrators,ou=confluence,ou=Group,dc=devstream,dc=org -cn: confluence-administrators -description:: d2lraeeuoeeQhue7hA== -objectclass: groupOfUniqueNames -uniquemember: uid=example,ou=People,dc=devstream,dc=org - -# confluence users group -dn: cn=confluence-users,ou=confluence,ou=Group,dc=devstream,dc=org -cn: confluence-users -description:: d2lraeaZrumAmueUqOaItw== -objectclass: groupOfUniqueNames -uniquemember: uid=example,ou=People,dc=devstream,dc=org - -# jira organizationalUnit -dn: ou=jira,ou=Group,dc=devstream,dc=org -objectclass: organizationalUnit -objectclass: top -ou: jira - -# jira administrators Group -dn: cn=jira-administrators,ou=jira,ou=Group,dc=devstream,dc=org -cn: jira-administrators -description:: amlyYeeuoeeQhue7hA== -objectclass: groupOfUniqueNames -uniquemember: uid=example,ou=People,dc=devstream,dc=org - -# jira users group -dn: cn=jira-software-users,ou=jira,ou=Group,dc=devstream,dc=org -cn: jira-software-users -description:: amlyYeeuoeeQhue7hA== -objectclass: groupOfUniqueNames -uniquemember: uid=example,ou=People,dc=devstream,dc=org - -dn: ou=People,dc=devstream,dc=org -objectclass: organizationalUnit -ou: People - -# People for example -dn: uid=example,ou=People,dc=devstream,dc=org -cn: example -gidnumber: 500 -givenname: example -homedirectory: /home/example -loginshell: /bin/sh -mail: example@devstream.org -objectclass: inetOrgPerson -objectclass: posixAccount -objectclass: top -sn: example -uid: example -uidnumber: 1007 -userpassword: example@123456 -``` - -Login your `phpldapadmin` service and import the sample configuration above.After importing the data successfully, the result is as follows. - - - -### Verify the LDAP Service - -Log in to the container where the ldap service is located, and then use the `ldapsearch` command to query the user(`uid=example,ou=people,dc=devstream,dc=org`) created above - -```bash -root@openldap-openldap-stack-ha-0:/# ldapsearch -x -H ldap://127.0.0.1:389 -b uid=example,ou=people,dc=devstream,dc=org -D "cn=admin,dc=devstream,dc=org" -w Not@SecurePassw0rd - -# extended LDIF -# -# LDAPv3 -# base with scope subtree -# filter: (objectclass=*) -# requesting: ALL -# - -# example, People, devstream.org -dn: uid=example,ou=People,dc=devstream,dc=org -cn: example -gidNumber: 500 -givenName: example -homeDirectory: /home/example -loginShell: /bin/sh -mail: example@devstream.org -objectClass: inetOrgPerson -objectClass: posixAccount -objectClass: top -sn: example -uid: example -uidNumber: 1007 -userPassword:: ZXhhbXBsZUAxMjM0NTY= - -# search result -search: 2 -result: 0 Success - -# numResponses: 2 -# numEntries: 1 -``` - -If your command output is as above, your ldap service is fine. The above `valuesYaml` is only to facilitate your local testing, if you want production available, you also have to configure `replicaCount`, data persistence, etc., refer to [OpenLDAP values.yaml](https://github.com/jp-gouin/helm-openldap/blob/master/values.yaml) diff --git a/docs/plugins/helm-installer/openldap.zh.md b/docs/plugins/helm-installer/openldap.zh.md deleted file mode 100644 index 9e48c3556..000000000 --- a/docs/plugins/helm-installer/openldap.zh.md +++ /dev/null @@ -1,26 +0,0 @@ -# 使用 DevStream 部署 OpenLDAP - -## 前缀匹配 - -`instanceID` 的前缀需要是 `openldap`,最小化 tools 配置示例: - -```yaml -tools: -- name: helm-installer - instanceID: openldap -``` - -## 默认配置 - -| 配置项 | 默认值 | 描述 | -| ---- | ---- | ---- | -| chart.chartPath | "" | 本地 chart 包路径 | -| chart.chartName | helm-openldap/openldap-stack-ha | chart 包名称 | -| chart.version | "" | chart 包版本 | -| chart.timeout | 10m | helm install 的超时时间 | -| chart.upgradeCRDs | true | 是否更新 CRDs(如果有) | -| chart.releaseName | openldap | helm 发布名称 | -| chart.namespace | openldap | 部署的命名空间 | -| chart.wait | true | 是否等待部署完成 | -| repo.url | https://jp-gouin.github.io/helm-openldap/ | helm 仓库地址 | -| repo.name | helm-openldap | helm 仓库名 | diff --git a/docs/plugins/helm-installer/sonarqube.md b/docs/plugins/helm-installer/sonarqube.md deleted file mode 100644 index 4651dd9bf..000000000 --- a/docs/plugins/helm-installer/sonarqube.md +++ /dev/null @@ -1,26 +0,0 @@ -# Install SonarQube with DevStream - -## InstanceID Prefix - -The `instanceID` prefix must be `sonarqube`, the minimum tools configuration example: - -```yaml -tools: -- name: helm-installer - instanceID: sonarqube -``` - -### Default Configs - -| key | default value | description | -| ---- | ---- | ---- | -| chart.chartPath | "" | local chart path | -| chart.chartName | sonarqube/sonarqube | community chart name | -| chart.version | "" | chart version | -| chart.timeout | 10m | this config will wait 5 minutes to deploy | -| chart.releaseName | sonarqube | helm release name | -| chart.upgradeCRDs | true | default update CRD config | -| chart.namespace | sonarqube | namespace where helm to deploy | -| chart.wait | true | whether to wait until installation is complete | -| repo.url | https://SonarSource.github.io/helm-chart-sonarqube | helm repo address | -| repo.name | sonarqube | helm repo name | diff --git a/docs/plugins/helm-installer/sonarqube.zh.md b/docs/plugins/helm-installer/sonarqube.zh.md deleted file mode 100644 index 7c63a5c2b..000000000 --- a/docs/plugins/helm-installer/sonarqube.zh.md +++ /dev/null @@ -1,103 +0,0 @@ -# 使用 DevStream 部署 SonarQube - -## 1. 前置要求 - -- 有一个可用的 Kubernetes 集群,版本 1.19+。 -- 运行 Sonarqube 小规模服务至少需要 2GB 的 RAM 内存。 -- Sonarqube 官方镜像目前只支持 linux/amd64 架构。 -- 更多硬件配置可参考[官方网站](https://docs.sonarqube.org/latest/requirements/hardware-recommendations/)。 - -## 2、部署架构 - -Sonarqube 内部会使用 Elastcisearch 来做搜索的索引,所以生产环境中需要通过挂载目录的方式持久化数据。 -另外 Sonarqube 也需要一个外部数据库来存储数据,目前支持 `PostgreSQL`,`Oracle`,`Microsoft SQL Sever`,默认使用 `PostgreSQL`。 - -## 3、开始部署 - -下文将介绍如何配置 `sonarqube` 插件,完成 Sonarqube 应用的部署。 - -### 3.1、默认配置 - -`sonarqube` 插件的配置项多数都有默认值,具体默认值信息如下表: - -| 配置项 | 默认值 | 描述 | -|-------------------| ---- | ---- | -| chart.chartName | sonarqube/sonarqube | helm chart 包名称 | -| chart.timeout | 10m | helm install 的超时时间 | -| chart.version | "" | chart 版本 | -| chart.upgradeCRDs | true | 是否更新 CRDs(如果有) | -| chart.releaseName | sonarqube | helm 发布名称 | -| chart.wait | true | 是否等待部署完成 | -| chart.namespace | sonarqube | 部署的命名空间 | -| repo.url | https://SonarSource.github.io/helm-chart-sonarqube| helm 仓库地址 | -| repo.name | sonarqube | helm 仓库名 | - -### 3.2、测试环境 - -在测试环境中可以使用如下配置: - -```yaml -tools: -- name: helm-installer - instanceID: sonarqube-001 - dependsOn: [ ] - options: - valuesYaml: | - prometheusExporter: - enabled: false -``` - -在该配置下: -- sonarqube 存储使用集群默认的 `StorageClass`。 -- 默认使用 `PostgreSQL` 数据库来存储数据,使用集群默认的 `StorageClass`。 -- 默认使用 `NodePort` 对外暴露 9000 端口。 - -### 3.3、生产环境 - -#### 3.3.1、证书配置 - -- 使用已有证书 - 1. 在集群中创建 `Secret` 保存证书信息。 - 2. 在 `valuesYaml` 配置项中增加如下证书配置。 - -```yaml -valuesYaml: | - tls: - # secret 名称 - - secretName: chart-example-tls - hosts: - # 证书对应的域名 - - chart-example.local -``` - -#### 3.3.2、存储配置 - -- 数据库配置(以 PostgreSQL 为例) - 1. 在外部配置高可用数据库。 - 2. 在 `valuesYaml` 配置项中增加配置: - -```yaml -valuesYaml: | - postgresql: - enabled: false - jdbcOverwrite: - enabled: true - # PostgreSQL 数据库连接配置 - jdbcUrl: "jdbc:postgresql://myPostgress/myDatabase?socketTimeout=1500" - jdbcUsername: "sonarUser" - jdbcPassword: "sonarPass" -``` - -- SonarQube 存储配置 - 1. 在集群中创建需要的 `StorageClass`。 - 2. 在 `valuesYaml` 配置项中增加配置: - -```yaml -valuesYaml: | - persistence: - enabled: true - # 使用集群中创建的 storageclass 名称 - storageclass: prod-storageclass - # 使用的磁盘大小 - size: 20g -``` diff --git a/docs/plugins/helm-installer/tekton.md b/docs/plugins/helm-installer/tekton.md deleted file mode 100644 index 4b4ed8472..000000000 --- a/docs/plugins/helm-installer/tekton.md +++ /dev/null @@ -1,26 +0,0 @@ -# Install Tekton with DevStream - -## InstanceID Prefix - -The `instanceID` prefix must be `tekton`, the minimum tools configuration example: - -```yaml -tools: -- name: helm-installer - instanceID: tekton -``` - -## Default Configs - -| key | default value | description | -| ---- | ---- | ---- | -| chart.chartPath | "" | local chart path | -| chart.chartName | tekton/tekton-pipeline | chart name | -| chart.version | "" | chart version | -| chart.timeout | 10m | this config will wait 5 minutes to deploy | -| chart.upgradeCRDs | true | default update CRD config | -| chart.releaseName | tekton | helm release name | -| chart.wait | true | whether to wait until installation is complete | -| chart.namespace | tekton | namespace where helm to deploy | -| repo.url | https://steinliber.github.io/tekton-helm-chart/ | helm community repo address | -| repo.name | tekton | helm repo name | diff --git a/docs/plugins/helm-installer/tekton.zh.md b/docs/plugins/helm-installer/tekton.zh.md deleted file mode 100644 index d283ef2a3..000000000 --- a/docs/plugins/helm-installer/tekton.zh.md +++ /dev/null @@ -1,26 +0,0 @@ -# 使用 DevStream 部署 Tekton - -## 前缀匹配 - -`instanceID` 的前缀需要是 `tekton`,最小化 tools 配置示例: - -```yaml -tools: -- name: helm-installer - instanceID: tekton -``` - -## 默认配置 - -| 配置项 | 默认值 | 描述 | -| ---- | ---- | ---- | -| chart.chartPath | "" | 本地 chart 包路径 | -| chart.chartName | tekton/tekton-pipeline | helm 包名称 | -| chart.version | "" | chart 版本 | -| chart.timeout | 5m | 等待部署成功的时间 | -| chart.upgradeCRDs | true | 默认更新 CRD 配置(如果存在的话) | -| chart.releaseName | tekton | helm 发布名称 | -| chart.wait | true | 是否等待部署完成 | -| chart.namespace | tekton | helm 部署的命名空间名称 | -| repo.url | https://steinliber.github.io/tekton-helm-chart/ | helm 官方仓库地址 | -| repo.name | tekton | helm 仓库名 | diff --git a/docs/plugins/helm-installer/vault.md b/docs/plugins/helm-installer/vault.md deleted file mode 100644 index 0c3fdf818..000000000 --- a/docs/plugins/helm-installer/vault.md +++ /dev/null @@ -1,112 +0,0 @@ -# Install Vault with DevStream - -## InstanceID Prefix - -The `instanceID` prefix must be `vault`, the minimum tools configuration example: - -```yaml -tools: -- name: helm-installer - instanceID: vault -``` - -## Default Configs - -| key | default value | description | -| ---- | ---- | ---- | -| chart.chartPath | "" | local chart path | -| chart.chartName | hashicorp/vault | chart name | -| chart.version | "" | chart version | -| chart.timeout | 5m | this config will wait 5 minutes to deploy | -| chart.releaseName | vault | helm release name | -| chart.upgradeCRDs | true | default update CRD config | -| chart.wait | true | whether to wait until installation is complete | -| chart.namespace | vault | namespace where helm to deploy | -| repo.url | https://helm.releases.hashicorp.com | helm official repo address | -| repo.name | hashicorp | helm repo name | - -## Initialize all the Vault pods - -After installing the Vault on k8s, you can initialize all pods of the Vault on k8s. To know more about the Vault, you can refer to: - -- [Vault init](https://www.vaultproject.io/docs/commands/operator/init) -- [Vault:Seal/Unseal](https://www.vaultproject.io/docs/concepts/seal) - -At first, you must install [jq](https://stedolan.github.io/jq/) tool: jq is a lightweight and flexible command-line JSON processor. -[Download jq](https://stedolan.github.io/jq/download/) - -In the command below, the variable `$NAMESPACE` you should replace with "hashicorp" if you do not modify the namespace variable. -Otherwise, use the namespace name you replaced. - -1. Initialize vault-0 -```shell -# Initialize vault-0 with one key share and one key threshold. -kubectl exec vault-0 -n $NAMESPACE -- vault operator init -key-shares=1 -key-threshold=1 -format=json > cluster-keys.json -``` -2. Display the unseal key -```shell -# Display the unseal key found in cluster-keys.json -cat cluster-keys.json | jq -r ".unseal_keys_b64[]" -``` -3. Create a variable to capture the Vault unseal key -```shell -# Create a variable named VAULT_UNSEAL_KEY to capture the Vault unseal key. -VAULT_UNSEAL_KEY=$(cat cluster-keys.json | jq -r ".unseal_keys_b64[]") -``` -4. Unseal vault-0 -```shell -# Unseal vault-0 running on the vault-0 pod. -kubectl exec vault-0 -n $NAMESPACE -- vault operator unseal $VAULT_UNSEAL_KEY -``` -You will see the above command's output like this. Make sure the value of `Initialized` is 'true' and the value of `Sealed` is 'false'. -```shell -Key Value ---- ----- -Seal Type shamir -Initialized true -Sealed false -Total Shares 1 -Threshold 1 -Version 1.9.2 -Storage Type raft -Cluster Name vault-cluster-14052440 -Cluster ID 7630cd33-2ee1-39c1-db3f-e48a6d79970a -HA Enabled true -HA Cluster https://vault-0.vault-internal:8201 -HA Mode active -Active Since 2022-04-23T16:45:47.6060163Z -Raft Committed Index 30 -Raft Applied Index 30 -``` - -5. Initialize vault-1 and vault-2 like vault-0 - -```shell -# Initialize vault-1 -kubectl exec vault-1 -n $NAMESPACE -- vault operator init -key-shares=1 -key-threshold=1 -format=json > cluster-keys.json -VAULT_UNSEAL_KEY=$(cat cluster-keys.json | jq -r ".unseal_keys_b64[]") -kubectl exec vault-1 -n $NAMESPACE -- vault operator unseal $VAULT_UNSEAL_KEY -# Initialize vault-2 -kubectl exec vault-1 -n $NAMESPACE -- vault operator init -key-shares=1 -key-threshold=1 -format=json > cluster-keys.json -VAULT_UNSEAL_KEY=$(cat cluster-keys.json | jq -r ".unseal_keys_b64[]") -kubectl exec vault-1 -n $NAMESPACE -- vault operator unseal $VAULT_UNSEAL_KEY -``` - -6. Verify all the pods status -```shell -# Verify all the Vault pods are running and ready. -kubectl get pods -n $NAMESPACE -``` - -You will see the above command's outputs like this below. Make sure all the pods are running and ready. -```shell -NAME READY STATUS RESTARTS AGE -vault-0 1/1 Running 0 2m29s -vault-1 1/1 Running 0 2m29s -vault-2 1/1 Running 0 2m29s -vault-agent-injector-68dc986-bnsj2 1/1 Running 0 2m28s -``` - -7. After the above operations, you want to use the Vault to write/read secrets. You need to follow the documentation of the hashicorp Vault: -- [Set a secret in Vault](https://learn.hashicorp.com/tutorials/vault/kubernetes-minikube-consul#set-a-secret-in-vault) -- [Your First Secret](https://learn.hashicorp.com/tutorials/vault/getting-started-first-secret) diff --git a/docs/plugins/helm-installer/vault.zh.md b/docs/plugins/helm-installer/vault.zh.md deleted file mode 100644 index 15fd75f37..000000000 --- a/docs/plugins/helm-installer/vault.zh.md +++ /dev/null @@ -1,26 +0,0 @@ -# 使用 DevStream 部署 Vault - -## 前缀匹配 - -`instanceID` 的前缀需要是 `vault`, 最小化 tools 配置示例: - -```yaml -tools: -- name: helm-installer - instanceID: vault -``` - -## 默认配置 - -| 配置项 | 默认值 | 描述 | -| ---- | ---- | ---- | -| chart.chartPath | "" | 本地 chart 包路径 | -| chart.chartName | hashicorp/vault | chart 包名称 | -| chart.version | "" | chart 包版本 | -| chart.timeout | 10m | helm install 的超时时间 | -| chart.upgradeCRDs | true | 是否更新 CRDs(如果有) | -| chart.releaseName | vault | helm 发布名称 | -| chart.namespace | vault | 部署的命名空间 | -| chart.wait | true | 是否等待部署完成 | -| repo.url | https://helm.releases.hashicorp.com | helm 仓库地址 | -| repo.name | hashicorp | helm 仓库名 | diff --git a/docs/plugins/jenkins-pipeline.md b/docs/plugins/jenkins-pipeline.md deleted file mode 100644 index dff658063..000000000 --- a/docs/plugins/jenkins-pipeline.md +++ /dev/null @@ -1,19 +0,0 @@ -# jenkins-pipeline Plugin - -This plugin is used to create a `Jenkins Pipeline` for Github/Gitlab Repo. - -## Usage - -The following content is an example of the "tool file". - -For more information on the main config, the tool file and the var file of DevStream, see [Core Concepts Overview](../core-concepts/overview.md) and [DevStream Configuration](../core-concepts/config.md). - -``` yaml ---8<-- "jenkins-pipeline.yaml" -``` - -**Notes:** - -- `scm` config option represents codebase location; for more info, you can refer to [SCM Config](./scm-option.md). -- The `pipeline` config option controls `CI` stages; you can refer to [Pipeline Config](./pipeline.md) for more info. -- `jenkins.token` is the password of `jenkins.user`. \ No newline at end of file diff --git a/docs/plugins/jenkins-pipeline.zh.md b/docs/plugins/jenkins-pipeline.zh.md deleted file mode 100644 index 81969d861..000000000 --- a/docs/plugins/jenkins-pipeline.zh.md +++ /dev/null @@ -1,279 +0,0 @@ -# jenkins-pipeline 插件 - -`jenkins-pipeline` 插件用于打通 GitHub/GitLab 和 Jenkins,实现自动化创建 Jenkins Pipeline 的功能。 - -本文将演示: - -1. 通过 [`repo-scaffolding`](./repo-scaffolding.zh.md) 插件在 GitLab 上创建一个 Java Spring Boot 项目脚手架; -2. 通过 `jenkins-pipeline` 插件在 Jenkins 上创建一条 Java Spring Boot 的 CI 流水线; -3. 通过 `jenkins-pipeline` 插件实现在 GitLab 和 Jenkins 上分别配置相应参数,实现当 GitLab 上的代码库有 push 或者 merge 事件时,自动触发 Jenkins 上的流水线运行,同时流水线的执行结果自动回写到 GitLab 上。 - -!!! tip "提示" - - GitHub 与 GitLab 的主要区别在于 DevStream tool config 的 options.scm.url 以及所需要的 token 等不同。 - -## 1、前置要求 - -**必须满足** - -- 一套可用的 Jenkins 环境 -- 一套可用的 GitLab 环境 -- 一套可用的 Harbor 环境 -- Jenkins 与 GitLab、Harbor 网络互通 -- 执行 dtm 的主机与 Jenkins、GitLab 网络互通 - -!!! warning "注意" - - 当前插件暂时只支持对接使用 dtm 部署的 Jenkins。 - -本文基于如下环境编写: - -- **系统**:一台装有 CentOS 7 的云主机; -- **k8s**:minikube 方式部署的单节点集群; -- **Jenkins、Harbor 和 GitLab**:使用 dtm 方式部署。 - -## 2、配置 - -你需要预先准备好 GitLab 的 token 和镜像仓库(Harbor 等)的密码,然后配置到环境变量里,例如(记得替换占位字符): - -```shell -export IMAGE_REPO_PASSWORD=YOUR_IMAGE_REPO_PASSWORD -export GITLAB_TOKEN=YOUR_GITLAB_TOKEN -``` - -!!! tip "提示" - - 如果是 GitHub,则这里的环境变量改成: - - ```shell - export IMAGE_REPO_PASSWORD=YOUR_IMAGE_REPO_PASSWORD - export GITHUB_TOKEN=YOUR_GITHUB_TOKEN - ``` - -然后准备 DevStream 插件配置: - -下面的配置文件展示的是"tool file"的内容。 - -关于更多关于DevStream的主配置、tool file、var file的信息,请阅读[核心概念概览](../core-concepts/overview.zh.md)和[DevStream配置](../core-concepts/config.zh.md). - -```yaml -tools: -- name: repo-scaffolding - instanceID: springboot - dependsOn: [] - options: - destinationRepo: - owner: root - name: spring-demo - branch: master - scmType: gitlab - baseURL: YOUR_GITLAB_ADDR - token: [[ env GITLAB_TOKEN ]] - sourceRepo: - owner: devstream-io - name: dtm-repo-scaffolding-java-springboot - scmType: github -- name: jenkins-pipeline - instanceID: default - dependsOn: [repo-scaffolding.springboot] - options: - jenkins: - url: YOUR_JENKINS_ADDR - user: admin - enableRestart: true - scm: - url: git@YOUR_REPO_CLONE_ADDRESS/root/spring-demo - branch: master - apiURL: YOUR_JENKINS_ADDR - token: [[ env GITLAB_TOKEN ]] - pipeline: - jobName: test-job - configLocation: https://raw.githubusercontent.com/devstream-io/dtm-jenkins-pipeline-example/main/springboot/Jenkinsfile - imageRepo: - url: YOUR_HARBOR_ADDR - user: admin - password: [[ env IMAGE_REPO_PASSWORD ]] -``` - -上述配置文件中使用的 GitLab、Jenkins 和 Harbor 访问地址需要替换成你的环境中实际地址。例如: - -- **YOUR_GITLAB_ADDR**: http://44.33.22.11:30080 -- **YOUR_REPO_CLONE_ADDRESS**: http://44.33.22.11:30022 -- **YOUR_JENKINS_ADDR**: http://44.33.22.11:32000 -- **YOUR_HARBOR_ADDR**: http://harbor.example.com:80 - -除了这几个必须修改的配置项外,其他配置项你可以在确保理解含义的前提下灵活决定是否调整。 - -*注意:这个配置示例仅是 tool config,完整的 DevStream 配置文件还需要补充 core config 等内容,具体参考[这个文档](../core-concepts/config.zh.md)。* - -## 3、开始执行 - -你可以使用 `dtm apply` 命令开始应用当前配置: - -- `dtm apply -f config-jenkins-pipeline.yaml -y` - -这个过程的日志大致如下: - -```shell -2022-09-05 09:01:08 ℹ [INFO] Apply started. -2022-09-05 09:01:08 ℹ [INFO] Using dir to store plugins. -2022-09-05 09:01:09 ℹ [INFO] Using local backend. State file: devstream.state. -2022-09-05 09:01:09 ℹ [INFO] Tool (repo-scaffolding/springboot) found in config but doesn't exist in the state, will be created. -2022-09-05 09:01:09 ℹ [INFO] Tool (jenkins-pipeline/default) found in config but doesn't exist in the state, will be created. -2022-09-05 09:01:09 ℹ [INFO] Start executing the plan. -2022-09-05 09:01:09 ℹ [INFO] Changes count: 2. -2022-09-05 09:01:09 ℹ [INFO] -------------------- [ Processing progress: 1/2. ] -------------------- -2022-09-05 09:01:09 ℹ [INFO] Processing: (repo-scaffolding/springboot) -> Create ... -2022-09-05 09:01:10 ✔ [SUCCESS] Tool (repo-scaffolding/springboot) Create done. -2022-09-05 09:01:10 ℹ [INFO] -------------------- [ Processing progress: 2/2. ] -------------------- -2022-09-05 09:01:10 ℹ [INFO] Processing: (jenkins-pipeline/default) -> Create ... -2022-09-05 09:01:10 ℹ [INFO] Secret jenkins/docker-config has been created. -2022-09-05 09:01:14 ✔ [SUCCESS] Tool (jenkins-pipeline/default) Create done. -2022-09-05 09:01:14 ℹ [INFO] -------------------- [ Processing done. ] -------------------- -2022-09-05 09:01:14 ✔ [SUCCESS] All plugins applied successfully. -2022-09-05 09:01:14 ✔ [SUCCESS] Apply finished. -``` - -## 4、执行结果 - -你可以在 GitLab、Jenkins 上查看本次执行结果: - -- **Repo Scaffolding** - -首先你可以在 GitLab 上可以看 repo scaffolding 的效果,dtm 为你创建了一个 Java Spring Boot 项目脚手架: - -
- ![Repo scaffolding](./jenkins-pipeline/repo-scaffolding.png){ width="1000" } -
Repo scaffolding
-
- -- **Pipeline** - -接着你可以在 Jenkins 上看到刚才 dtm 为你创建的 Pipeline: - -
- ![Pipeline](./jenkins-pipeline/pipeline.png){ width="1000" } -
Pipeline
-
- -如果你点开这个 test-job,就能看到它已经被触发了一次,执行结果如下: - -
- ![Pipeline console](./jenkins-pipeline/pipeline-console.png){ width="1000" } -
Pipeline console
-
- -- **状态回写** - -然后你可以回到 GitLab,看一下 Jenkins Pipeline 的执行结果有没有被成功回写: - -
- ![GitLab status](./jenkins-pipeline/gitlab-status.png){ width="1000" } -
GitLab status
-
- -- **检查镜像** - -通过 Jenkins 的日志你可以找到刚才 push 的镜像地址为 `harbor.example.com:80/library/spring-demo:latest`: - -
- ![Jenkins' logs](./jenkins-pipeline/jenkins-logs.png){ width="1000" } -
Jenkins' logs
-
- -// TODO(daniel-hutao): 补充 Harbor 截图 - -最后你可以通过 `docker pull` 下载该镜像: - -
- ![Docker pulling](./jenkins-pipeline/docker-pull.png){ width="1000" } -
Docker pulling
-
- -## 配置详解 - -当前插件完整配置如下: - -``` yaml ---8<-- "jenkins-pipeline.yaml" -``` - - -## 离线配置 (下文待进一步整理) - -目前的 `jenkins-pipeline` 插件配置过程中会连接外部网络来安装插件和配置 `pipeline` 的共享库,为了支持在离线环境中使用 `jenkins-pipeline` 来创建和管理 `jenkins` 流水线,我们在 `jenkins-pipeline` 插件中提供了一个选项 `offline` 用于表示需要在离线环境下配置 `jenkins-pipeline`。 - -### Jenkins 插件 - -流水线正常运行需要依赖 `jenkins` 的几个插件。在离线环境中因为无法下载外部环境的插件,需要在 `jenkins` 预先安装好插件或者使用已经有插件的 `jenkins` 镜像,devstream 官方也提供了已预先安装好所有依赖的镜像 `devstreamdev/jenkins:2.361.1-jdk11-dtm-0.2`。 - -依赖的插件如下: -| 插件名 | 作用 | 备注 | -|--------------|----------------|----------------------------| -| dingding-notifications | 用于发送钉钉通知 | 如果需要使用钉钉通知,则需要安装此插件 | -| github-branch-source | jenkins github 插件 | 如果代码仓库使用的是 github,则必须安装此插件 | -| gitlab-plugin | jenkins gitlab 插件 | 如果代码仓库使用的是 gitlab,则必须安装此插件 | -| sonar | sonar sanner 代码扫描插件 | 如果需要使用 sonarqube,则需要安装此插件 | -| kubernetes | jenkins kubernetes 插件 | 用于 jenkins runner, 必须安装此插件 | -| git | jenkins git 插件 | 用于代码的克隆和权限认证, 必须安装此插件 | -| configuration-as-code | jenkins 配置即代码插件 | 用于 devstream 配置 jenkins 的全局配置, 必须安装此插件| - -### 共享库 - -在 `jenkins` 中 devstream 默认使用共享库来管理 jenkins 流水线的共用代码,在离线环境中因为无法连接共享库,所以 devstream 提供了单独的 `Jenkinfile` 配置。 - -### app 示例配置 - -```yaml -apps: -- name: test - spec: - language: java - framework: springboot - repo: - url: gitlab.com/root/test.git - branch: main - token: [[ env GITLAB_TOKEN ]] - repoTemplate: - url: https://github.com/devstream-io/dtm-repo-scaffolding-java-springboot.git - ci: - - type: template - templateName: ci-pipeline - -pipelineTemplates: -- name: ci-pipeline - type: jenkins-pipeline - options: - branch: main - jenkins: - url: jenkins.com - user: admin - offline: true # 在此处设置 offline 为 true, 即开启该 jenkins-pipeline 的离线模式 - imageRepo: - user: repoUser - password: [[ env IMAGE_REPO_PASSWORD ]] -``` - -使用该配置可得到以下输出: - -```text -2022-12-02 19:51:52 ℹ [INFO] Apply started. -2022-12-02 19:51:52 ℹ [INFO] Using local backend. State file: devstream-app.state. -2022-12-02 19:51:52 ℹ [INFO] Tool (repo-scaffolding/test) found in config but doesn't exist in the state, will be created. -2022-12-02 19:51:52 ℹ [INFO] Tool (jenkins-pipeline/test) found in config but doesn't exist in the state, will be created. -2022-12-02 19:51:52 ℹ [INFO] Start executing the plan. -2022-12-02 19:51:52 ℹ [INFO] Changes count: 2. -2022-12-02 19:51:52 ℹ [INFO] -------------------- [ Processing progress: 1/2. ] -------------------- -2022-12-02 19:51:52 ℹ [INFO] Processing: (repo-scaffolding/test) -> Create ... -2022-12-02 19:51:52 ℹ [INFO] github start to download repoTemplate... -2022-12-02 19:51:57 ✔ [SUCCESS] Tool (repo-scaffolding/test) Create done. -2022-12-02 19:51:57 ℹ [INFO] -------------------- [ Processing progress: 2/2. ] -------------------- -2022-12-02 19:51:57 ℹ [INFO] Processing: (jenkins-pipeline/test) -> Create ... -2022-12-02 19:51:58 ℹ [INFO] jenkins plugin imageRepo start config... -2022-12-02 19:51:58 ⚠ [WARN] jenkins gitlab ssh key not config, private repo can't be clone -2022-12-02 19:52:00 ℹ [INFO] jenkins start config casc... -2022-12-02 19:52:07 ✔ [SUCCESS] Tool (jenkins-pipeline/test) Create done. -2022-12-02 19:52:07 ℹ [INFO] -------------------- [ Processing done. ] -------------------- -2022-12-02 19:52:07 ✔ [SUCCESS] All plugins applied successfully. -2022-12-02 19:52:07 ✔ [SUCCESS] Apply finished. -``` diff --git a/docs/plugins/jenkins-pipeline/docker-pull.png b/docs/plugins/jenkins-pipeline/docker-pull.png deleted file mode 100644 index f242bbd4c..000000000 Binary files a/docs/plugins/jenkins-pipeline/docker-pull.png and /dev/null differ diff --git a/docs/plugins/jenkins-pipeline/gitlab-status.png b/docs/plugins/jenkins-pipeline/gitlab-status.png deleted file mode 100644 index a63c2de7c..000000000 Binary files a/docs/plugins/jenkins-pipeline/gitlab-status.png and /dev/null differ diff --git a/docs/plugins/jenkins-pipeline/jenkins-logs.png b/docs/plugins/jenkins-pipeline/jenkins-logs.png deleted file mode 100644 index e2f87942f..000000000 Binary files a/docs/plugins/jenkins-pipeline/jenkins-logs.png and /dev/null differ diff --git a/docs/plugins/jenkins-pipeline/pipeline-console.png b/docs/plugins/jenkins-pipeline/pipeline-console.png deleted file mode 100644 index 4b5e613c5..000000000 Binary files a/docs/plugins/jenkins-pipeline/pipeline-console.png and /dev/null differ diff --git a/docs/plugins/jenkins-pipeline/pipeline.png b/docs/plugins/jenkins-pipeline/pipeline.png deleted file mode 100644 index d5c6192d0..000000000 Binary files a/docs/plugins/jenkins-pipeline/pipeline.png and /dev/null differ diff --git a/docs/plugins/jenkins-pipeline/repo-scaffolding.png b/docs/plugins/jenkins-pipeline/repo-scaffolding.png deleted file mode 100644 index 0c124b9d2..000000000 Binary files a/docs/plugins/jenkins-pipeline/repo-scaffolding.png and /dev/null differ diff --git a/docs/plugins/jira.md b/docs/plugins/jira.md deleted file mode 100644 index 1e9851989..000000000 --- a/docs/plugins/jira.md +++ /dev/null @@ -1,20 +0,0 @@ -# jira Plugin - -This plugin integrates Jira with your GitHub repo. - -## Usage - -The following content is an example of the "tool file". - -For more information on the main config, the tool file and the var file of DevStream, see [Core Concepts Overview](../core-concepts/overview.md) and [DevStream Configuration](../core-concepts/config.md). - -```yaml ---8<-- "jira.yaml" -``` - -**Notes:** - -- Jira language must be English -- There should be an existing Jira project -- `scm` config option represents codebase location; for more info, you can refer to [SCM Config](./scm-option.md). -- `jira.token` should be created before using this plugin; you can refer to [Manage API tokens for your Atlassian account](https://support.atlassian.com/atlassian-account/docs/manage-api-tokens-for-your-atlassian-account/). diff --git a/docs/plugins/jira.zh.md b/docs/plugins/jira.zh.md deleted file mode 100644 index 2c83fb4ec..000000000 --- a/docs/plugins/jira.zh.md +++ /dev/null @@ -1,16 +0,0 @@ -# jira 插件 - -该插件用于将 Github 中的 Issue 实时同步到 Jira 中。 - -## 用例 - -```yaml ---8<-- "jira.yaml" -``` - -**注意:** - -- Jira 项目的语言必须是英语。 -- Jira 的项目必须是已存在。 -- `scm` 配置字段用于表示代码仓库的配置信息,具体配置可查看[SCM配置项](./scm-option.zh.md)。 -- `jira.token` 需要先在 Jira 中创建,如何创建可以查询文档 [Manage API tokens for your Atlassian account](https://support.atlassian.com/atlassian-account/docs/manage-api-tokens-for-your-atlassian-account/)。 diff --git a/docs/plugins/pipeline.md b/docs/plugins/pipeline.md deleted file mode 100644 index 9b0c9c84d..000000000 --- a/docs/plugins/pipeline.md +++ /dev/null @@ -1,50 +0,0 @@ -# Pipeline Config Option - -## imageRepo - -Devstream will add a build stage in `CI` if you have configured this option. This stage will build a docker image and push this image to the configured image repository(like Dockerhub or self-host Harbor). - -### Options - -| Option | Description | -| ------ | ------------------------------------------ | -| user | the user name of image repository | -| url | the address of image repository, the default value is Dockerhub address | -| password| the password of `user`, this config option is used for auth when pushing image | - -## dingTalk - -Devstream will add a notify stage in `CI` if you have configured this option. This stage will notify DingDing whether this `CI` is successful or failed. - -### Options - -| Option | Description | -| ------------- | ------------------------------------------------------------ | -| name | the name of dingding robot | -| webhook | the webhook of dingding robot | -| securityType | the auth method of dingding robot, support SECRET/KEYWORD for now | -| securityValue | if you set `securityType` to SECRET, you can set this value to secret value | - -## sonarqube - -Devstream will add a scanner stage in `CI` if you have configured this option. This stage will use [sonarqube](https://www.sonarqube.org/) to scan code. - -### Options - -| Option | Description | -| ------ | ----------------------------------------------------------------------------------------------------------------------- | -| name | the name of sonarqube | -| token | the token of sonarqube, you can refer to this [doc](https://sonarqube.inria.fr/sonarqube/documentation/user-guide/user-token/) to get this token | -| url | the url address of soanrqube | - -## language - -If you config this option, Devstream will add the language's default option in the pipeline. For example, if you set `name` to `golang`, `CI` test stage will use `go test ./...` to run the test command. - -### Options - -| Option | Description | -| --------- | ---------------- | -| name | name of programing language | -| framework | the framework | -| version | the version of programing language | diff --git a/docs/plugins/pipeline.zh.md b/docs/plugins/pipeline.zh.md deleted file mode 100644 index 895b7ec16..000000000 --- a/docs/plugins/pipeline.zh.md +++ /dev/null @@ -1,50 +0,0 @@ -# Pipeline 配置项 - -## imageRepo - -若设置了该配置项,则流水线中会基于项目打包出一个 Docker 项目镜像并推送到配置的镜像仓库中。 - -### 具体配置字段 - -| 字段名 | 描述 | -| ------ | ------------------------------------------- | -| user | 镜像仓库的用户名 | -| url | 镜像仓库的地址,默认为 Dockerhub 的官方地址 | -| password | 镜像仓库的用户密码,用于上插件镜像时的权限认证 | - -## dingTalk - -若设置了该配置项,则会在流水线失败或者成功后往指定的钉钉群发送通知。 - -### 具体配置字段 - -| 字段名 | 描述 | -| ------------- | ------------------------------------------------------------ | -| name | 钉钉机器人的名称 | -| webhook | 在钉钉群中创建机器人后获取到的回调地址 | -| securityType | 钉钉发送通知的认证方式,目前支持 SECRET 和 KEY | -| securityValue | 若 securityType 配置为 SECRET,该字段表示对应的 SECRET VALUE | - -## sonarqube - -若设置了该配置项,则会在流水线运行测试的同时使用 [sonarqube](https://www.sonarqube.org/) 扫描代码。 - -### 具体配置字段 - -| 字段名 | 描述 | -| ------ | ----------------------------------------------------------------------------------------------------------------------- | -| name | sonarqube 的配置名称 | -| token | sonarqube 的 token,获取方式可以查阅该[文档](https://sonarqube.inria.fr/sonarqube/documentation/user-guide/user-token/) | -| url | soanrqube 的 url 地址 | - -## language - -若设置了该配置项,则会在流水线模版渲染时配置语言和框架的默认值。如设置字段 `name` 为 `golang`,则流水线中的测试镜像会使用 `golang` 镜像,命令会使用 `go test ./...`。 - -### 具体配置字段 - -| 字段名 | 描述 | -| --------- | ---------------- | -| name | 编程语言的名称 | -| framework | 所使用的框架 | -| version | 编程语言使用版本 | diff --git a/docs/plugins/plugins-list.md b/docs/plugins/plugins-list.md deleted file mode 100644 index d234d2ea7..000000000 --- a/docs/plugins/plugins-list.md +++ /dev/null @@ -1,35 +0,0 @@ -# Plugins List - -| Type | Plugin | Note | Usage/Doc | -|--------------------------------|-----------------------------|--------------------------------|---------------------------------------| -| Issue Tracking | trello | Trello Configuration | [doc](trello.md) | -| Issue Tracking | jira | Jira Configuration | [doc](jira.md) | -| Issue Tracking | zentao | Zentao installation | [doc](zentao.md) | -| Source Code Management | repo-scaffolding | App scaffolding | [doc](repo-scaffolding.md) | -| Source Code Management | gitlab-ce-docker | GitLab CE version installation | [doc](gitlab-ce-docker.md) | -| CI | jenkins-pipeline | Jenkins pipeline creation | [doc](jenkins-pipeline.md) | -| CI | github-actions | GitHub Actions CI | [doc](github-actions.md) | -| CI | gitlab-ci | Generic GitLab CI | [doc](gitlab-ci.md) | -| CI | ci-generic | Generic CI plugin | [doc](ci-generic.md) | -| CD/GitOps | argocdapp | Argo CD Application creation | [doc](argocdapp.md) | -| Image Repository | harbor-docker | Harbor Docker compose install | [doc](harbor-docker.md) | -| Deployment | helm-installer | Helm chart install | [doc](helm-installer/helm-installer.md)| - -Or, to get a list of plugins, run: - -```shell -$ dtm list plugins -argocdapp -ci-generic -devlake-config -github-actions -gitlab-ce-docker -gitlab-ci -harbor-docker -helm-installer -jenkins-pipeline -jira -repo-scaffolding -trello -zentao -``` diff --git a/docs/plugins/plugins-list.zh.md b/docs/plugins/plugins-list.zh.md deleted file mode 100644 index c9087a15a..000000000 --- a/docs/plugins/plugins-list.zh.md +++ /dev/null @@ -1,35 +0,0 @@ -# 插件列表 - -| Type | Plugin | Note | Usage/Doc | -| ---------------------- | ------------------- | ------------------------------- | --------------------------------------- | -| Issue Tracking | trello | Trello 配置 | [doc](trello.md) | -| Issue Tracking | jira | Jira 配置 | [doc](jira.md) | -| Issue Tracking | zentao | Zentao 安装 | [doc](zentao.md) | -| Source Code Management | repo-scaffolding | 应用仓库脚手架 | [doc](repo-scaffolding.md) | -| Source Code Management | gitlab-ce-docker | 使用 docker 安装 GitLab CE 版本 | [doc](gitlab-ce-docker.md) | -| CI | jenkins-pipeline | 创建 Jenkins pipeline | [doc](jenkins-pipeline.md) | -| CI | github-actions | 创建 GitHub Actions | [doc](github-actions.md) | -| CI | gitlab-ci | 创建 GitLab CI | [doc](gitlab-ci.md) | -| CI | ci-generic | 通用 CI 插件 | [doc](ci-generic.md) | -| CD/GitOps | argocdapp | 创建 Argo CD 应用 | [doc](argocdapp.md) | -| Image Repository | harbor-docker | 使用 Docker Compose 安装 Harbor | [doc](harbor-docker.md) | -| Deployment | helm-installer | 使用 Helm 安装工具 | [doc](helm-installer/helm-installer.md) | - -你也可以通过执行以下命令来获取当前的插件列表: - -```shell -$ dtm list plugins -argocdapp -ci-generic -devlake-config -github-actions -gitlab-ce-docker -gitlab-ci -harbor-docker -helm-installer -jenkins-pipeline -jira -repo-scaffolding -trello -zentao -``` diff --git a/docs/plugins/repo-scaffolding.md b/docs/plugins/repo-scaffolding.md deleted file mode 100644 index 7c64e3020..000000000 --- a/docs/plugins/repo-scaffolding.md +++ /dev/null @@ -1,127 +0,0 @@ -# repo-scaffolding Plugin - -This plugin bootstraps a GitHub or GitLab repo with scaffolding code for a web application. - -## Usage - -The following content is an example of the "tool file". - -For more information on the main config, the tool file, and the var file of DevStream, see [Core Concepts Overview](../core-concepts/overview.md) and [DevStream Configuration](../core-concepts/config.md). - -```yaml ---8<-- "repo-scaffolding.yaml" -``` - -**Notes:** - -- If you run `dtm delete`, the repo will be completely removed. -- If the `Update` interface is called, the repo will be completely removed and recreated. -- For the `repo-scaffolding` plugin, we only need `repo`, `delete_repo` permission for the token. -- `destinationRepo` config option represents codebase location; for more info, you can refer to [SCM Config](./scm-option.md). -- `sourceRepo` config option represents codebase location; for more info, you can refer to [SCM Config](./scm-option.md). -- if `destinationRepo` type is `Gitlab`, then `Devstream` supports config `destinationRepo`.`visibility`. This configuration is used to set the permissions of the new repository. The options are `public`, `private`, and `internal`. - - -### vars - -This configuration is used for template rendering, It has default variables listed below: - -```json -{ - "AppName": destinationRepo.repo, - "Repo": { - "Name": destinationRepo.repo, - "Owner": destinationRepo.owner - } -} -``` - -## Outputs - -This plugin has three outputs: - -- `owner` -- `repo` -- `repoURL` - - -## Examples - -### official scaffolding repo config - -These repositories are official scaffolding repo to use for `sourceRepo` config; You can use these repo directly or just create one for yourself. - -| language | org | repo | -|-------------|---------------|-------------------------------------| -| Golang | devstream-io | dtm-repo-scaffolding-golang-gin | -| Golang | devstream-io | dtm-repo-scaffolding-golang-cli | -| Python | devstream-io | dtm-repo-scaffolding-python-flask | -| Java | devstream-io | dtm-repo-scaffolding-java-springboot| - - -### Golang - -```yaml -tools: -- name: repo-scaffolding - instanceID: golang-scaffolding - options: - destinationRepo: - owner: test_owner - org: "" - name: dtm-test-golang - branch: main - scmType: github - sourceRepo: - org: devstream-io - name: dtm-repo-scaffolding-golang-gin - scmType: github - vars: - imageRepo: dtm-test/golang-repo -``` - -This config will create `dtm-test-golang` repo for user test_owner in GitHub, and the variable ImageRepo will be used for template rendering. - -### Golang CLI - -```yaml -tools: -- name: repo-scaffolding - instanceID: golang-cli-scaffolding - options: - destinationRepo: - owner: test_owner - org: "" - name: dtm-test-golang-cli - branch: main - scmType: github - sourceRepo: - org: devstream-io - name: dtm-repo-scaffolding-golang-cli - scmType: github -``` - -This config will create `dtm-test-golang-cli` repo for user test_owner in GitHub. - -### Java Spring - -```yaml -tools: -- name: repo-scaffolding - instanceID: java-scaffolding - options: - destinationRepo: - owner: test_owner - org: "" - name: dtm-test-java - branch: main - baseUrl: 127.0.0.1:30001 - visibility: public - scmType: gitlab - sourceRepo: - org: devstream-io - name: dtm-repo-scaffolding-java-springboot - scmType: github -``` - -This config will create `dtm-test-java` repo for user test_owner in GitHub. diff --git a/docs/plugins/repo-scaffolding.zh.md b/docs/plugins/repo-scaffolding.zh.md deleted file mode 100644 index ceb6703eb..000000000 --- a/docs/plugins/repo-scaffolding.zh.md +++ /dev/null @@ -1,124 +0,0 @@ -# repo-scaffolding 插件 - -这个插件会基于一个脚手架仓库来初始化一个 Gihub 或者 GitLab 仓库。 - -## 用例 - -下面的配置文件展示的是"tool file"的内容。 - -关于更多关于DevStream的主配置、tool file、var file的信息,请阅读[核心概念概览](../core-concepts/overview.zh.md)和[DevStream配置](../core-concepts/config.zh.md). - -```yaml ---8<-- "repo-scaffolding.yaml" -``` - -**注意:** - -- 如果你执行 `dtm delete` 命令,这个仓库将会被删除。 -- 如果你执行 `dtm update` 命令, 这个仓库将会被删除然后重新创建。 -- 对于 `repo-scaffolding` 插件,目前只需要 token 有 `repo`, `delete_repo` 权限即可。 -- `destinationRepo` 配置字段用于表示代码仓库的配置信息,具体配置可查看[SCM配置项](./scm-option.zh.md)。 -- `sourceRepo` 配置字段用于表示代码仓库的配置信息,具体配置可查看[SCM配置项](./scm-option.zh.md)。 -- `destinationRepo` 如果是 `gitlab`, 则支持配置 `destinationRepo.visibility`,此配置用于设置新建仓库的权限,支持的选项有 `public`, `private` 和 `internal`。 - -### 变量 - -这个配置用于设置渲染源脚手架仓库时的变量,以下变量会默认设置: - -```json -{ - "AppName": destinationRepo.repo, - "Repo": { - "Name": destinationRepo.repo, - "Owner": destinationRepo.owner - } -} -``` - -## Outputs - -这个插件有以下三个输出: - -- `owner` -- `repo` -- `repoURL` - -## 示例 - -### 官方支持脚手架项目 - -以下仓库是用于在 `sourceRepo` 设置的官方脚手架仓库,你可以使用这些仓库或者创建自己的脚手架仓库。 - -| language | org | repo | -|-------------|---------------|----------------------------| -| Golang | devstream-io | dtm-scaffolding-golang | -| Golang | devstream-io | dtm-scaffolding-golang-cli | -| Java Spring | spring-guides | gs-spring-boot | -| Python | devstream-io | dtm-repo-scaffolding-python-flask | - -### Golang - -```yaml -tools: - - name: repo-scaffolding - instanceID: golang-scaffolding - options: - destinationRepo: - owner: test_owner - org: "" - name: dtm-test-golang - branch: main - scmType: github - sourceRepo: - org: devstream-io - name: dtm-scaffolding-golang - scmType: github - vars: - ImageRepo: dtm-test/golang-repo -``` - -这个配置在 GitHub 为用于 test_owner 创建 `dtm-test-golang` 仓库,它的生成是基于 `devstream-io/dtm-scaffolding-golang` 官方 Golang 脚手架仓库和传入的变量 `ImageRepo`。 - -### Golang CLI - -```yaml -tools: - - name: repo-scaffolding - instanceID: golang-cli-scaffolding - options: - destinationRepo: - owner: test_owner - org: "" - name: dtm-test-golang-cli - branch: main - scmType: github - sourceRepo: - org: devstream-io - name: dtm-scaffolding-golang-cli - scmType: github -``` - -这个配置在 GitHub 为用于 test_owner 创建 `dtm-test-golang-cli` 仓库,它的生成是基于 `devstream-io/dtm-scaffolding-golang-cli` 官方 Golang CLI 脚手架仓库。 - -### Java Spring - -```yaml -tools: - - name: repo-scaffolding - instanceID: java-scaffolding - options: - destinationRepo: - owner: test_owner - org: "" - name: dtm-test-java - branch: main - baseUrl: 127.0.0.1:30001 - visibility: public - scmType: gitlab - sourceRepo: - org: spring-guides - name: gs-spring-boot - scmType: github -``` - -这个配置会在 GitLab 为用户 test_owner 创建 `dtm-test-java` 仓库,使用的是 Spring 官方的 `spring-guides/gs-spring-boot` 仓库。 diff --git a/docs/plugins/scm-option.md b/docs/plugins/scm-option.md deleted file mode 100644 index 190b08eaa..000000000 --- a/docs/plugins/scm-option.md +++ /dev/null @@ -1,67 +0,0 @@ -# SCM Config Option - -SCM Config Option is used to represent codebase-related config option. - -## Config Options - -| Option | Description | Note | -| --------------------- | ----------------- | ------------------------------------------ | -| owner | the owner of repo | This option can't be configured at the same time with `org` | -| org | the organization of repo | This option can't be configured at the same time with `owner`| -| scmType | the repo type | Support `Gitlab`/`Github` for now | -| name | the repo name | | -| baseURL | the gitlab url address | If you use `Gitlab` for SCM, you should set this field | -| url | the repo url address | If you configure this option, then `org`, `owner`, `scmType`, `name` field can be empty | -| token | the repo api token | | -| branch | the repo branch | If this option is empty, For `Github`, branch will be `main`, for `Gitlab`, branch will be `master` | - -**Notes:** - -_You need to get the token of the repo first._ - -- For `Github` Repo, you can refer to this [doc](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) about how to get `Github` token. -- For gitlab.com (instead of a self-hosted GitLab), [click here](https://gitlab.com/-/profile/personal_access_tokens?name=DevStream+Access+token&scopes=api) to create a token for DevStream (the scope contains API only). -- For self-hosted GitLab, refer to the [official doc here](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html) for more info. - -## Example - -### Github SCM Config With URL - -```yaml -scm: - url: https://github.com/devstream-io/dtm-repo-scaffolding-python-flask.git - branch: main - token: TEST_TOKEN -``` - -### Github SCM Config Without URL - -```yaml -scm: - name: dtm-repo-scaffolding-python-flask - scmType: github - org: devstream-io - branch: main - token: TEST_TOKEN -``` - -### Gitlab SCM Config With URL - -```yaml -scm: - url: https://test.gitlab.com/testUser/dtm-repo-scaffolding-python-flask.git - branch: master - token: TEST_TOKEN -``` - -### Gitlab SCM Config With URL - -```yaml -scm: - name: dtm-repo-scaffolding-python-flask - owner: testUser - baseURL: https://test.gitlab.com - branch: master - token: TEST_TOKEN - scmType: gitlab -``` \ No newline at end of file diff --git a/docs/plugins/scm-option.zh.md b/docs/plugins/scm-option.zh.md deleted file mode 100644 index d46a95d48..000000000 --- a/docs/plugins/scm-option.zh.md +++ /dev/null @@ -1,67 +0,0 @@ -# SCM 配置项 - -SCM 配置项用于表示代码仓库相关的配置信息。 - -## 配置项 - -| 字段 | 描述 | 备注 | -| --------------------- | ----------------- | ------------------------------------------ | -| owner | 仓库拥有者 | 这个字段不能和 `org` 同时配置 | -| org | 仓库所属的组织 | 这个字段不能和 `owner` 同时配置 | -| scmType | 仓库类型 | 目前支持 `Gitlab`/`Github` | -| name | 仓库名 | | -| baseURL | 仓库的根 url 地址 | 如果你使用的是 `Gitlab` 仓库,你就需要配置这个字段 | -| url | 仓库的 url 地址 | 如果你配置了这个字段, 那么 `org`, `owner`, `scmType`, `name` 就不需要配置 | -| token | 仓库的认证 token | | -| branch | 仓库的分支 | 如果该字段为空,对于 `Github` 仓库,会使用 main 分支,对于 `Gitlab` 仓库,会使用 master 分支 | - -**Notes:** - -_你需要先获取仓库对应的 token。_ - -- 对于 `Github` 仓库,你可以查阅该[文档](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) 来获取 token。 -- 对于 `Gitlab` 官方仓库(非自建), 可以查看该[文档](https://gitlab.com/-/profile/personal_access_tokens?name=DevStream+Access+token&scopes=api)来创建 token (配置的 scope 只需要包含 API)。 -- 对于自建的 `Gitlab` 仓库,可以查看该[文档](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html) for more info。 - -## 示例 - -### 使用 url 配置 Github 仓库 - -```yaml -scm: - url: https://github.com/devstream-io/dtm-repo-scaffolding-python-flask.git - branch: main - token: TEST_TOKEN -``` - -### 不使用 url 配置 Github 仓库 - -```yaml -scm: - name: dtm-repo-scaffolding-python-flask - scmType: github - org: devstream-io - branch: main - token: TEST_TOKEN -``` - -### 使用 url 配置 Gitlab 仓库 - -```yaml -scm: - url: https://test.gitlab.com/testUser/dtm-repo-scaffolding-python-flask.git - branch: master - token: TEST_TOKEN -``` - -### 不使用 url 配置 Gitlab 仓库 - -```yaml -scm: - name: dtm-repo-scaffolding-python-flask - owner: testUser - baseURL: https://test.gitlab.com - branch: master - token: TEST_TOKEN - scmType: gitlab -``` \ No newline at end of file diff --git a/docs/plugins/trello.md b/docs/plugins/trello.md deleted file mode 100644 index 5774fe4e1..000000000 --- a/docs/plugins/trello.md +++ /dev/null @@ -1,28 +0,0 @@ -# trello Plugin - -This plugin integrates Trello with your GitHub repo. - -## Usage - -The following content is an example of the "tool file". - -For more information on the main config, the tool file, and the var file of DevStream, see [Core Concepts Overview](../core-concepts/overview.md) and [DevStream Configuration](../core-concepts/config.md). - -```yaml ---8<-- "trello.yaml" -``` - -**Notes:** - -- Trello board description is managed by DevStream, please don't modify it. -- To config a `board.token`, see [here](https://trello.com/app-key). -- `scm` config option represents codebase location; for more info, you can refer to [SCM Config](./scm-option.md). - -## Outputs - -This plugin has four outputs: - -- `boardId` -- `todoListId` -- `doingListId` -- `doneListId` diff --git a/docs/plugins/trello.zh.md b/docs/plugins/trello.zh.md deleted file mode 100644 index 5a618d44e..000000000 --- a/docs/plugins/trello.zh.md +++ /dev/null @@ -1,24 +0,0 @@ -# trello 插件 - -该插件用于将 Github 中的 Issue 实时同步到 trello 中。 - -# 用例 - -```yaml ---8<-- "trello.yaml" -``` - -**Notes:** - -- Devstream 会帮你管理 Trello 看板的描述,请不要修改它。 -- 该插件需要配置 `board.token`, 如何获取可以查看该 [文档](https://trello.com/app-key)。 -- `scm` 配置字段用于表示代码仓库的配置信息,具体配置可查看[SCM配置项](./scm-option.zh.md)。 - -## Outputs - -该插件产生以下输出: - -- `boardId` -- `todoListId` -- `doingListId` -- `doneListId` \ No newline at end of file diff --git a/docs/plugins/zentao.md b/docs/plugins/zentao.md deleted file mode 100644 index d5da97f51..000000000 --- a/docs/plugins/zentao.md +++ /dev/null @@ -1,115 +0,0 @@ -# zentao Plugin - -This plugin installs [ZenTao](https://zentao.net/) in an existing Kubernetes cluster by go client. - -**Notes:** - -- ZenTao will be installed in K8s cluster, please prepare a K8s cluster before using ZenTao plugin. -For the local build, you can use `hack/e2e/e2e-up.sh` to create a K8s cluster via `Kind`. -- Currently, all fields listed in the example config file below are required. You can modify them according to your needs. -- This plugin is not supported to run on `arm64` architecture now. - -## Usage - -The following content is an example of the "tool file". - -For more information on the main config, the tool file, and the var file of DevStream, see [Core Concepts Overview](../core-concepts/overview.md) and [DevStream Configuration](../core-concepts/config.md). - -```yaml -config: - state: - backend: local - options: - stateFile: devstream.state - -tools: - # name of the tool - - name: zentao - # id of the tool instance - instanceID: default - # format: name.instanceID; If specified, dtm will make sure the dependency is applied first before handling this tool - dependsOn: [] - # options for the plugin - options: - # namespace for ZenTao application - namespace: 'zentao' - # storageClassName used to match pv and pvc - storageClassName: 'zentao-storage' - # two PersistentVolumes for ZenTao and mysql should be specified - persistentVolumes: - # name of ZenTao pv - - pvName: "zentao-pv" - # capacity of ZenTao pv - pvCapacity: "1G" - # name of mysql pv - - pvName: "mysql-pv" - # capacity of mysql pv - pvCapacity: "1G" - # two PersistentVolumeClaims for ZenTao and mysql should be specified - persistentVolumeClaims: - # name of ZenTao pvc - - pvcName: "zentao-pvc" - # capacity of ZenTao pvc - pvcCapacity: "1G" - # name of mysql pvc - - pvcName: "mysql-pvc" - # capacity of mysql pvc - pvcCapacity: "1G" - # ZenTao application is deployed by K8s Deployment - deployment: - # name of ZenTao deployment - name: 'zentao-dp' - # number of application replica - replicas: 1 - # ZenTao image - image: 'easysoft/zentao:latest' - envs: - - key: 'MYSQL_ROOT_PASSWORD' - # initial password value for mysql database, you can specify any value you like - value: '123456' - # ZenTao application is exposed via K8s Service - service: - # name of ZenTao service - name: 'zentao-svc' - # nodePort of ZenTao service, currently ZenTao plugin only support `nodePort` type - nodePort: 30081 -``` - -## Deployment - -### Step1: Prepare a Kubernetes Cluster - -- If you already have a Kubernetes cluster, ignore this step. -- If not, you can use `hack/e2e/e2e-up.sh` to create a K8s cluster via `Kind` as test environment. - -```shell -bash hack/e2e/e2e-up.sh -``` - -### Step2: Create ZenTao Application via Config File - -- Create a ZenTao config file following the usage example above. - -```shell -./dtm apply -f ZenTao.yaml --debug -``` - -### Step3: Initialize ZenTao Application - -1. Visit `http://NodeIP:NodePort`("NodeIP" and "NodePort" are Kubernets node IP and node port) to start the initialization process. Press `Start Installation` button to the next step. -![](zentao/zentao-welcome.jpg) - -2. You don't need to do anything about the system check and it's done automatically. If there are system check items that do not pass, please make sure that the previous operation is correct. If it still doesn't work, create an issue to track your problem. -![](zentao/zentao-systemCheck.jpg) - -3. Fill in database password filed with `options.deployment.mysqlPasswdValue` which was set previously in `ZenTao.yaml`. -![](zentao/zentao-configuration.jpg) - -4. If everything proceeds successfully, you will see the Zendo introduction. -![](zentao/zentao-intro.jpg) - -5. Fill in your company name and create an administrator account. -![](zentao/zentao-account.jpg) - -6. Now, the Zendo application has been successfully deployed. -![](zentao/zentao-web.jpg) diff --git a/docs/plugins/zentao.zh.md b/docs/plugins/zentao.zh.md deleted file mode 100644 index e2791d277..000000000 --- a/docs/plugins/zentao.zh.md +++ /dev/null @@ -1,114 +0,0 @@ -# zentao(禅道)插件 - -该插件将通过`client-go`库在 Kubernetes 集群中安装[禅道应用](https://zentao.net/). - -**注意:** - -- 使用该插件之前,用户需要准备好 Kubernetes 集群,并正确设置 KUBECONFIG 配置。本地测试时,用户可以通过`hack/e2e/e2e-up.sh`创建临时 Kubernetes 集群。 -- 用户可以根据自己的需求修改以下配置文件中的字段,所有列出的字段都需要被设置。 -- 该插件暂时不支持运行在`arm64` CPU 架构上;在`amd64` CPU 架构上运行正常。 - -## 用法示例 - -下面的配置文件展示的是"tool file"的内容。 - -关于更多关于DevStream的主配置、tool file、var file的信息,请阅读[核心概念概览](../core-concepts/overview.zh.md)和[DevStream配置](../core-concepts/config.zh.md). - -```yaml -config: - state: - backend: local - options: - stateFile: devstream.state - -tools: - # name of the tool - - name: zentao - # id of the tool instance - instanceID: default - # format: name.instanceID; If specified, dtm will make sure the dependency is applied first before handling this tool - dependsOn: [] - # options for the plugin - options: - # namespace for ZenTao application - namespace: 'zentao' - # storageClassName used to match pv and pvc - storageClassName: 'zentao-storage' - # two PersistentVolumes for ZenTao and mysql should be specified - persistentVolumes: - # name of ZenTao pv - - pvName: "zentao-pv" - # capacity of ZenTao pv - pvCapacity: "1G" - # name of mysql pv - - pvName: "mysql-pv" - # capacity of mysql pv - pvCapacity: "1G" - # two PersistentVolumeClaims for ZenTao and mysql should be specified - persistentVolumeClaims: - # name of ZenTao pvc - - pvcName: "zentao-pvc" - # capacity of ZenTao pvc - pvcCapacity: "1G" - # name of mysql pvc - - pvcName: "mysql-pvc" - # capacity of mysql pvc - pvcCapacity: "1G" - # ZenTao application is deployed by K8s Deployment - deployment: - # name of ZenTao deployment - name: 'zentao-dp' - # number of application replica - replicas: 1 - # ZenTao image - image: 'easysoft/zentao:latest' - envs: - - key: 'MYSQL_ROOT_PASSWORD' - # initial password value for mysql database, you can specify any value you like - value: '123456' - # ZenTao application is exposed via K8s Service - service: - # name of ZenTao service - name: 'zentao-svc' - # nodePort of ZenTao service, currently ZenTao plugin only support `nodePort` type - nodePort: 30081 -``` - -## 部署 - -### 第一步:准备一个 Kubernetes 集群 - -- 如果已经部署 Kubernetes 集群环境,可以忽略第一步。 -- 如果尚未部署 Kubernetes 集群环境,可以使用脚本`hack/e2e/e2e-up.sh`来创建一个 Kubernetes 测试环境,该脚本会基于`Kind`创建集群。 - -```shell -bash hack/e2e/e2e-up.sh -``` - -### 第二步:利用配置文件创建禅道应用 - -- 根据上面提供的示例用法创建一个`zentao.yaml`. - -```shell -./dtm apply -f zentao.yaml --debug -``` - -### 第三步:初始化禅道应用 - -1. 浏览器访问`http://NodeIP:NodePort`("NodeIP"与"NodePort"对应 Kubernets 节点IP和节点端口)来初始化禅道服务。点击`开始安装`按钮进入下一步。 -![](zentao/zentao-welcome.jpg) - -2. 针对系统检查步骤,用户不需要做任何操作,系统检查会自动完成。如果存在未通过的检查项,请确保之前的部署流程均按照上述文档指示进行。如果仍不能解决问题,请在社区新建 issue 来跟踪相关问题。 -![](zentao/zentao-systemCheck.jpg) - -3. 数据库部署步骤只需在`数据库密码`栏填入之前设置的`options.deployment.mysqlPasswdValue`值。 -![](zentao/zentao-configuration.jpg) - -4. 如果上述所有操作均成功完成,这时将展示禅道应用介绍界面。 -![](zentao/zentao-intro.jpg) - -5. 填写用户的公司信息并创建管理员账户,然后点击`保存`。 -![](zentao/zentao-account.jpg) - -6. 至此,禅道服务就部署成功啦! -![](zentao/zentao-web.jpg) diff --git a/docs/plugins/zentao/zentao-account.jpg b/docs/plugins/zentao/zentao-account.jpg deleted file mode 100644 index 6d654a10b..000000000 Binary files a/docs/plugins/zentao/zentao-account.jpg and /dev/null differ diff --git a/docs/plugins/zentao/zentao-configuration.jpg b/docs/plugins/zentao/zentao-configuration.jpg deleted file mode 100644 index dbadd7952..000000000 Binary files a/docs/plugins/zentao/zentao-configuration.jpg and /dev/null differ diff --git a/docs/plugins/zentao/zentao-intro.jpg b/docs/plugins/zentao/zentao-intro.jpg deleted file mode 100644 index 900152460..000000000 Binary files a/docs/plugins/zentao/zentao-intro.jpg and /dev/null differ diff --git a/docs/plugins/zentao/zentao-systemCheck.jpg b/docs/plugins/zentao/zentao-systemCheck.jpg deleted file mode 100644 index 86e362c96..000000000 Binary files a/docs/plugins/zentao/zentao-systemCheck.jpg and /dev/null differ diff --git a/docs/plugins/zentao/zentao-web.jpg b/docs/plugins/zentao/zentao-web.jpg deleted file mode 100644 index 3ae5d0318..000000000 Binary files a/docs/plugins/zentao/zentao-web.jpg and /dev/null differ diff --git a/docs/plugins/zentao/zentao-welcome.jpg b/docs/plugins/zentao/zentao-welcome.jpg deleted file mode 100644 index 95714a79c..000000000 Binary files a/docs/plugins/zentao/zentao-welcome.jpg and /dev/null differ diff --git a/docs/pmc.md b/docs/pmc.md deleted file mode 100644 index 4da8808e0..000000000 --- a/docs/pmc.md +++ /dev/null @@ -1,46 +0,0 @@ -# DevStream PMC Guide - -## The DevStream PMC - -The DevStream project management committee (PMC) is a committee with responsibility and governance for DevStream's top-level projects. - -While committers on the project can update the code, only the PMC has the authority to vote on formal releases of the project's software. - -The PMC is also responsible for voting in new committers and PMC members to the project. - -## PMC Chair - -Every PMC has a Chairperson, and so does DevStream PMC. The Chair is the interface of the project. - -The term period of the PMC Chair is one year (maximum). The same person can take the Chair position multiple terms in a row. PMC members can ask for a vote before the end of the term period, and the Chair can quit the position voluntarily. - -If the PMC wishes to change the Chair, a vote should be held, or a consensus should be reached within the PMC about who the new Chair should be. - -If the Chair candidate has more than half of the votes, they become the new Chair. - -## PMC Member - -A PMC member is a committer who was elected due to merit for the evolution of the project. - -They have write access to the code repository, the right to vote on community-related decisions, and the right to propose other active contributors as committers. - -The PMC is the entity that controls the project, nobody else. In particular, the PMC must vote to approve any formal release of their project's software products. - -## Standards - -All PMC members, including the Chair, should participate actively in discussions related to the DevStream project and make non-code contributions such as PR/marketing for the project, community building, and documentation/blog. - -Optional conditions are as follows: - -- give at least one public DevStream-related talk/speech at conferences, or publish more than 5 DevStream-related blogs; -- no less than 100 commits or no less than 10,000 lines of code to the DevStream main project; -- familiar with the market positioning, short/long term roadmap, and core module logic; -- lead the implementation of DevStream in your own team/company. - -## Add a PMC Member - -Adding a PMC member requires sending a notice to the PMC of the vote to add someone. The PMC Chair or any other PMC member can send this notification. - -Since the PMC isn't a big organization, the nominated candidate must be voted in favor by all current PMC members. - -To formally add the candidate to the PMC, the PMC Chair needs to invite the new PMC member formally. diff --git a/docs/quickstart.md b/docs/quickstart.md deleted file mode 100644 index 1267c5279..000000000 --- a/docs/quickstart.md +++ /dev/null @@ -1,150 +0,0 @@ -# Quick Start - -In this quickstart, you will do the following automatically with DevStream: - -- create a GitHub repository with automatically generated code for a web application written in Golang with the [Gin](https://github.com/gin-gonic/gin) framework; -- set up GitHub Actions workflow for the app created in the previous step. - ---- - -## 1 Download - -In your working directory, run: - -```shell -sh -c "$(curl -fsSL https://download.devstream.io/download.sh)" -``` - - - -!!! note "Note" - The command above does the following: - - - find out your OS and chip architecture; - - find the latest version of the `dtm` binary; - - download the correct `dtm` according to OS/architecture; - - grant the binary execution permission. - -!!! quote "Optional" - You can then move `dtm` to a place which is in your PATH. For example: `mv dtm /usr/local/bin/`. - - For more ways to install `dtm`, see [install dtm](./install.md). - ---- - -## 2 Configuration - -Run the following command to generate the template configuration file `config.yaml` for quickstart. - -```shell -./dtm show config -t quickstart > config.yaml -``` - -Then set the following environment variables by running (replace values within the double quotes): - -```shell -export GITHUB_USER="" -export DOCKERHUB_USERNAME="" -export IMAGE_REPO_PASSWORD="" -export GITHUB_TOKEN="" -``` - -!!! tip "Tip" - Go to [Personal Access Token](https://github.com/settings/tokens/new) to generate a new `GITHUB_TOKEN` for `dtm`. - - For "Quick Start", you only need `repo`,`workflow`,`delete_repo` permissions. - -Then you should run the following commands to update our config file with those env vars: - -=== "**macOS** or **FreeBSD** based systems" - - ```shell - sed -i.bak "s@YOUR_GITHUB_USERNAME_CASE_SENSITIVE@${GITHUB_USER}@g" config.yaml - sed -i.bak "s@YOUR_DOCKER_USERNAME@${DOCKERHUB_USERNAME}@g" config.yaml - ``` - -=== "**GNU** Linux users" - - ```shell - sed -i "s@YOUR_GITHUB_USERNAME_CASE_SENSITIVE@${GITHUB_USER}@g" config.yaml - sed -i "s@YOUR_DOCKER_USERNAME@${DOCKERHUB_USERNAME}@g" config.yaml - ``` - - - ---- - -## 3 Init - -Run: - -```shell -./dtm init -f config.yaml -``` - - - ---- - -## 4 Apply - -Run: - -```shell -./dtm apply -f config.yaml -y -``` - - - ---- - -## 5 Check the Results - -Go to your GitHub repositories list and you can see the new repo `go-webapp-devstream-demo` has been created. - -There is scaffolding code for a Golang web app in it, with GitHub Actions CI workflow set up properly. - -The commits (made by DevStream when scaffolding the repo and creating workflows) have triggered the CI, and the workflow has finished successfully, as shown in the screenshot below: - -![](./images/quickstart.png) - ---- - -## 6 Clean Up - -Run: - -```shell -./dtm delete -f config.yaml -``` - -Input `y` then press enter to continue, and you should see similar output: - -!!! success "Output" - - ```text title="" - 2022-12-12 12:29:00 ℹ [INFO] Delete started. - 2022-12-12 12:29:00 ℹ [INFO] Using local backend. State file: devstream.state. - 2022-12-12 12:29:00 ℹ [INFO] Tool (github-actions/default) will be deleted. - 2022-12-12 12:29:00 ℹ [INFO] Tool (repo-scaffolding/golang-github) will be deleted. - Continue? [y/n] - Enter a value (Default is n): y - 2022-12-12 12:29:00 ℹ [INFO] Start executing the plan. - 2022-12-12 12:29:00 ℹ [INFO] Changes count: 2. - 2022-12-12 12:29:00 ℹ [INFO] -------------------- [ Processing progress: 1/2. ] -------------------- - 2022-12-12 12:29:00 ℹ [INFO] Processing: (github-actions/default) -> Delete ... - 2022-12-12 12:29:02 ℹ [INFO] Prepare to delete 'github-actions_default' from States. - 2022-12-12 12:29:02 ✔ [SUCCESS] Tool (github-actions/default) delete done. - 2022-12-12 12:29:02 ℹ [INFO] -------------------- [ Processing progress: 2/2. ] -------------------- - 2022-12-12 12:29:02 ℹ [INFO] Processing: (repo-scaffolding/golang-github) -> Delete ... - 2022-12-12 12:29:03 ✔ [SUCCESS] GitHub repo go-webapp-devstream-demo removed. - 2022-12-12 12:29:03 ℹ [INFO] Prepare to delete 'repo-scaffolding_golang-github' from States. - 2022-12-12 12:29:03 ✔ [SUCCESS] Tool (repo-scaffolding/golang-github) delete done. - 2022-12-12 12:29:03 ℹ [INFO] -------------------- [ Processing done. ] -------------------- - 2022-12-12 12:29:03 ✔ [SUCCESS] All plugins deleted successfully. - 2022-12-12 12:29:03 ✔ [SUCCESS] Delete finished. - ``` - -Now if you check your GitHub repo list again, everything has been nuked by DevStream. Hooray! - -You can also remove the DevStream state file (which should be empty now) by running: `rm devstream.state`. diff --git a/docs/quickstart.zh.md b/docs/quickstart.zh.md deleted file mode 100644 index 85b8837af..000000000 --- a/docs/quickstart.zh.md +++ /dev/null @@ -1,187 +0,0 @@ -# 快速开始 - -在这个“快速开始”流程中,你将使用 DevStream 自动化地完成以下工作: - -- 在 GitHub 上创建一个包含了自动生成的 Golang [Gin](https://github.com/gin-gonic/gin) 框架代码的 web 应用代码库; -- 为这个代码库配置好 GitHub Actions 工作流。 - ---- - -## 1 下载 - -进入你的工作目录,运行: - -```shell -sh -c "$(curl -fsSL https://download.devstream.io/download.sh)" -``` - -!!! note "提示" - 上面的命令会做以下事情: - - - 检测你的操作系统和芯片架构; - - 找到最新版本的 `dtm` 二进制文件; - - 根据操作系统和架构下载正确的 `dtm` 二进制文件; - - 授予二进制文件执行权限。 - -!!! quote "可选" - 你可以将 `dtm` 移到 PATH 中。例如:`mv dtm /usr/local/bin/`。 - - 更多安装方式详见[安装dtm](./install.zh.md)。 - ---- - -## 2 配置 - -运行以下命令来生成 quickstart 的模板配置文件 `config.yaml` 。 - -```shell -./dtm show config -t quickstart > config.yaml -``` - -运行以下命令以设置这些环境变量(记得替换双引号内的值): - -```shell -export GITHUB_USER="" -export DOCKERHUB_USERNAME="" -export IMAGE_REPO_PASSWORD="" -export GITHUB_TOKEN="" -``` - -!!! tip "提示" - 参考 [Personal Access Token](https://github.com/settings/tokens/new) 为 `dtm` 生成新的 `GITHUB_TOKEN`。 - - 对于“快速开始”,你只需要勾选 `repo`、`workflow`、`delete_repo` 权限。 - -接着,你可以运行以下命令,以使用环境变量来修改配置文件: - -=== "**macOS** 或基于 **FreeBSD** 的操作系统" - - ```shell title="" - sed -i.bak "s@YOUR_GITHUB_USERNAME_CASE_SENSITIVE@${GITHUB_USER}@g" config.yaml - sed -i.bak "s@YOUR_DOCKER_USERNAME@${DOCKERHUB_USERNAME}@g" config.yaml - ``` - -=== "**GNU** Linux 用户" - - ```shell title="" - sed -i "s@YOUR_GITHUB_USERNAME_CASE_SENSITIVE@${GITHUB_USER}@g" config.yaml - sed -i "s@YOUR_DOCKER_USERNAME@${DOCKERHUB_USERNAME}@g" config.yaml - ``` - ---- - -## 3 初始化(Init) - -运行: - -```shell -./dtm init -f config.yaml -``` - -!!! success "你会看到类似下面的输出" - ``` title="" - 2022-12-12 11:40:34 ℹ [INFO] Using dir to store plugins. - 2022-12-12 11:40:34 ℹ [INFO] -------------------- [ repo-scaffolding-darwin-arm64_0.10.2 ] -------------------- - 2022-12-12 11:40:35 ℹ [INFO] Downloading: [repo-scaffolding-darwin-arm64_0.10.2.so] ... - 87.75 MiB / 87.75 MiB [================================] 100.00% 7.16 MiB/s 12s - 2022-12-12 11:40:47 ✔ [SUCCESS] [repo-scaffolding-darwin-arm64_0.10.2.so] download succeeded. - 2022-12-12 11:40:48 ℹ [INFO] Downloading: [repo-scaffolding-darwin-arm64_0.10.2.md5] ... - 33 B / 33 B [=========================================] 100.00% 115.84 KiB/s 0s - 2022-12-12 11:40:48 ✔ [SUCCESS] [repo-scaffolding-darwin-arm64_0.10.2.md5] download succeeded. - 2022-12-12 11:40:48 ℹ [INFO] Initialize [repo-scaffolding-darwin-arm64_0.10.2] finished. - 2022-12-12 11:40:48 ℹ [INFO] -------------------- [ repo-scaffolding-darwin-arm64_0.10.2 ] -------------------- - - 2022-12-12 11:40:48 ℹ [INFO] -------------------- [ github-actions-darwin-arm64_0.10.2 ] -------------------- - 2022-12-12 11:40:48 ℹ [INFO] Downloading: [github-actions-darwin-arm64_0.10.2.so] ... - 90.27 MiB / 90.27 MiB [================================] 100.00% 10.88 MiB/s 8s - 2022-12-12 11:40:57 ✔ [SUCCESS] [github-actions-darwin-arm64_0.10.2.so] download succeeded. - 2022-12-12 11:40:57 ℹ [INFO] Downloading: [github-actions-darwin-arm64_0.10.2.md5] ... - 33 B / 33 B [=========================================] 100.00% 145.46 KiB/s 0s - 2022-12-12 11:40:57 ✔ [SUCCESS] [github-actions-darwin-arm64_0.10.2.md5] download succeeded. - 2022-12-12 11:40:57 ℹ [INFO] Initialize [github-actions-darwin-arm64_0.10.2] finished. - 2022-12-12 11:40:57 ℹ [INFO] -------------------- [ github-actions-darwin-arm64_0.10.2 ] -------------------- - 2022-12-12 11:40:57 ✔ [SUCCESS] Initialize finished. - ``` - ---- - -## 4 应用(Apply) - -运行: - -```shell -./dtm apply -f config.yaml -y -``` - -!!! success "你会看到类似下面的输出" - - ```text title="" - 2022-12-12 11:44:39 ℹ [INFO] Apply started. - 2022-12-12 11:44:39 ℹ [INFO] Using local backend. State file: devstream.state. - 2022-12-12 11:44:39 ℹ [INFO] Tool (repo-scaffolding/golang-github) found in config but doesn't exist in the state, will be created. - 2022-12-12 11:44:39 ℹ [INFO] Tool (github-actions/default) found in config but doesn't exist in the state, will be created. - 2022-12-12 11:44:39 ℹ [INFO] Start executing the plan. - 2022-12-12 11:44:39 ℹ [INFO] Changes count: 2. - 2022-12-12 11:44:39 ℹ [INFO] -------------------- [ Processing progress: 1/2. ] -------------------- - 2022-12-12 11:44:39 ℹ [INFO] Processing: (repo-scaffolding/golang-github) -> Create ... - 2022-12-12 11:44:39 ℹ [INFO] github start to download repoTemplate...2022-12-12 11:44:42 ✔ [SUCCESS] The repo go-webapp-devstream-demo has been created. - 2022-12-12 11:44:49 ✔ [SUCCESS] Tool (repo-scaffolding/golang-github) Create done. - 2022-12-12 11:44:49 ℹ [INFO] -------------------- [ Processing progress: 2/2. ] -------------------- - 2022-12-12 11:44:49 ℹ [INFO] Processing: (github-actions/default) -> Create ... - 2022-12-12 11:44:57 ✔ [SUCCESS] Tool (github-actions/default) Create done. - 2022-12-12 11:44:57 ℹ [INFO] -------------------- [ Processing done. ] -------------------- - 2022-12-12 11:44:57 ✔ [SUCCESS] All plugins applied successfully. - 2022-12-12 11:44:57 ✔ [SUCCESS] Apply finished. - ``` - ---- - -## 5 检查结果 - -前往你的 GitHub 仓库列表,可以看到一个新的仓库 `go-webapp-devstream-demo` 已经被创建了。 - -包含了 Golang web 应用程序的脚手架代码,并正确设置了 GitHub Actions CI 工作流。 - -DevStream 在生成仓库脚手架和创建工作流时的代码提交,已经触发了 CI,且工作流已经成功地运行完毕,如下图所示: - -![](./images/quickstart.png) - ---- - -## 6 清理 - -运行: - -```shell -./dtm delete -f config.yaml -``` - -输入 `y` 然后回车,你会看到类似下面的输出: - -!!! success "输出" - ```title="" - 2022-12-12 12:29:00 ℹ [INFO] Delete started. - 2022-12-12 12:29:00 ℹ [INFO] Using local backend. State file: devstream.state. - 2022-12-12 12:29:00 ℹ [INFO] Tool (github-actions/default) will be deleted. - 2022-12-12 12:29:00 ℹ [INFO] Tool (repo-scaffolding/golang-github) will be deleted. - Continue? [y/n] - Enter a value (Default is n): y - 2022-12-12 12:29:00 ℹ [INFO] Start executing the plan. - 2022-12-12 12:29:00 ℹ [INFO] Changes count: 2. - 2022-12-12 12:29:00 ℹ [INFO] -------------------- [ Processing progress: 1/2. ] -------------------- - 2022-12-12 12:29:00 ℹ [INFO] Processing: (github-actions/default) -> Delete ... - 2022-12-12 12:29:02 ℹ [INFO] Prepare to delete 'github-actions_default' from States. - 2022-12-12 12:29:02 ✔ [SUCCESS] Tool (github-actions/default) delete done. - 2022-12-12 12:29:02 ℹ [INFO] -------------------- [ Processing progress: 2/2. ] -------------------- - 2022-12-12 12:29:02 ℹ [INFO] Processing: (repo-scaffolding/golang-github) -> Delete ... - 2022-12-12 12:29:03 ✔ [SUCCESS] GitHub repo go-webapp-devstream-demo removed. - 2022-12-12 12:29:03 ℹ [INFO] Prepare to delete 'repo-scaffolding_golang-github' from States. - 2022-12-12 12:29:03 ✔ [SUCCESS] Tool (repo-scaffolding/golang-github) delete done. - 2022-12-12 12:29:03 ℹ [INFO] -------------------- [ Processing done. ] -------------------- - 2022-12-12 12:29:03 ✔ [SUCCESS] All plugins deleted successfully. - 2022-12-12 12:29:03 ✔ [SUCCESS] Delete finished. - ``` - -现在,如果你看看 GitHub 仓库列表,所有东西都被 DevStream 消灭了。妙哉! - -你也可以通过运行 `rm devstream.state` 来删除 DevStream 状态文件(现在应该是个空文件)。 diff --git a/docs/requirements.txt b/docs/requirements.txt deleted file mode 100644 index 781b28b45..000000000 --- a/docs/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -mkdocs -mkdocs-material -mkdocs-i18n -mkdocs-literate-nav diff --git a/docs/understanding_the_basics.md b/docs/understanding_the_basics.md deleted file mode 100644 index 268dfa795..000000000 --- a/docs/understanding_the_basics.md +++ /dev/null @@ -1,39 +0,0 @@ -# Understanding the Basics - -Before effectively using DevStream, it is necessary to understand the underlying technology that it is built on. - -It is also necessary to understand the features and commands provided to you, and how to use them. - -The section below provides some useful links to build up this understanding. - -## Learn the Fundamentals about DevOps - -- Go through a few online resources on DevOps: - - [Microsoft: Introduce the foundation pillars of DevOps: Culture and Lean Product](https://docs.microsoft.com/en-us/learn/modules/introduce-foundation-pillars-devops/) - - [AWS: What is DevOps?](https://aws.amazon.com/devops/what-is-devops/?nc1=h_ls) - - [Atlassian: DevOps](https://www.atlassian.com/devops) - - [Weaveworks: Guide To GitOps](https://www.weave.works/technologies/gitops/) -- Agile: - - [Atlassian: Discover the Spotify model](https://www.atlassian.com/agile/agile-at-scale/spotify) - - [AWS: Two-Pizza Teams](https://docs.aws.amazon.com/whitepapers/latest/introduction-devops-aws/two-pizza-teams.html) - - [Google TechTalks: Scrum et al.](https://www.youtube.com/watch?v=IyNPeTn8fpo) -- CloudNative: - - [Microsoft: What is Cloud Native?](https://docs.microsoft.com/en-us/dotnet/architecture/cloud-native/definition) - - [Cloud Native End User Tech Radar](https://radar.cncf.io/) - - [The Twelve Factor Apps](https://12factor.net/) - -## Learn the Basics about Docker and Kubernetes - -Go through the online Docker and Kubernetes tutorials. You can search for: - -- A Beginner-Friendly Introduction to Containers, VMs and Docker -- Introduction to Kubernetes -- Tutorials -- Hands on labs -- Kustomize -- Helm - -## Getting Started - -- [Quick Start](./quickstart.md) -- [Use Case - GitOps Toolchain](./use-cases/gitops/2-gitops-tools.md) diff --git a/docs/understanding_the_basics.zh.md b/docs/understanding_the_basics.zh.md deleted file mode 100644 index aacddcfb3..000000000 --- a/docs/understanding_the_basics.zh.md +++ /dev/null @@ -1,39 +0,0 @@ -# 基础知识 - -在我们高效使用 DevStream 之前,有必要了解它所基于的底层技术。 - -同时我们也有必要了解它提供给你的功能和命令,以及如何使用它们。 - -下面的部分提供了一些链接来帮助你了解这些基础知识。 - -## 学习关于 DevOps 的基础知识 - -- 关于 DevOps 的在线资源: - - [微软:介绍 DevOps 的基础支柱:文化与精益产品](https://docs.microsoft.com/zh-cn/learn/modules/introduce-foundation-pillars-devops/) - - [AWS:什么是 DevOps?](https://aws.amazon.com/cn/devops/what-is-devops/) - - [Atlassian: DevOps](https://www.atlassian.com/zh/devops) - - [Weaveworks:GitOps 指南](https://www.weave.works/technologies/gitops/) -- 敏捷性 (Agile): - - [Atlassian: Spotify 模型](https://www.atlassian.com/zh/agile/agile-at-scale/spotify) - - [AWS: 双比萨团队(Two-Pizza Teams)](https://docs.aws.amazon.com/zh_cn/whitepapers/latest/introduction-devops-aws/two-pizza-teams.html) - - [Google TechTalks: Scrum et al.](https://www.youtube.com/watch?v=IyNPeTn8fpo) -- 云原生 (CloudNative): - - [微软:什么是云原生?](https://docs.microsoft.com/zh-cn/dotnet/architecture/cloud-native/definition) - - [Cloud Native End User Tech Radar](https://radar.cncf.io/) - - [应用的十二要素(The Twelve Factor Apps)](https://12factor.net/zh_cn/) - -## 了解有关 Docker 和 Kubernetes 的基础知识 - -学习网上的 Docker 和 Kubernetes 教程。你可以搜索: - -- A Beginner-Friendly Introduction to Containers, VMs and Docker -- Introduction to Kubernetes -- Tutorials -- Hands on labs -- Kustomize -- Helm - -## 让我们开始吧! - -- [快速入门](./quickstart.zh.md) -- [应用场景 - GitOps 工具链](./use-cases/gitops/2-gitops-tools.zh.md) diff --git a/docs/use-cases/case-overview.md b/docs/use-cases/case-overview.md deleted file mode 100644 index 048e9c0bd..000000000 --- a/docs/use-cases/case-overview.md +++ /dev/null @@ -1 +0,0 @@ -# Case Overview diff --git a/docs/use-cases/case-overview.zh.md b/docs/use-cases/case-overview.zh.md deleted file mode 100644 index af397c446..000000000 --- a/docs/use-cases/case-overview.zh.md +++ /dev/null @@ -1,43 +0,0 @@ -# 场景概览 - -左侧的导航目录默认是以“工具集”的维度来区分的,如果你明确知道自己想要使用哪些工具来搭建自己的 DevOps 工具链,相信这个目录对你来说已经足够了。或者如果你想从开发语言,或者自己团队的状态或阶段等维度筛选适合自己的使用场景,那么你可以参考如下索引: - -=== "开发语言和框架" - - ## Python + Flask - - 1. [用 DTM 实现基于 Argo CD 的 GitOps 流程](./gitops/2-gitops-tools.zh.md) - 2. [用 DTM Apps 管理能力实现基于 Argo CD 的 GitOps 流程](./gitops/3-gitops-apps.zh.md) - 3. [用 DTM Apps 实现基于 GitHub,Argo CD 和 GitHub Actions 的 CICD 流程](./gitops-python-flask/2-github-dtm-apps.zh.md) - 4. [用 DTM Tools 实现基于 GitHub,Argo CD 和 GitHub Actions 的 CICD 流程](./gitops-python-flask/3-github-dtm-tools.zh.md) - 5. [用 DevStream 搭建 Gitlab CI + Argo CD 工具链,管理 Python Flask 项目](./gitops-python-flask/4-gitlab-dtm-apps.zh.md) - - ## Java + Spring Boot - - 1. [通过 DevStream 实现 Java Spring Boot 项目脚手架的快速创建与 Jenkins CI 流水线的自动化配置](./gitlab-jenkins-harbor/5-java-springboot-pipeline-with-gitlab-jenkins-harbor.zh.md); - 2. [用 DevStream 部署 GitLab + Jenkins + Harbor 工具链,管理 Java Spring Boot 项目开发生命周期全流程](./gitlab-jenkins-harbor/6-gitlab-jenkins-harbor-java-springboot.zh.md); - -=== "用户状态或阶段" - - ## 工具从0到1 - - 1. [从0到1在自建(On-Premise)环境通过 DevStream 部署完整的 GitLab + Jenkins + Harbor 工具链](./gitlab-jenkins-harbor/2-gitlab-jenkins-harbor.zh.md); - 2. [在离线环境通过 DevStream 部署 GitLab + Jenkins + Harbor 工具链](./gitlab-jenkins-harbor/3-gitlab-jenkins-harbor-air-gapped.zh.md); - - ## 工具新增或替换 - - 1. [用 DevStream 部署 Jenkins 并且一键打通其与你的企业内自建 GitLab](./gitlab-jenkins-harbor/4-jenkins-gitlab-integ.zh.md); - - ## 应用从0到1 - - 1. [通过 DevStream 实现 Java Spring Boot 项目脚手架的快速创建与 Jenkins CI 流水线的自动化配置](./gitlab-jenkins-harbor/5-java-springboot-pipeline-with-gitlab-jenkins-harbor.zh.md); - - ## 工具和应用从0到1 - - 1. [用 DTM 实现基于 Argo CD 的 GitOps 流程](./gitops/2-gitops-tools.zh.md) - 2. [用 DTM Apps 管理能力实现基于 Argo CD 的 GitOps 流程](./gitops/3-gitops-apps.zh.md) - 3. [用 DTM Apps 实现基于 GitHub,Argo CD 和 GitHub Actions 的 CICD 流程](./gitops-python-flask/2-github-dtm-apps.zh.md) - 4. [用 DTM Tools 实现基于 GitHub,Argo CD 和 GitHub Actions 的 CICD 流程](./gitops-python-flask/3-github-dtm-tools.zh.md) - 5. [用 DevStream 搭建 Gitlab CI + Argo CD 工具链,管理 Python Flask 项目](./gitops-python-flask/4-gitlab-dtm-apps.zh.md) - 6. [用 DevStream 部署 GitLab + Jenkins + Harbor 工具链,管理 Java Spring Boot 项目开发生命周期全流程](./gitlab-jenkins-harbor/6-gitlab-jenkins-harbor-java-springboot.zh.md); - 7. [在离线环境使用 DevStream 搭建 GitLab + Jenkins + Harbor 工具链,管理 Java Spring Boot 项目开发生命周期全流程](./gitlab-jenkins-harbor/7-java-springboot-pipeline-with-gitlab-jenkins-harbor-air-gapped.zh.md)。 diff --git a/docs/use-cases/gitlab-jenkins-harbor/1-overview.md b/docs/use-cases/gitlab-jenkins-harbor/1-overview.md deleted file mode 100644 index 3d7d443bf..000000000 --- a/docs/use-cases/gitlab-jenkins-harbor/1-overview.md +++ /dev/null @@ -1,3 +0,0 @@ -# Overview - -// TODO(daniel-hutao): Chinese version first. diff --git a/docs/use-cases/gitlab-jenkins-harbor/1-overview.zh.md b/docs/use-cases/gitlab-jenkins-harbor/1-overview.zh.md deleted file mode 100644 index 80089f78e..000000000 --- a/docs/use-cases/gitlab-jenkins-harbor/1-overview.zh.md +++ /dev/null @@ -1,10 +0,0 @@ -# 概述 - -《GitLab + Jenkins + Harbor 工具链管理》系列 DevStream 应用场景主要包括下述实践: - -1. [从0到1在自建(On-Premise)环境通过 DevStream 部署完整的 GitLab + Jenkins + Harbor 工具链](./2-gitlab-jenkins-harbor.zh.md); -2. [在离线环境通过 DevStream 部署 GitLab + Jenkins + Harbor 工具链](./3-gitlab-jenkins-harbor-air-gapped.zh.md); -3. [用 DevStream 部署 Jenkins 并且一键打通其与你的企业内自建 GitLab](./4-jenkins-gitlab-integ.zh.md); -4. [通过 DevStream 实现 Java Spring Boot 项目脚手架的快速创建与 Jenkins CI 流水线的自动化配置](./5-java-springboot-pipeline-with-gitlab-jenkins-harbor.zh.md); -5. [用 DevStream 部署 GitLab + Jenkins + Harbor 工具链,管理 Java Spring Boot 项目开发生命周期全流程](./6-gitlab-jenkins-harbor-java-springboot.zh.md); -6. [在离线环境使用 DevStream 搭建 GitLab + Jenkins + Harbor 工具链,管理 Java Spring Boot 项目开发生命周期全流程](./7-java-springboot-pipeline-with-gitlab-jenkins-harbor-air-gapped.zh.md)。 diff --git a/docs/use-cases/gitlab-jenkins-harbor/2-gitlab-jenkins-harbor.md b/docs/use-cases/gitlab-jenkins-harbor/2-gitlab-jenkins-harbor.md deleted file mode 100644 index e7c35240f..000000000 --- a/docs/use-cases/gitlab-jenkins-harbor/2-gitlab-jenkins-harbor.md +++ /dev/null @@ -1,3 +0,0 @@ -# GitLab + Jenkins + Harbor Toolchain Deployment in No Time - -// TODO(daniel-hutao): Chinese version first. diff --git a/docs/use-cases/gitlab-jenkins-harbor/2-gitlab-jenkins-harbor.zh.md b/docs/use-cases/gitlab-jenkins-harbor/2-gitlab-jenkins-harbor.zh.md deleted file mode 100644 index 6a06ec563..000000000 --- a/docs/use-cases/gitlab-jenkins-harbor/2-gitlab-jenkins-harbor.zh.md +++ /dev/null @@ -1,319 +0,0 @@ -# 快速部署 GitLab + Jenkins + Harbor 工具链 - -## 1、概述 - -本文将介绍如何通过 DevStream 在本地快速部署 `GitLab + Jenkins + Harbor` 工具链。 - -!!! hint "提示" - - 本文基于 kubeadm 部署的单节点 k8s 环境,不适用于 minikube 和 kind 等 docker-in-docker 类型的 k8s 集群。 - -## 2、安装 dtm - -你可以参考[这个文档](../../install.zh.md)完成 dtm 的下载与安装。 - -## 3、准备配置文件 - -DevStream 可以简单地以 **local** 作为[状态](../../core-concepts/state.zh.md) Backend,也就是将状态保存到本地文件。如果你在本地测试,可以选择使用这种方式; -而企业 On premise 环境部署可能需要使用 **k8s** Backend 将状态通过 `kube-apiserver` 存入 etcd,两种方式配置分别如下: - -=== "DevStream with 'local' Backend" - - ```yaml title="local Backend" - config: - state: - backend: local - options: - stateFile: devstream.state - ``` - -=== "DevStream with 'k8s' Backend" - - ```yaml title="k8s Backend" - config: - state: - backend: k8s - options: - namespace: devstream - configmap: state - ``` - -下文将以 `local` Backend 为例演示。 - -在编写 `gitlab-ce-docker` 和 `helm-installer`(用于安装 Jenkins 和 Harbor)这两个插件的配置文件之前,你需要先定义一些变量,这会让后续的配置和维护工作变得更加简单: - -```yaml title="config-tools.yaml" -config: - state: - backend: local - options: - stateFile: devstream.state -vars: - gitlabHostname: gitlab.example.com - jenkinsHostname: jenkins.example.com - harborHostname: harbor.example.com - harborURL: http://harbor.example.com - jenkinsAdminUser: admin - jenkinsAdminPassword: changeme - gitlabSSHPort: 30022 - gitlabHttpPort: 30080 - gitlabHttpsPort: 30443 -``` - -你可以根据自己的需要,选择性自定义上述 vars 配置的值,这些变量内容主要是域名、端口号等可修改配置项。 - -继续往里面追加工具链相关插件的配置,你的配置文件会扩充成这样: - -```yaml title="config-tools.yaml" -config: - state: - backend: local - options: - stateFile: devstream.state -vars: - gitlabHostname: gitlab.example.com - jenkinsHostname: jenkins.example.com - harborHostname: harbor.example.com - harborURL: http://harbor.example.com - jenkinsAdminUser: admin - jenkinsAdminPassword: changeme - gitlabSSHPort: 30022 - gitlabHttpPort: 30080 - gitlabHttpsPort: 30443 -tools: -- name: gitlab-ce-docker - instanceID: default - dependsOn: [] - options: - hostname: [[ gitlabHostname ]] - gitlabHome: /srv/gitlab - sshPort: [[ gitlabSSHPort ]] - httpPort: [[ gitlabHttpPort ]] - httpsPort: [[ gitlabHttpsPort ]] - rmDataAfterDelete: false - imageTag: "rc" -- name: helm-installer - instanceID: jenkins-001 - dependsOn: [] - options: - valuesYaml: | - serviceAccount: - create: true - name: jenkins - controller: - adminUser: [[ jenkinsAdminUser ]] - adminPassword: [[ jenkinsAdminPassword ]] - ingress: - enabled: true - hostName: [[ jenkinsHostname ]] - enableRawHtmlMarkupFormatter: true - JCasC: - defaultConfig: true -- name: helm-installer - instanceID: harbor-001 - dependsOn: [] - options: - valuesYaml: | - externalURL: [[ harborURL ]] - expose: - type: ingress - tls: - enabled: false - ingress: - hosts: - core: [[ harborHostname ]] - chartmuseum: - enabled: false - notary: - enabled: false - trivy: - enabled: false - persistence: - persistentVolumeClaim: - registry: - storageClass: "" - accessMode: ReadWriteOnce - size: 5Gi - jobservice: - storageClass: "" - accessMode: ReadWriteOnce - size: 1Gi - database: - storageClass: "" - accessMode: ReadWriteOnce - size: 1Gi - redis: - storageClass: "" - accessMode: ReadWriteOnce - size: 1Gi -``` - -## 4、初始化 - -你可以将上面这个配置文件(config-tools.yaml)放到服务器上任意一个合适的目录,比如 `~/devstream-test/`,然后在该目录下执行: - -```shell title="初始化命令" -dtm init -f config-tools.yaml -``` - -这个命令会帮助你下载所有需要的 DevStream 插件。 - -## 5、开始部署 - -接着你就可以执行 apply 命令了: - -```shell title="开始部署" -dtm apply -f config-tools.yaml -y -``` - -这个命令执行成功的话,你可以大致看到如下日志: - -```shell title="部署日志" -2022-11-30 08:14:05 ℹ [INFO] Apply started. -2022-11-30 08:14:06 ℹ [INFO] Using local backend. State file: devstream.state. -2022-11-30 08:14:06 ℹ [INFO] Tool (gitlab-ce-docker/default) found in config but doesn't exist in the state, will be created. -2022-11-30 08:14:06 ℹ [INFO] Tool (helm-installer/jenkins-001) found in config but doesn't exist in the state, will be created. -2022-11-30 08:14:06 ℹ [INFO] Tool (helm-installer/harbor-001) found in config but doesn't exist in the state, will be created. -2022-11-30 08:14:06 ℹ [INFO] Start executing the plan. -2022-11-30 08:14:06 ℹ [INFO] Changes count: 3. -2022-11-30 08:14:06 ℹ [INFO] -------------------- [ Processing progress: 1/3. ] -------------------- -2022-11-30 08:14:06 ℹ [INFO] Processing: (gitlab-ce-docker/default) -> Create ... -2022-11-30 08:14:06 ℹ [INFO] Cmd: docker image ls gitlab/gitlab-ce:rc -q. -2022-11-30 08:14:06 ℹ [INFO] Running container as the name -2022-11-30 08:14:06 ℹ [INFO] Cmd: docker run --detach --hostname gitlab.example.com --publish 30022:22 --publish 30080:80 --publish 30443:443 --name gitlab --restart always --volume /srv/gitlab/config:/etc/gitlab --volume /srv/gitlab/data:/var/opt/gitlab --volume /srv/gitlab/logs:/var/log/gitlab gitlab/gitlab-ce:rc. -Stdout: 34cdd2a834a1c21be192064eacf1e29536ff45c52562956b97d6d376a5dae11b -2022-11-30 08:14:07 ℹ [INFO] Cmd: docker inspect --format='{{json .Mounts}}' gitlab. -2022-11-30 08:14:07 ℹ [INFO] GitLab access URL: http://gitlab.example.com:30080 -2022-11-30 08:14:07 ℹ [INFO] GitLab initial root password: execute the command -> docker exec -it gitlab grep 'Password:' /etc/gitlab/initial_root_password -2022-11-30 08:14:07 ✔ [SUCCESS] Tool (gitlab-ce-docker/default) Create done. -2022-11-30 08:14:07 ℹ [INFO] -------------------- [ Processing progress: 2/3. ] -------------------- -2022-11-30 08:14:07 ℹ [INFO] Processing: (helm-installer/jenkins-001) -> Create ... -2022-11-30 08:14:07 ℹ [INFO] Filling default config with instance: jenkins-001. -2022-11-30 08:14:07 ℹ [INFO] Creating or updating helm chart ... -2022/11/30 08:14:09 creating 13 resource(s) -2022/11/30 08:14:09 beginning wait for 13 resources with timeout of 10m0s -2022/11/30 08:14:09 StatefulSet is not ready: jenkins/jenkins. 0 out of 1 expected pods are ready -... -2022/11/30 08:14:49 StatefulSet is not ready: jenkins/jenkins. 0 out of 1 expected pods are ready -2022/11/30 08:14:51 release installed successfully: jenkins/jenkins-4.2.15 -2022-11-30 08:14:51 ✔ [SUCCESS] Tool (helm-installer/jenkins-001) Create done. -2022-11-30 08:14:51 ℹ [INFO] -------------------- [ Processing progress: 3/3. ] -------------------- -2022-11-30 08:14:51 ℹ [INFO] Processing: (helm-installer/harbor-001) -> Create ... -2022-11-30 08:14:51 ℹ [INFO] Filling default config with instance: harbor-001. -2022-11-30 08:14:51 ℹ [INFO] Creating or updating helm chart ... -2022/11/30 08:14:52 checking 28 resources for changes -2022/11/30 08:14:52 Created a new Secret called "harbor-core" in harbor -... -2022/11/30 08:14:52 Created a new Ingress called "harbor-ingress" in harbor -2022/11/30 08:14:52 beginning wait for 28 resources with timeout of 10m0s -2022/11/30 08:14:52 Deployment is not ready: harbor/harbor-core. 0 out of 1 expected pods are ready -... -2022/11/30 08:15:50 Deployment is not ready: harbor/harbor-jobservice. 0 out of 1 expected pods are ready -2022/11/30 08:15:52 release installed successfully: harbor/harbor-1.10.2 -2022-11-30 08:15:52 ✔ [SUCCESS] Tool (helm-installer/harbor-001) Create done. -2022-11-30 08:15:52 ℹ [INFO] -------------------- [ Processing done. ] -------------------- -2022-11-30 08:15:52 ✔ [SUCCESS] All plugins applied successfully. -2022-11-30 08:15:52 ✔ [SUCCESS] Apply finished. -``` - -从日志里你可以看到,这时候 GitLab、Jenkins 和 Harbor 就已经部署完成了。 - -## 6、验证部署结果 - -你可以通过如下方式验证 GitLab + Jenkins + Harbor 三个工具的部署结果。 - -### 6.1、DNS 配置 - -前面你给 GitLab + Jenkins + Harbor 三个工具的配置文件里都设置了域名,然后你可以直接将这些域名与 IP 的映射关系配置到 DNS 服务器里。 - -如果没有 DNS 服务器,你也可以直接将域名与 IP 的映射关系配置到 `/etc/hosts` 以及 `CoreDNS` 的 ConfigMap `kube-system/coredns` 里让域名生效。比如我的主机 IP 是 44.33.22.11,这时候可以这样配置: - -1. 修改 `/etc/hosts` 文件,添加这条记录: - - ```shell title="dns record" - 44.33.22.11 gitlab.example.com jenkins.example.com harbor.example.com - ``` - -2. 修改 `CoreDNS` 的配置,在 ConfigMap `kube-system/coredns` 中添加静态解析记录,执行命令:`kubectl edit cm coredns -n kube-system`,在 hosts(第20行左右) 部分添加: - - ```shell title="dns record" - 44.33.22.11 gitlab.example.com jenkins.example.com harbor.example.com - ``` - - 这时候在当前主机上,就可以分别通过如下地址访问到 GitLab、Jenkins 和 Harbor 了,同时 Jenkins 也能顺利地通过域名访问到 GitLab 和 Harbor: - - - `GitLab`: http://gitlab.example.com:30080 - - `Jenkins`: http://jenkins.example.com - - `Harbor`: http://harbor.example.com - -最后由于当前刚才 DevStream 使用了 Docker 的方式直接运行的 GitLab,所以不管是主机的 /etc/hosts 还是 CoreDNS 的配置都无法让 GitLab 解析到 Jenkins 的域名,因此你还需要在 GitLab 容器内加一行配置: - -```sh -docker exec -it gitlab bash -echo "44.33.22.11 jenkins.example.com" >> /etc/hosts -exit -``` - -### 6.2、访问 GitLab - -你可以在自己的 PC 里配置 `44.33.22.11 gitlab.example.com` 静态域名解析记录,然后在浏览器里通过 `http://gitlab.example.com:30080` 访问到 GitLab: - -
- ![GitLab login](./gitlab-jenkins-harbor-java-springboot/gitlab-login.png){ width="1000" } -
GitLab login page
-
- -通过执行如下命令,你可以设置 GitLab 的 root 密码: - -```shell title="get GitLab root Password" -docker exec -it gitlab bash # 进入容器 -gitlab-rake "gitlab:password:reset" # 执行后按照提示输入用户名 root,回车后输入密码 -``` - -拿到 root 密码后,你可以尝试用 root/YOUR_PASSWORD 来登录 GitLab。 - -### 6.3、访问 Jenkins - -前面你可能已经通过 `curl http://jenkins.example.com` 在主机内验证了 Jenkins 的网络连通性,想要远程通过域名访问 Jenkins,你还需要在自己的 PC 里配置 `44.33.22.11 jenkins.example.com` 静态域名解析记录。 - -接着在浏览器里通过 `http://jenkins.example.com` 就可以访问到 Jenkins 了: - -
- ![Jenkins login](./gitlab-jenkins-harbor-java-springboot/jenkins-login.png){ width="1000" } -
Jenkins login page
-
- -Jenkins 的 admin 用户初始登录密码是 `changeme`,如果你仔细看了前面 dtm 使用的配置文件,可以发现这是在配置文件里指定的。你可以尝试用 `admin/changeme` 登录 Jenkins 检查功能是否正常,不过当前你不需要在 Jenkins 上进行任何额外的操作。 - -
- ![Jenkins dashboard](./gitlab-jenkins-harbor-java-springboot/jenkins-dashboard.png){ width="1000" } -
Jenkins dashboard
-
- -### 6.4、访问 Harbor - -前面你可能也已经通过 `curl http://harbor.example.com` 在主机内验证了 Harbor 的网络连通性,同样你可以通过 `docker login harbor.example.com:80` 命令来尝试登录 Harbor。 - -现在你需要在自己的 PC 里配置 `44.33.22.11 harbor.example.com` 静态域名解析记录。 - -接着你可以在浏览器里通过 `http://harbor.example.com` 访问到 Harbor: - -
- ![Harbor login](./gitlab-jenkins-harbor-java-springboot/harbor-login.png){ width="1000" } -
Harbor login page
-
- -Harbor 的 admin 用户初始登录密码是 `Harbor12345`,你可以尝试用 `admin/Harbor12345` 登录 Harbor 检查功能是否正常,不过当前你同样也不需要在 Harbor 上进行任何额外的操作。 - -
- ![Harbor dashboard](./gitlab-jenkins-harbor-java-springboot/harbor-dashboard.png){ width="1000" } -
Harbor dashboard
-
- -## 7、环境清理 - -你可以通过如下命令清理环境: - -```shell title="环境清理命令" -dtm delete -f config-tools.yaml -y -``` diff --git a/docs/use-cases/gitlab-jenkins-harbor/3-gitlab-jenkins-harbor-air-gapped.md b/docs/use-cases/gitlab-jenkins-harbor/3-gitlab-jenkins-harbor-air-gapped.md deleted file mode 100644 index 2909c491a..000000000 --- a/docs/use-cases/gitlab-jenkins-harbor/3-gitlab-jenkins-harbor-air-gapped.md +++ /dev/null @@ -1,3 +0,0 @@ -# GitLab + Jenkins + Harbor Toolchain Air-gapped Environment Deployment - -// TODO(daniel-hutao): Chinese version first. diff --git a/docs/use-cases/gitlab-jenkins-harbor/3-gitlab-jenkins-harbor-air-gapped.zh.md b/docs/use-cases/gitlab-jenkins-harbor/3-gitlab-jenkins-harbor-air-gapped.zh.md deleted file mode 100644 index 84a21dc0f..000000000 --- a/docs/use-cases/gitlab-jenkins-harbor/3-gitlab-jenkins-harbor-air-gapped.zh.md +++ /dev/null @@ -1,284 +0,0 @@ -# 离线环境快速部署 GitLab + Jenkins + Harbor 工具链 - -在“[这个文档](./2-gitlab-jenkins-harbor.zh.md)”里我们介绍了怎样通过 DevStream 在本地快速部署 `GitLab + Jenkins + Harbor` 工具链。 - -但是如果你的服务器是离线的,你只有一台可以访问互联网的 PC,这台 PC 可以通过企业内部网络访问到你要用来部署 `GitLab + Jenkins + Harbor` 工具链服务器,类似下图这样: - -
- ![GitLab token](./gitlab-jenkins-harbor-air-gapped/air-gapped-env.png){ width="500" } -
-
- -这时候,你就需要用到 DevStream 的工具链离线部署能力了。 - -## 1、下载 dtm 和 DevStream Plugins - -首先你需要下载 DevStream 的命令行(CLI)工具 `dtm` 和所需的 DevStream 插件(plugins)。 - -### 1.1、下载 dtm - -你可以参考[这个文档](../../install.zh.md)下载 dtm。 - -唯一需要注意的是,下载完之后,请记得将 dtm 传输到你需要使用它的机器上。 - -### 1.2、下载 plugins - -继续在你的 PC 上执行如下命令来下载 DevStream plugins: - -```shell -dtm init --download-only --plugins="gitlab-ce-docker, helm-installer" -d=plugins -``` - -这条命令执行成功后,你可以在本地 plugins 目录下看到如下文件: - -```shell -$ ls plugins/ -gitlab-ce-docker-linux-amd64_0.10.3.md5 -helm-installer-linux-amd64_0.10.3.md5 -gitlab-ce-docker-linux-amd64_0.10.3.so -helm-installer-linux-amd64_0.10.3.so -``` - -## 2、下载镜像 - -因为 DevStream 需要使用容器化方式部署 GitLab、Jenkins 和 Harbor,那么在开始离线部署前,你需要先下载这几个工具对应的容器镜像。DevStream 提供了这几个工具对应的镜像列表,并且帮你准备了工具脚本从而更加容易地完成镜像离线工作: - -1. [GitLab CE images](../../plugins/gitlab-ce-docker/gitlab-ce-images.txt) -2. [Jenkins images](../../plugins/helm-installer/jenkins/jenkins-images.txt) -3. [Harbor images](../../plugins/helm-installer/harbor/harbor-images.txt) - -你可以通过如下命令将镜像列表下载到本地: - -```shell -curl -o jenkins-images.txt https://raw.githubusercontent.com/devstream-io/devstream/main/docs/plugins/helm-installer/jenkins/jenkins-images.txt -curl -o harbor-images.txt https://raw.githubusercontent.com/devstream-io/devstream/main/docs/plugins/helm-installer/harbor/harbor-images.txt -curl -o jenkins-images.txt https://raw.githubusercontent.com/devstream-io/devstream/main/docs/plugins/gitlab-ce-docker/gitlab-ce-images.txt -``` - -可以通过如下命令下载 DevStream 提供的工具脚本,这个脚本可以帮助你快速将这些镜像下载到本地并且上传到私有镜像仓库: - -```shell -curl -o image-pull-push.sh https://raw.githubusercontent.com/devstream-io/devstream/main/hack/image-pull-push.sh -chmod +x image-pull-push.sh -``` - -如果你还没有一个私有镜像仓库,可以参考[这篇文章](../reference/image-registry.zh.md)快速部署一个 Docker Registry。 - -接下来,你就可以通过下述命令快速完成镜像的下载和上传了: - -```shell -# 查看工具脚本的使用方法和注意事项等 -./image-pull-push.sh -h # (1) -# 设置镜像仓库地址,按需修改 -export IMAGE_REPO_ADDR=registry.devstream.io -# 下载 xxx-images.txt 中所有镜像并保存到本地压缩包中 -./image-pull-push.sh -f harbor-images.txt -r ${IMAGE_REPO_ADDR} -s -./image-pull-push.sh -f jenkins-images.txt -r ${IMAGE_REPO_ADDR} -s -./image-pull-push.sh -f gitlab-ce-images.txt -r ${IMAGE_REPO_ADDR} -s -# 从压缩包中 load 镜像并 push 到私有镜像仓库(如果镜像仓库需要登录,则需要先手动执行 docker login) -./image-pull-push.sh -f harbor-images.txt -r ${IMAGE_REPO_ADDR} -l -u -./image-pull-push.sh -f jenkins-images.txt -r ${IMAGE_REPO_ADDR} -l -u -./image-pull-push.sh -f gitlab-ce-images.txt -r ${IMAGE_REPO_ADDR} -l -u -``` - -1. 强烈建议你先看下本脚本的使用说明和示例 - -!!! note "注意" - - 如果你下载镜像的机器和内部私有镜像仓库之间网络隔离,那么你可以在镜像下载到本地压缩包后,先将该压缩包复制到能够访问镜像仓库的机器上,然后再执行 load 和 push 等操作。 - -## 3、下载 Helm Chart 包 - -你可以通过如下命令下载 Harbor 和 Jenkins 的 Helm chart 包: - -```shell -helm repo add harbor https://helm.goharbor.io -helm repo update -helm search repo harbor -l -helm pull harbor/harbor --version=1.10.0 -``` - -```shell -helm repo add jenkins https://charts.jenkins.io -helm repo update -helm search repo jenkins -l -helm pull jenkins/jenkins --version=4.2.5 -``` - -执行完上述命令后,你可以在本地看到如下文件: - -```shell -$ ls -harbor-1.10.0.tgz jenkins-4.2.5.tgz -``` - -## 4、准备配置文件 - -这时候需要联网下载的各种“物料”你就准备好了。接着你可以开始编写 DevStream 的配置文件了: - -```yaml title="DevStream Config" -config: - state: - backend: local - options: - stateFile: devstream.state -vars: - imageRepo: registry.devstream.io - gitlabHostname: gitlab.example.com - jenkinsHostname: jenkins.example.com - harborHostname: harbor.example.com - harborURL: http://harbor.example.com - jenkinsAdminUser: admin - jenkinsAdminPassword: changeme - gitlabSSHPort: 30022 - gitlabHttpPort: 30080 - gitlabHttpsPort: 30443 - -tools: -- name: gitlab-ce-docker - instanceID: default - dependsOn: [] - options: - hostname: [[ gitlabHostname ]] - gitlabHome: /srv/gitlab - sshPort: [[ gitlabSSHPort ]] - httpPort: [[ gitlabHttpPort ]] - httpsPort: [[ gitlabHttpsPort ]] - rmDataAfterDelete: false - imageTag: "rc" -- name: helm-installer - instanceID: jenkins-001 - dependsOn: [] - options: - chartPath: "./jenkins-4.2.5.tgz" - valuesYaml: | - serviceAccount: - create: true - name: jenkins - controller: - image: [[ imageRepo ]]/devstreamdev/jenkins - tag: 2.361.1-jdk11-dtm-0.1 - imagePullPolicy: "IfNotPresent" - sidecars: - configAutoReload: - image: [[ imageRepo ]]/kiwigrid/k8s-sidecar:1.15.0 - adminUser: [[ jenkinsAdminUser ]] - adminPassword: [[ jenkinsAdminPassword ]] - ingress: - enabled: true - hostName: [[ jenkinsHostname ]] - enableRawHtmlMarkupFormatter: true - JCasC: - defaultConfig: true -- name: helm-installer - instanceID: harbor-001 - dependsOn: [] - options: - chartPath: "./harbor-1.10.0.tgz" - valuesYaml: | - externalURL: [[ harborURL ]] - expose: - type: ingress - tls: - enabled: false - ingress: - hosts: - core: [[ harborHostname ]] - nginx: - image: - repository: [[ imageRepo ]]/goharbor/nginx-photon - tag: v2.5.3 - portal: - image: - repository: [[ imageRepo ]]/goharbor/harbor-portal - tag: v2.5.3 - core: - image: - repository: [[ imageRepo ]]/goharbor/harbor-core - tag: v2.5.3 - jobservice: - image: - repository: [[ imageRepo ]]/goharbor/harbor-jobservice - tag: v2.5.3 - registry: - registry: - image: - repository: [[ imageRepo ]]/goharbor/registry-photon - tag: v2.5.3 - controller: - image: - repository: [[ imageRepo ]]/goharbor/harbor-registryctl - tag: v2.5.3 - chartmuseum: - enabled: false - image: - repository: [[ imageRepo ]]/goharbor/chartmuseum-photon - tag: v2.5.3 - trivy: - enabled: false - image: - repository: [[ imageRepo ]]/goharbor/trivy-adapter-photon - tag: v2.5.3 - notary: - enabled: false - server: - image: - repository: [[ imageRepo ]]/goharbor/notary-server-photon - tag: v2.5.3 - signer: - image: - repository: [[ imageRepo ]]/goharbor/notary-signer-photon - tag: v2.5.3 - database: - internal: - image: - repository: [[ imageRepo ]]/goharbor/harbor-db - tag: v2.5.3 - redis: - internal: - image: - repository: [[ imageRepo ]]/goharbor/redis-photon - tag: v2.5.3 - exporter: - image: - repository: [[ imageRepo ]]/goharbor/harbor-exporter - tag: v2.5.3 - persistence: - persistentVolumeClaim: - registry: - storageClass: "" - accessMode: ReadWriteOnce - size: 5Gi - jobservice: - storageClass: "" - accessMode: ReadWriteOnce - size: 1Gi - database: - storageClass: "" - accessMode: ReadWriteOnce - size: 1Gi - redis: - storageClass: "" - accessMode: ReadWriteOnce - size: 1Gi -``` - -你可以将这个配置文件保存为 `config.yaml` - -## 5、开始部署 - -现在你可以通过如下命令开始部署 GitLab、Jenkins 和 Harbor 了: - -```shell -dtm apply -f config.yaml -y -``` - -完成部署后,你可以参考[这篇文档](./2-gitlab-jenkins-harbor.zh.md)继续学习如何访问 GitLab、Jenkins 和 Harbor 三个工具。 - -## 6、环境清理 - -你可以通过如下命令清理环境: - -```shell title="环境清理命令" -dtm delete -f config.yaml -y -``` diff --git a/docs/use-cases/gitlab-jenkins-harbor/4-jenkins-gitlab-integ.md b/docs/use-cases/gitlab-jenkins-harbor/4-jenkins-gitlab-integ.md deleted file mode 100644 index 665323669..000000000 --- a/docs/use-cases/gitlab-jenkins-harbor/4-jenkins-gitlab-integ.md +++ /dev/null @@ -1,3 +0,0 @@ -# Integate Jenkins With Your GitLab - -// TODO(daniel-hutao): Chinese version first. diff --git a/docs/use-cases/gitlab-jenkins-harbor/4-jenkins-gitlab-integ.zh.md b/docs/use-cases/gitlab-jenkins-harbor/4-jenkins-gitlab-integ.zh.md deleted file mode 100644 index c93d0ccfb..000000000 --- a/docs/use-cases/gitlab-jenkins-harbor/4-jenkins-gitlab-integ.zh.md +++ /dev/null @@ -1,3 +0,0 @@ -# 一键打通你的 Jenkins 与 GitLab - -// TODO(daniel-hutao): Write this doc in the next pr. diff --git a/docs/use-cases/gitlab-jenkins-harbor/5-java-springboot-pipeline-with-gitlab-jenkins-harbor.md b/docs/use-cases/gitlab-jenkins-harbor/5-java-springboot-pipeline-with-gitlab-jenkins-harbor.md deleted file mode 100644 index 3c2ff4074..000000000 --- a/docs/use-cases/gitlab-jenkins-harbor/5-java-springboot-pipeline-with-gitlab-jenkins-harbor.md +++ /dev/null @@ -1,3 +0,0 @@ -# Java Spring Boot Project Scaffolding and Jenkins CI Pipeline Automated Creating - -// TODO(daniel-hutao): Chinese version first. diff --git a/docs/use-cases/gitlab-jenkins-harbor/5-java-springboot-pipeline-with-gitlab-jenkins-harbor.zh.md b/docs/use-cases/gitlab-jenkins-harbor/5-java-springboot-pipeline-with-gitlab-jenkins-harbor.zh.md deleted file mode 100644 index d52dbb420..000000000 --- a/docs/use-cases/gitlab-jenkins-harbor/5-java-springboot-pipeline-with-gitlab-jenkins-harbor.zh.md +++ /dev/null @@ -1,246 +0,0 @@ -# Java Spring Boot 项目脚手架的快速创建与 Jenkins CI 流水线的自动化配置 - -在[快速部署 GitLab + Jenkins + Harbor 工具链](./2-gitlab-jenkins-harbor.zh.md)中你已经学会了如何使用 DevStream 快速部署 GitLab + Jenkins + Harbor 工具链。 - -本文将基于部署好 GitLab + Jenkins + Harbor 工具链,继续以 Java Spring Boot 项目为例,演示如何通过 DevStream 快速创建 Java Spring Boot 项目脚手架,同时在 Jenkins 上自动创建对应的 Pipeline 实现 Java Spring Boot 项目的 CI 流程。 - -## 1、工作流介绍 - -本文最终将实现的工具链相关工作流如下图所示: - -
- ![Workflow](./gitlab-jenkins-harbor-java-springboot/workflow.png){ width="1000" } -
GitLab + Jenkins + Harbor Toolchain Workflow
-
- -这里的工作流主要是: - -1. DevStream 根据你给定配置,选择默认项目模板或者用户自定义模板创建项目脚手架; -2. DevStream 根据你给定配置,使用 CI 模板创建 CI 流程,过程中会涉及到 CI 工具的配置(比如调用 Jenkins API 完成一些 Jenkins 插件的安装等); -3. 最终 DevStream 完成全部配置后,如果你提交代码到 DevStream 为你创建的代码库中,GitLab 便会出发 Jenkins 执行相应的 CI 流程,Jenkins 上的流水线运行结果也会实时回显到 GitLab 上,并且这个过程中构建出来的容器镜像会被自动推送到 Harbor 上。 - -## 2、安装 dtm - -你可以参考[这个文档](../../install.zh.md)完成 dtm 的下载与安装。 - -## 3、准备配置文件 - -DevStream 可以简单地以 **local** 作为[状态](../../core-concepts/state.zh.md) Backend,也就是将状态保存到本地文件。如果你在本地测试,可以选择使用这种方式; -而企业 On premise 环境部署可能需要使用 **k8s** Backend 将状态通过 `kube-apiserver` 存入 etcd,两种方式配置分别如下: - -=== "DevStream with 'local' Backend" - - ```yaml title="local Backend" - config: - state: - backend: local - options: - stateFile: devstream.state - ``` - -=== "DevStream with 'k8s' Backend" - - ```yaml title="k8s Backend" - config: - state: - backend: k8s - options: - namespace: devstream - configmap: state - ``` - -下文将以 `local` Backend 为例演示。 - -在编写应用相关配置项之前,你需要先定义一些变量,这会让后续的配置和维护工作变得更加简单: - -```yaml title="config-apps.yaml" -config: - state: - backend: local - options: - stateFile: devstream.state -vars: - appName: myapp - gitlabURL: http://gitlab.example.com:30080 - jenkinsURL: http://jenkins.example.com - harborURL: http://harbor.example.com -``` - -你可以根据自己的需要,选择性自定义上述 vars 配置的值,这些变量内容主要是域名等可修改配置项。 - -继续往里面追加应用相关配置,你的配置文件会扩充成这样: - -```yaml title="config-apps.yaml" -config: - state: - backend: local - options: - stateFile: devstream-app.state -vars: - appName: myapp - gitlabURL: http://gitlab.example.com:30080 - jenkinsURL: http://jenkins.example.com - harborURL: http://harbor.example.com -apps: -- name: [[ appName ]] - spec: - language: java - framework: springboot - repo: - url: [[ gitlabURL ]]/root/[[ appName ]].git - branch: main - token: [[ env GITLAB_TOKEN ]] - repoTemplate: - url: https://github.com/devstream-io/dtm-repo-scaffolding-java-springboot.git - ci: - - type: template - templateName: ci-pipeline -pipelineTemplates: -- name: ci-pipeline - type: jenkins-pipeline - options: - branch: main - jenkins: - url: [[ jenkinsURL ]] - user: admin - enableRestart: true - password: [[ env JENKINS_PASSWORD ]] - imageRepo: - user: admin - url: [[ harborURL ]]/library - password: [[ env IMAGE_REPO_PASSWORD ]] -``` - -你可以将这个配置文件放到服务器上的某一个路径内,比如 `~/devstream-test/config-apps.yaml`。 - -## 4、让配置生效 - -你还需要几个简单的步骤来让上述配置生效。 - -### 4.1、准备 GitLab Token - -你可以参考下图方式在 GitLab 上创建一个 token,这个 token 将给 DevStream 必要的权限用来在 GitLab 上创建项目脚手架等: - -
- ![GitLab token](./gitlab-jenkins-harbor-java-springboot/gitlab-token.png){ width="1000" } -
Generate GitLab token
-
- -然后这个 token 需要被设置到环境变量里: - -```shell title="环境变量配置" -export GITLAB_TOKEN=YOUR_GITLAB_TOKEN -``` - -同时需要将 Harbor 密码配置到环境变量里: - -```shell title="环境变量配置" -export IMAGE_REPO_PASSWORD=Harbor12345 -``` - -此外由于 DevStream 需要调用 Jenkins 的 API 来帮你创建流水线,所以你还需要告诉 DevStream Jenkins 的密码: - -```shell title="环境变量配置" -export JENKINS_PASSWORD=changeme -``` - -你可以将这个配置文件放到服务器上同一个目录,比如 `~/devstream-test/`,然后在该目录下执行: - -### 4.2、开始执行 - -首先你需要执行初始化命令: - -```shell title="初始化" -dtm init -f config-apps.yaml -``` - -这时候 DevStream 会帮你下载 `jenkins-pipeline` 和 `repo-scaffolding` 两个插件,最终将有这两个插件来帮你完成代码库脚手架的创建和 Jenkins 流水线的配置。 - -接着你可以继续执行如下命令 - -```shell -dtm apply -f config-apps.yaml -y -``` - -如果 apply 命令执行成功的话,你可以看到大致如下日志: - -```shell title="执行日志" -2022-12-02 01:04:44 ℹ [INFO] Delete started. -2022-12-02 01:04:44 ℹ [INFO] Using local backend. State file: devstream-app.state. -2022-12-02 01:04:44 ℹ [INFO] Tool (jenkins-pipeline/myapp) will be deleted. -2022-12-02 01:04:44 ℹ [INFO] Tool (repo-scaffolding/myapp) will be deleted. -2022-12-02 01:04:44 ℹ [INFO] Start executing the plan. -2022-12-02 01:04:44 ℹ [INFO] Changes count: 2. -2022-12-02 01:04:44 ℹ [INFO] -------------------- [ Processing progress: 1/2. ] ---------- ----------- -2022-12-02 01:04:44 ℹ [INFO] Processing: (jenkins-pipeline/myapp) -> Delete ... -2022-12-02 01:04:46 ℹ [INFO] Prepare to delete 'jenkins-pipeline_myapp' from States. -2022-12-02 01:04:46 ✔ [SUCCESS] Tool (jenkins-pipeline/myapp) delete done. -2022-12-02 01:04:46 ℹ [INFO] -------------------- [ Processing progress: 2/2. ] -------------------- -2022-12-02 01:04:46 ℹ [INFO] Processing: (repo-scaffolding/myapp) -> Delete ... -2022-12-02 01:04:46 ℹ [INFO] Prepare to delete 'repo-scaffolding_myapp' from States. -2022-12-02 01:04:46 ✔ [SUCCESS] Tool (repo-scaffolding/myapp) delete done. -2022-12-02 01:04:46 ℹ [INFO] -------------------- [ Processing done. ] -------------------- -2022-12-02 01:04:46 ✔ [SUCCESS] All plugins deleted successfully. -2022-12-02 01:04:46 ✔ [SUCCESS] Delete finished. -root@dtm-realk8sdev:~# ./dtm apply -y -f config-apps.yaml -2022-12-02 01:04:55 ℹ [INFO] Apply started. -2022-12-02 01:04:55 ℹ [INFO] Using local backend. State file: devstream-app.state. -2022-12-02 01:04:55 ℹ [INFO] Tool (repo-scaffolding/myapp) found in config but doesn't exist in the state, will be created. -2022-12-02 01:04:55 ℹ [INFO] Tool (jenkins-pipeline/myapp) found in config but doesn't exist in the state, will be created. -2022-12-02 01:04:55 ℹ [INFO] Start executing the plan. -2022-12-02 01:04:55 ℹ [INFO] Changes count: 2. -2022-12-02 01:04:55 ℹ [INFO] -------------------- [ Processing progress: 1/2. ] -------------------- -2022-12-02 01:04:55 ℹ [INFO] Processing: (repo-scaffolding/myapp) -> Create ... -2022-12-02 01:04:55 ℹ [INFO] github start to download repoTemplate... -2022-12-02 01:04:56 ✔ [SUCCESS] Tool (repo-scaffolding/myapp) Create done. -2022-12-02 01:04:56 ℹ [INFO] -------------------- [ Processing progress: 2/2. ] -------------------- -2022-12-02 01:04:56 ℹ [INFO] Processing: (jenkins-pipeline/myapp) -> Create ... -2022-12-02 01:04:57 ℹ [INFO] jenkins plugin imageRepo start config... -2022-12-02 01:04:57 ⚠ [WARN] jenkins gitlab ssh key not config, private repo can't be clone -2022-12-02 01:04:57 ℹ [INFO] jenkins start config casc... -2022-12-02 01:04:59 ✔ [SUCCESS] Tool (jenkins-pipeline/myapp) Create done. -2022-12-02 01:04:59 ℹ [INFO] -------------------- [ Processing done. ] -------------------- -2022-12-02 01:04:59 ✔ [SUCCESS] All plugins applied successfully. -2022-12-02 01:04:59 ✔ [SUCCESS] Apply finished. -``` - -## 5、查看执行结果 - -这时候你可以在 GitLab 上看到 dtm 为你准备的 Java Spring Boot 项目脚手架: - -
- ![Repo Scaffolding](./gitlab-jenkins-harbor-java-springboot/repo-scaffolding.png){ width="1000" } -
Repo scaffolding
-
- -接着你可以登录 Jenkins,查看 dtm 为你创建的 Pipeline: - -
- ![Jenkins Pipeline](./gitlab-jenkins-harbor-java-springboot/jenkins-pipeline.png){ width="1000" } -
Jenkins pipeline
-
- -这个 Pipeline 会自动执行一次,执行完成后回到 GitLab,你可以看到 Jenkins 回写的 Pipeline 状态: - -
- ![GitLab Status](./gitlab-jenkins-harbor-java-springboot/gitlab-status.png){ width="1000" } -
GitLab Status
-
- -后面每当 GitLab 上这个 repo 发生 Push 或者 Merge 事件的时候,就会触发 Jenkins 上的 Pipeline 运行。 - -当然,在 Harbor 上你可以找到 CI 流程构建出来的容器镜像: - -
- ![GitLab Status](./gitlab-jenkins-harbor-java-springboot/harbor-image.png){ width="1000" } -
Image in Harbor
-
- -## 6、环境清理 - -你可以通过如下命令清理环境: - -```shell title="环境清理命令" -dtm delete -f config-apps.yaml -y -``` diff --git a/docs/use-cases/gitlab-jenkins-harbor/6-gitlab-jenkins-harbor-java-springboot.md b/docs/use-cases/gitlab-jenkins-harbor/6-gitlab-jenkins-harbor-java-springboot.md deleted file mode 100644 index 2901e3e53..000000000 --- a/docs/use-cases/gitlab-jenkins-harbor/6-gitlab-jenkins-harbor-java-springboot.md +++ /dev/null @@ -1,3 +0,0 @@ -# to be translated - -// TODO(daniel-hutao): Chinese version first diff --git a/docs/use-cases/gitlab-jenkins-harbor/6-gitlab-jenkins-harbor-java-springboot.zh.md b/docs/use-cases/gitlab-jenkins-harbor/6-gitlab-jenkins-harbor-java-springboot.zh.md deleted file mode 100644 index f99d8b233..000000000 --- a/docs/use-cases/gitlab-jenkins-harbor/6-gitlab-jenkins-harbor-java-springboot.zh.md +++ /dev/null @@ -1,542 +0,0 @@ -# 搭建 GitLab + Jenkins + Harbor 工具链,管理 Java Spring Boot 项目开发生命周期全流程 - -## 0、配套视频 - -我们给本文流程录制了视频 demo,你可以根据自己的习惯决定先看视频版还是文字版: - -
- -
- -!!! hint "提示" - - 跳转到B站观看清晰度更高。 - -## 1、概述 - -本文将介绍如何通过 DevStream 在本地部署 `GitLab + Jenkins + Harbor` 工具链,并且以 Java Spring Boot 项目为例,演示如何通过 DevStream 快速创建 Java Spring Boot 项目脚手架,同时在 Jenkins 上自动创建对应的 Pipeline 实现 Java Spring Boot 项目的 CI 流程。 - -!!! hint "提示" - - 本文基于 kubeadm 部署的单节点 k8s 环境,不适用于 minikube 和 kind 等 docker-in-docker 类型的 k8s 集群。 - -### 1.1、工作流介绍 - -本文最终将实现的工具链相关工作流如下图所示: - -
- ![Workflow](./gitlab-jenkins-harbor-java-springboot/workflow.png){ width="1000" } -
GitLab + Jenkins + Harbor Toolchain Workflow
-
- -图中工作流主要是: - -1. DevStream 会按需部署 GitLab、Jenkins 和 Harbor 3个工具; -2. DevStream 根据你给定配置,选择默认项目模板或者用户自定义模板创建项目脚手架; -3. DevStream 根据你给定配置,使用 CI 模板创建 CI 流程,过程中会涉及到 CI 工具的配置(比如调用 Jenkins API 完成一些 Jenkins 插件的安装等); -4. 最终 DevStream 完成全部配置后,如果你提交代码到 DevStream 为你创建的代码库中,GitLab 便会出发 Jenkins 执行相应的 CI 流程,Jenkins 上的流水线运行结果也会实时回显到 GitLab 上,并且这个过程中构建出来的容器镜像会被自动推送到 Harbor 上。 - -!!! note "注意" - - 这个图中的 GitLab 和 GitHub 可以随意互换,它们只是被当做保存项目脚手架模板库存放地址和脚手架模板渲染后的最终项目存放地址,DevStream 对 GitHub 和 GitLab 都支持。换言之,如果你将模板保存到 GitLab,最终项目也托管在 GitLab,便可以完全不依赖 GitHub 而使用 DevStream。 - -### 1.2 安装 dtm - -你可以参考[这个文档](../../install.zh.md)完成 dtm 的下载与安装。 - -### 1.3、相关插件概览 - -当前工具链主要涉及如下 DevStream 插件: - -- **工具链搭建** - - [`gitlab-ce-docker`](../../plugins/gitlab-ce-docker.zh.md):本地部署 GitLab 环境; - - [`helm-installer`](../../plugins/helm-installer/helm-installer.zh.md):本地部署 Jenkins 和 Harbor 环境。 -- **工具链使用** - - [`repo-scaffolding`](../../plugins/repo-scaffolding.zh.md):创建 Java Spring Boot 项目脚手架; - - [`jenkins-pipeline`](../../plugins/jenkins-pipeline.zh.md):在 Jenkins 上创建 Pipeline,并打通 GitLab 与 Jenkins,实现 GitLab 上发生 Push/Merge 等事件时触发 Jenkins Pipeline 运行,并且让 Pipeline 状态能够回写到 GitLab。 - -!!! hint "提示" - - 1. 上述插件不是必选集,你可以根据实际情况灵活调整。比如你本地已经有 GitLab 环境了,那么你可以果断忽略 `gitlab-ce-docker` 插件。 - 2. 你不再需要关心 repo-scaffolding 和 jenkins-pipeline 这类“非工具部署”相关插件的配置,你只要定义好自己的“app”,剩下的工作 DevStream 会帮你完成。至于 app 如何定义,下文会详细介绍。 - -### 1.4、部署流程介绍 - -你将分2步来完成这条工具链的搭建过程。 - -1. 使用 `gitlab-ce-docker`、`jenkins` 和 `harbor` 三个插件完成 GitLab、Jenkins 和 Harbor 工具的部署; -2. 定义一个 app,来实现 Java Spring Boot 项目脚手架的创建和 Jenkins Pipeline 的配置等工作。 - -## 2、开始部署 GitLab + Jenkins + Harbor - -本节接续介绍如何使用 DevStream 来完成 GitLab、Jenkins 和 Harbor 3个工具的部署。 - -### 2.1、准备 tools 配置文件(config-tools.yaml) - -DevStream 可以简单地以 **local** 作为 Backend,也就是将状态保存到本地文件,如果你在本地测试,可以使用这种方式; -而企业 On premise 环境部署可能需要使用 **k8s** Backend 将状态通过 `kube-apiserver` 存入 etcd,两种方式配置分别如下: - -=== "DevStream with 'local' Backend" - - ```yaml title="local Backend" - config: - state: - backend: local - options: - stateFile: devstream.state - ``` - -=== "DevStream with 'k8s' Backend" - - ```yaml title="k8s Backend" - config: - state: - backend: k8s - options: - namespace: devstream - configmap: state - ``` - -下文将以 `local` Backend 为例演示。 - -在编写 `gitlab-ce-docker` 和 `helm-installer`(用户安装 Jenkins 和 Harbor)这两个插件的配置文件之前,你需要先定义一些变量,这会让后续的配置和维护工作变得更加简单: - -```yaml title="config-tools.yaml" -config: - state: - backend: local - options: - stateFile: devstream.state -vars: - gitlabHostname: gitlab.example.com - jenkinsHostname: jenkins.example.com - harborHostname: harbor.example.com - harborURL: http://harbor.example.com - jenkinsAdminUser: admin - jenkinsAdminPassword: changeme - gitlabSSHPort: 30022 - gitlabHttpPort: 30080 - gitlabHttpsPort: 30443 -``` - -继续往里面追加工具链相关插件的配置,你的配置文件会扩充成这样: - -```yaml title="config-tools.yaml" -config: - state: - backend: local - options: - stateFile: devstream.state -vars: - gitlabHostname: gitlab.example.com - jenkinsHostname: jenkins.example.com - harborHostname: harbor.example.com - harborURL: http://harbor.example.com - jenkinsAdminUser: admin - jenkinsAdminPassword: changeme - gitlabSSHPort: 30022 - gitlabHttpPort: 30080 - gitlabHttpsPort: 30443 -tools: -- name: gitlab-ce-docker - instanceID: default - dependsOn: [] - options: - hostname: [[ gitlabHostname ]] - gitlabHome: /srv/gitlab - sshPort: [[ gitlabSSHPort ]] - httpPort: [[ gitlabHttpPort ]] - httpsPort: [[ gitlabHttpsPort ]] - rmDataAfterDelete: false - imageTag: "rc" -- name: helm-installer - instanceID: jenkins-001 - dependsOn: [] - options: - valuesYaml: | - serviceAccount: - create: true - name: jenkins - controller: - adminUser: [[ jenkinsAdminUser ]] - adminPassword: [[ jenkinsAdminPassword ]] - ingress: - enabled: true - hostName: [[ jenkinsHostname ]] - enableRawHtmlMarkupFormatter: true - JCasC: - defaultConfig: true -- name: helm-installer - instanceID: harbor-001 - dependsOn: [] - options: - valuesYaml: | - externalURL: [[ harborURL ]] - expose: - type: ingress - tls: - enabled: false - ingress: - hosts: - core: [[ harborHostname ]] - chartmuseum: - enabled: false - notary: - enabled: false - trivy: - enabled: false - persistence: - persistentVolumeClaim: - registry: - storageClass: "" - accessMode: ReadWriteOnce - size: 5Gi - jobservice: - storageClass: "" - accessMode: ReadWriteOnce - size: 1Gi - database: - storageClass: "" - accessMode: ReadWriteOnce - size: 1Gi - redis: - storageClass: "" - accessMode: ReadWriteOnce - size: 1Gi -``` - -### 2.2、初始化 - -你可以将上面这个配置文件(config-tools.yaml)放到服务器上任意一个合适的目录,比如 `~/devstream-test/`,然后在该目录下执行: - -```shell title="初始化命令" -dtm init -f config-tools.yaml -``` - -这个命令会帮助你下载所有需要的 DevStream 插件。 - -### 2.3、开始部署 - -接着你就可以执行 apply 命令了: - -```shell title="开始部署" -dtm apply -f config-tools.yaml -y -``` - -这个命令执行成功的话,你可以大致看到如下日志: - -```shell title="部署日志" -2022-11-30 08:14:05 ℹ [INFO] Apply started. -2022-11-30 08:14:06 ℹ [INFO] Using local backend. State file: devstream.state. -2022-11-30 08:14:06 ℹ [INFO] Tool (gitlab-ce-docker/default) found in config but doesn't exist in the state, will be created. -2022-11-30 08:14:06 ℹ [INFO] Tool (helm-installer/jenkins-001) found in config but doesn't exist in the state, will be created. -2022-11-30 08:14:06 ℹ [INFO] Tool (helm-installer/harbor-001) found in config but doesn't exist in the state, will be created. -2022-11-30 08:14:06 ℹ [INFO] Start executing the plan. -2022-11-30 08:14:06 ℹ [INFO] Changes count: 3. -2022-11-30 08:14:06 ℹ [INFO] -------------------- [ Processing progress: 1/3. ] -------------------- -2022-11-30 08:14:06 ℹ [INFO] Processing: (gitlab-ce-docker/default) -> Create ... -2022-11-30 08:14:06 ℹ [INFO] Cmd: docker image ls gitlab/gitlab-ce:rc -q. -2022-11-30 08:14:06 ℹ [INFO] Running container as the name -2022-11-30 08:14:06 ℹ [INFO] Cmd: docker run --detach --hostname gitlab.example.com --publish 30022:22 --publish 30080:80 --publish 30443:443 --name gitlab --restart always --volume /srv/gitlab/config:/etc/gitlab --volume /srv/gitlab/data:/var/opt/gitlab --volume /srv/gitlab/logs:/var/log/gitlab gitlab/gitlab-ce:rc. -Stdout: 34cdd2a834a1c21be192064eacf1e29536ff45c52562956b97d6d376a5dae11b -2022-11-30 08:14:07 ℹ [INFO] Cmd: docker inspect --format='{{json .Mounts}}' gitlab. -2022-11-30 08:14:07 ℹ [INFO] GitLab access URL: http://gitlab.example.com:30080 -2022-11-30 08:14:07 ℹ [INFO] GitLab initial root password: execute the command -> docker exec -it gitlab grep 'Password:' /etc/gitlab/initial_root_password -2022-11-30 08:14:07 ✔ [SUCCESS] Tool (gitlab-ce-docker/default) Create done. -2022-11-30 08:14:07 ℹ [INFO] -------------------- [ Processing progress: 2/3. ] -------------------- -2022-11-30 08:14:07 ℹ [INFO] Processing: (helm-installer/jenkins-001) -> Create ... -2022-11-30 08:14:07 ℹ [INFO] Filling default config with instance: jenkins-001. -2022-11-30 08:14:07 ℹ [INFO] Creating or updating helm chart ... -2022/11/30 08:14:09 creating 13 resource(s) -2022/11/30 08:14:09 beginning wait for 13 resources with timeout of 10m0s -2022/11/30 08:14:09 StatefulSet is not ready: jenkins/jenkins. 0 out of 1 expected pods are ready -... -2022/11/30 08:14:49 StatefulSet is not ready: jenkins/jenkins. 0 out of 1 expected pods are ready -2022/11/30 08:14:51 release installed successfully: jenkins/jenkins-4.2.15 -2022-11-30 08:14:51 ✔ [SUCCESS] Tool (helm-installer/jenkins-001) Create done. -2022-11-30 08:14:51 ℹ [INFO] -------------------- [ Processing progress: 3/3. ] -------------------- -2022-11-30 08:14:51 ℹ [INFO] Processing: (helm-installer/harbor-001) -> Create ... -2022-11-30 08:14:51 ℹ [INFO] Filling default config with instance: harbor-001. -2022-11-30 08:14:51 ℹ [INFO] Creating or updating helm chart ... -2022/11/30 08:14:52 checking 28 resources for changes -2022/11/30 08:14:52 Created a new Secret called "harbor-core" in harbor -... -2022/11/30 08:14:52 Created a new Ingress called "harbor-ingress" in harbor -2022/11/30 08:14:52 beginning wait for 28 resources with timeout of 10m0s -2022/11/30 08:14:52 Deployment is not ready: harbor/harbor-core. 0 out of 1 expected pods are ready -... -2022/11/30 08:15:50 Deployment is not ready: harbor/harbor-jobservice. 0 out of 1 expected pods are ready -2022/11/30 08:15:52 release installed successfully: harbor/harbor-1.10.2 -2022-11-30 08:15:52 ✔ [SUCCESS] Tool (helm-installer/harbor-001) Create done. -2022-11-30 08:15:52 ℹ [INFO] -------------------- [ Processing done. ] -------------------- -2022-11-30 08:15:52 ✔ [SUCCESS] All plugins applied successfully. -2022-11-30 08:15:52 ✔ [SUCCESS] Apply finished. -``` - -从日志里你可以看到,这时候 GitLab、Jenkins 和 Harbor 就已经部署完成了。 - -### 2.4、验证三个工具的部署结果 - -你可以通过如下方式验证 GitLab + Jenkins + Harbor 三个工具的部署结果。 - -#### 2.4.1、DNS 配置 - -前面你给 GitLab + Jenkins + Harbor 三个工具的配置文件里都设置了域名,然后你可以直接将这些域名与 IP 的映射关系配置到 DNS 服务器里。 - -如果没有 DNS 服务器,你也可以直接将域名与 IP 的映射关系配置到 `/etc/hosts` 以及 `CoreDNS` 的 ConfigMap `kube-system/coredns` 里让域名生效。比如我的主机 IP 是 44.33.22.11,这时候可以这样配置: - -1. 修改 `/etc/hosts` 文件,添加这条记录: - - ```shell title="dns record" - 44.33.22.11 gitlab.example.com jenkins.example.com harbor.example.com - ``` - -2. 修改 `CoreDNS` 的配置,在 ConfigMap `kube-system/coredns` 中添加静态解析记录,执行命令:`kubectl edit cm coredns -n kube-system`,在 hosts(第20行左右) 部分添加: - - ```shell title="dns record" - 44.33.22.11 gitlab.example.com jenkins.example.com harbor.example.com - ``` - - 这时候在当前主机上,就可以分别通过如下地址访问到 GitLab、Jenkins 和 Harbor 了,同时 Jenkins 也能顺利地通过域名访问到 GitLab 和 Harbor: - - - `GitLab`: http://gitlab.example.com:30080 - - `Jenkins`: http://jenkins.example.com - - `Harbor`: http://harbor.example.com - -最后由于当前刚才 DevStream 使用了 Docker 的方式直接运行的 GitLab,所以不管是主机的 /etc/hosts 还是 CoreDNS 的配置都无法让 GitLab 解析到 Jenkins 的域名,因此你还需要在 GitLab 容器内加一行配置: - -```sh -docker exec -it gitlab bash -echo "44.33.22.11 jenkins.example.com" >> /etc/hosts -exit -``` - -#### 2.4.2、访问 GitLab - -你可以在自己的 PC 里配置 `44.33.22.11 gitlab.example.com` 静态域名解析记录,然后在浏览器里通过 `http://gitlab.example.com:30080` 访问到 GitLab: - -
- ![GitLab login](./gitlab-jenkins-harbor-java-springboot/gitlab-login.png){ width="1000" } -
GitLab login page
-
- -通过执行如下命令,你可以设置 GitLab 的 root 密码: - -```shell title="get GitLab root Password" -docker exec -it gitlab bash # 进入容器 -gitlab-rake "gitlab:password:reset" # 执行后按照提示输入用户名 root,回车后输入密码 -``` - -拿到 root 密码后,你可以尝试用 root/YOUR_PASSWORD 来登录 GitLab。因为后面你还需要用到 GitLab 的 token,所以这时候你可以顺手先创建一个 token: - -
- ![GitLab token](./gitlab-jenkins-harbor-java-springboot/gitlab-token.png){ width="1000" } -
Generate GitLab token
-
- -#### 2.4.3、访问 Jenkins - -前面你可能已经通过 `curl http://jenkins.example.com` 在主机内验证了 Jenkins 的网络连通性,想要远程通过域名访问 Jenkins,你还需要在自己的 PC 里配置 `44.33.22.11 jenkins.example.com` 静态域名解析记录。 - -接着在浏览器里通过 `http://jenkins.example.com` 就可以访问到 Jenkins 了: - -
- ![Jenkins login](./gitlab-jenkins-harbor-java-springboot/jenkins-login.png){ width="1000" } -
Jenkins login page
-
- -Jenkins 的 admin 用户初始登录密码是 `changeme`,如果你仔细看了前面 dtm 使用的配置文件,可以发现这是在配置文件里指定的。你可以尝试用 `admin/changeme` 登录 Jenkins 检查功能是否正常,不过当前你不需要在 Jenkins 上进行任何额外的操作。 - -
- ![Jenkins dashboard](./gitlab-jenkins-harbor-java-springboot/jenkins-dashboard.png){ width="1000" } -
Jenkins dashboard
-
- -#### 2.4.4、访问 Harbor - -前面你可能也已经通过 `curl http://harbor.example.com` 在主机内验证了 Harbor 的网络连通性,同样你可以通过 `docker login harbor.example.com:80` 命令来尝试登录 Harbor。 - -现在你需要在自己的 PC 里配置 `44.33.22.11 harbor.example.com` 静态域名解析记录。 - -接着你可以在浏览器里通过 `http://harbor.example.com` 访问到 Harbor: - -
- ![Harbor login](./gitlab-jenkins-harbor-java-springboot/harbor-login.png){ width="1000" } -
Harbor login page
-
- -Harbor 的 admin 用户初始登录密码是 `Harbor12345`,你可以尝试用 `admin/Harbor12345` 登录 Harbor 检查功能是否正常,不过当前你同样也不需要在 Harbor 上进行任何额外的操作。 - -
- ![Harbor dashboard](./gitlab-jenkins-harbor-java-springboot/harbor-dashboard.png){ width="1000" } -
Harbor dashboard
-
- -## 3、开始应用 apps - -本节你将继续使用 DevStream apps 管理能力实现一个 Java Spring Boot 项目的脚手架创建和 CI 流程配置等过程。 - -### 3.1、准备 apps 配置文件(config-apps.yaml) - -前面你已经掌握了“状态”和“变量”等的配置方式,结合 apps,你可以编写如下配置文件: - -```yaml title="config-apps.yaml" -config: - state: - backend: local - options: - stateFile: devstream-app.state -vars: - appName: myapp - gitlabURL: http://gitlab.example.com:30080 - jenkinsURL: http://jenkins.example.com - harborURL: http://harbor.example.com -apps: -- name: [[ appName ]] - spec: - language: java - framework: springboot - repo: - url: [[ gitlabURL ]]/root/[[ appName ]].git - branch: main - token: [[ env GITLAB_TOKEN ]] - repoTemplate: - url: https://github.com/devstream-io/dtm-repo-scaffolding-java-springboot.git - ci: - - type: template - templateName: ci-pipeline -pipelineTemplates: -- name: ci-pipeline - type: jenkins-pipeline - options: - branch: main - jenkins: - url: [[ jenkinsURL ]] - user: admin - enableRestart: true - password: [[ env JENKINS_PASSWORD ]] - imageRepo: - user: admin - url: [[ harborURL ]]/library - password: [[ env IMAGE_REPO_PASSWORD ]] -``` - -可以看到这里的状态配置换成了 devstream-app.state,这里需要保证和前面 tools 所使用的状态文件不是同一个。 - -### 3.2、让 apps 配置生效 - -你还记得刚才添加了一个 GitLab 的 token 不?这个 token 需要被设置到环境变量里: - -```shell title="环境变量配置" -export GITLAB_TOKEN=YOUR_GITLAB_TOKEN -``` - -同时如果你的 Harbor 没有去修改密码,这时候默认密码应该是 Harbor12345,你同样需要将 Harbor 密码配置到环境变量里: - -```shell title="环境变量配置" -export IMAGE_REPO_PASSWORD=Harbor12345 -``` - -此外由于 DevStream 需要调用 Jenkins 的 API 来帮你创建流水线,所以你还需要告诉 DevStream Jenkins 的密码: - -```shell title="环境变量配置" -export JENKINS_PASSWORD=changeme -``` - -你可以将这个配置文件放到服务器上同一个目录,比如 `~/devstream-test/`,然后在该目录下执行: - -```shell title="初始化" -dtm init -f config-apps.yaml -``` - -这时候 DevStream 会帮你下载 `jenkins-pipeline` 和 `repo-scaffolding` 两个插件,最终将有这两个插件来帮你完成代码库脚手架的创建和 Jenkins 流水线的配置。 - -接着你可以继续执行如下命令 - -```shell -dtm apply -f config-apps.yaml -y -``` - -如果 apply 命令执行成功的话,你可以看到大致如下日志: - -```shell title="执行日志" -2022-12-02 01:04:44 ℹ [INFO] Delete started. -2022-12-02 01:04:44 ℹ [INFO] Using local backend. State file: devstream-app.state. -2022-12-02 01:04:44 ℹ [INFO] Tool (jenkins-pipeline/myapp) will be deleted. -2022-12-02 01:04:44 ℹ [INFO] Tool (repo-scaffolding/myapp) will be deleted. -2022-12-02 01:04:44 ℹ [INFO] Start executing the plan. -2022-12-02 01:04:44 ℹ [INFO] Changes count: 2. -2022-12-02 01:04:44 ℹ [INFO] -------------------- [ Processing progress: 1/2. ] ---------- ----------- -2022-12-02 01:04:44 ℹ [INFO] Processing: (jenkins-pipeline/myapp) -> Delete ... -2022-12-02 01:04:46 ℹ [INFO] Prepare to delete 'jenkins-pipeline_myapp' from States. -2022-12-02 01:04:46 ✔ [SUCCESS] Tool (jenkins-pipeline/myapp) delete done. -2022-12-02 01:04:46 ℹ [INFO] -------------------- [ Processing progress: 2/2. ] -------------------- -2022-12-02 01:04:46 ℹ [INFO] Processing: (repo-scaffolding/myapp) -> Delete ... -2022-12-02 01:04:46 ℹ [INFO] Prepare to delete 'repo-scaffolding_myapp' from States. -2022-12-02 01:04:46 ✔ [SUCCESS] Tool (repo-scaffolding/myapp) delete done. -2022-12-02 01:04:46 ℹ [INFO] -------------------- [ Processing done. ] -------------------- -2022-12-02 01:04:46 ✔ [SUCCESS] All plugins deleted successfully. -2022-12-02 01:04:46 ✔ [SUCCESS] Delete finished. -root@dtm-realk8sdev:~# ./dtm apply -y -f config-apps.yaml -2022-12-02 01:04:55 ℹ [INFO] Apply started. -2022-12-02 01:04:55 ℹ [INFO] Using local backend. State file: devstream-app.state. -2022-12-02 01:04:55 ℹ [INFO] Tool (repo-scaffolding/myapp) found in config but doesn't exist in the state, will be created. -2022-12-02 01:04:55 ℹ [INFO] Tool (jenkins-pipeline/myapp) found in config but doesn't exist in the state, will be created. -2022-12-02 01:04:55 ℹ [INFO] Start executing the plan. -2022-12-02 01:04:55 ℹ [INFO] Changes count: 2. -2022-12-02 01:04:55 ℹ [INFO] -------------------- [ Processing progress: 1/2. ] -------------------- -2022-12-02 01:04:55 ℹ [INFO] Processing: (repo-scaffolding/myapp) -> Create ... -2022-12-02 01:04:55 ℹ [INFO] github start to download repoTemplate... -2022-12-02 01:04:56 ✔ [SUCCESS] Tool (repo-scaffolding/myapp) Create done. -2022-12-02 01:04:56 ℹ [INFO] -------------------- [ Processing progress: 2/2. ] -------------------- -2022-12-02 01:04:56 ℹ [INFO] Processing: (jenkins-pipeline/myapp) -> Create ... -2022-12-02 01:04:57 ℹ [INFO] jenkins plugin imageRepo start config... -2022-12-02 01:04:57 ⚠ [WARN] jenkins gitlab ssh key not config, private repo can't be clone -2022-12-02 01:04:57 ℹ [INFO] jenkins start config casc... -2022-12-02 01:04:59 ✔ [SUCCESS] Tool (jenkins-pipeline/myapp) Create done. -2022-12-02 01:04:59 ℹ [INFO] -------------------- [ Processing done. ] -------------------- -2022-12-02 01:04:59 ✔ [SUCCESS] All plugins applied successfully. -2022-12-02 01:04:59 ✔ [SUCCESS] Apply finished. -``` - -### 3.3、查看执行结果 - -这时候你可以在 GitLab 上看到 dtm 为你准备的 Java Spring Boot 项目脚手架: - -
- ![Repo Scaffolding](./gitlab-jenkins-harbor-java-springboot/repo-scaffolding.png){ width="1000" } -
Repo scaffolding
-
- -接着你可以登录 Jenkins,查看 dtm 为你创建的 Pipeline: - -
- ![Jenkins Pipeline](./gitlab-jenkins-harbor-java-springboot/jenkins-pipeline.png){ width="1000" } -
Jenkins pipeline
-
- -这个 Pipeline 会自动执行一次,执行完成后回到 GitLab,你可以看到 Jenkins 回写的 Pipeline 状态: - -
- ![GitLab Status](./gitlab-jenkins-harbor-java-springboot/gitlab-status.png){ width="1000" } -
GitLab Status
-
- -后面每当 GitLab 上这个 repo 发生 Push 或者 Merge 事件的时候,就会触发 Jenkins 上的 Pipeline 运行。 - -当然,在 Harbor 上你可以找到 CI 流程构建出来的容器镜像: - -
- ![GitLab Status](./gitlab-jenkins-harbor-java-springboot/harbor-image.png){ width="1000" } -
Image in Harbor
-
- -## 4、环境清理 - -你可以通过如下命令清理环境: - -```shell title="环境清理命令" -dtm delete -f config-apps.yaml -y -dtm delete -f config-tools.yaml -y -``` diff --git a/docs/use-cases/gitlab-jenkins-harbor/7-java-springboot-pipeline-with-gitlab-jenkins-harbor-air-gapped.md b/docs/use-cases/gitlab-jenkins-harbor/7-java-springboot-pipeline-with-gitlab-jenkins-harbor-air-gapped.md deleted file mode 100644 index 2901e3e53..000000000 --- a/docs/use-cases/gitlab-jenkins-harbor/7-java-springboot-pipeline-with-gitlab-jenkins-harbor-air-gapped.md +++ /dev/null @@ -1,3 +0,0 @@ -# to be translated - -// TODO(daniel-hutao): Chinese version first diff --git a/docs/use-cases/gitlab-jenkins-harbor/7-java-springboot-pipeline-with-gitlab-jenkins-harbor-air-gapped.zh.md b/docs/use-cases/gitlab-jenkins-harbor/7-java-springboot-pipeline-with-gitlab-jenkins-harbor-air-gapped.zh.md deleted file mode 100644 index baf00a27b..000000000 --- a/docs/use-cases/gitlab-jenkins-harbor/7-java-springboot-pipeline-with-gitlab-jenkins-harbor-air-gapped.zh.md +++ /dev/null @@ -1,280 +0,0 @@ -# 在离线环境搭建 GitLab + Jenkins + Harbor 工具链,管理 Java Spring Boot 项目开发生命周期全流程 - -在“[这个文档](./2-gitlab-jenkins-harbor.zh.md)”里我们介绍了怎样通过 DevStream 在本地部署 `GitLab + Jenkins + Harbor` 工具链,并且以 Java Spring Boot 项目为例,演示如何使用 DevStream 快速创建 Java Spring Boot 项目脚手架,同时在 Jenkins 上自动创建对应的 Pipeline 实现 Java Spring Boot 项目的 CI 流程。 - -本文将要演示的流水线最终效果与上面这个文档中介绍的几乎完全一致,唯一的区别就是本文假设你的服务器是离线的,你只有一台可以访问互联网的 PC,这台 PC 可以通过企业内部网络访问到你要使用 DevStream 的服务器,类似下图这样: - -
- ![GitLab token](./gitlab-jenkins-harbor-air-gapped/air-gapped-env.png){ width="500" } -
-
- -!!! info "提醒" - - 关于本文最终将要搭建的流水线及其工作效果,请查看“[这个文档](./6-gitlab-jenkins-harbor-java-springboot.zh.md)”。 - -## 1、下载 dtm 和 DevStream Plugins - -首先你需要下载 DevStream 的命令行(CLI)工具 `dtm` 和所需的 DevStream 插件(plugins)。 - -### 1.1、下载 dtm - -你可以参考[这个文档](../../install.zh.md)下载 dtm。 - -唯一需要注意的是,下载完之后,请记得将 dtm 传输到你需要使用它的机器上。 - -### 1.2、下载 plugins - -继续在你的 PC 上执行如下命令来下载 DevStream plugins: - -```shell -dtm init --download-only --plugins="gitlab-ce-docker, helm-installer, repo-scaffolding, jenkins-pipeline" -d=plugins -``` - -这条命令执行成功后,你可以在本地 plugins 目录下看到如下文件: - -```shell -$ ls plugins/ -gitlab-ce-docker-linux-amd64_0.10.2.md5 -helm-installer-linux-amd64_0.10.2.md5 -jenkins-pipeline-linux-amd64_0.10.2.md5 -repo-scaffolding-linux-amd64_0.10.2.md5 -gitlab-ce-docker-linux-amd64_0.10.2.so -helm-installer-linux-amd64_0.10.2.so -jenkins-pipeline-linux-amd64_0.10.2.so -repo-scaffolding-linux-amd64_0.10.2.so -``` - -## 2、下载镜像 - -因为 DevStream 需要使用容器化方式部署 GitLab、Jenkins 和 Harbor,那么在开始离线部署前,你需要先下载这几个工具对应的容器镜像。DevStream 提供了这几个工具对应的镜像列表,并且帮你准备了工具脚本从而更加容易地完成镜像离线工作: - -1. [GitLab CE images](../../plugins/gitlab-ce-docker/gitlab-ce-images.txt) -2. [Jenkins images](../../plugins/helm-installer/jenkins/jenkins-images.txt) -3. [Harbor images](../../plugins/helm-installer/harbor/harbor-images.txt) - -你可以通过如下命令将镜像列表下载到本地: - -```shell -curl -o jenkins-images.txt https://raw.githubusercontent.com/devstream-io/devstream/main/docs/plugins/helm-installer/jenkins/jenkins-images.txt -curl -o harbor-images.txt https://raw.githubusercontent.com/devstream-io/devstream/main/docs/plugins/helm-installer/harbor/harbor-images.txt -curl -o jenkins-images.txt https://raw.githubusercontent.com/devstream-io/devstream/main/docs/plugins/gitlab-ce-docker/gitlab-ce-images.txt -``` - -可以通过如下命令下载 DevStream 提供的工具脚本,这个脚本可以帮助你快速将这些镜像下载到本地并且上传到私有镜像仓库: - -```shell -curl -o image-pull-push.sh https://raw.githubusercontent.com/devstream-io/devstream/main/hack/image-pull-push.sh -chmod +x image-pull-push.sh -``` - -如果你还没有一个私有镜像仓库,可以参考[这篇文章](../reference/image-registry.zh.md)快速部署一个 Docker Registry。 - -接下来,你就可以通过下述命令快速完成镜像的下载和上传了: - -```shell -# 查看工具脚本的使用方法和注意事项等 -./image-pull-push.sh -h # (1) -# 设置镜像仓库地址,按需修改 -export IMAGE_REPO_ADDR=registry.devstream.io -# 下载 xxx-images.txt 中所有镜像并保存到本地压缩包中 -./image-pull-push.sh -f harbor-images.txt -r ${IMAGE_REPO_ADDR} -s -./image-pull-push.sh -f jenkins-images.txt -r ${IMAGE_REPO_ADDR} -s -./image-pull-push.sh -f gitlab-ce-images.txt -r ${IMAGE_REPO_ADDR} -s -# 从压缩包中 load 镜像并 push 到私有镜像仓库(如果镜像仓库需要登录,则需要先手动执行 docker login) -./image-pull-push.sh -f harbor-images.txt -r ${IMAGE_REPO_ADDR} -l -u -./image-pull-push.sh -f jenkins-images.txt -r ${IMAGE_REPO_ADDR} -l -u -./image-pull-push.sh -f gitlab-ce-images.txt -r ${IMAGE_REPO_ADDR} -l -u -``` - -1. 强烈建议你先看下本脚本的使用说明和示例 - -!!! note "注意" - - 如果你下载镜像的机器和内部私有镜像仓库之间网络隔离,那么你可以在镜像下载到本地压缩包后,先将该压缩包复制到能够访问镜像仓库的机器上,然后再执行 load 和 push 等操作。 - -## 3、下载 Helm Chart 包 - -你可以通过如下命令下载 Harbor 和 Jenkins 的 Helm chart 包: - -```shell -helm repo add harbor https://helm.goharbor.io -helm repo update -helm search repo harbor -l -helm pull harbor/harbor --version=1.10.0 -``` - -```shell -helm repo add jenkins https://charts.jenkins.io -helm repo update -helm search repo jenkins -l -helm pull jenkins/jenkins --version=4.2.5 -``` - -执行完上述命令后,你可以在本地看到如下文件: - -```shell -$ ls -harbor-1.10.0.tgz jenkins-4.2.5.tgz -``` - -## 4、准备配置文件 - -这时候需要联网下载的各种“物料”你就准备好了。接着你可以开始编写 DevStream 的配置文件了: - -```yaml title="DevStream Config" -config: - state: - backend: local - options: - stateFile: devstream.state -vars: - imageRepo: registry.devstream.io - gitlabHostname: gitlab.example.com - jenkinsHostname: jenkins.example.com - harborHostname: harbor.example.com - harborURL: http://harbor.example.com - jenkinsAdminUser: admin - jenkinsAdminPassword: changeme - gitlabSSHPort: 30022 - gitlabHttpPort: 30080 - gitlabHttpsPort: 30443 - -tools: -- name: gitlab-ce-docker - instanceID: default - dependsOn: [] - options: - hostname: [[ gitlabHostname ]] - gitlabHome: /srv/gitlab - sshPort: [[ gitlabSSHPort ]] - httpPort: [[ gitlabHttpPort ]] - httpsPort: [[ gitlabHttpsPort ]] - rmDataAfterDelete: false - imageTag: "rc" -- name: helm-installer - instanceID: jenkins-001 - dependsOn: [] - options: - chartPath: "./jenkins-4.2.5.tgz" - valuesYaml: | - serviceAccount: - create: true - name: jenkins - controller: - image: [[ imageRepo ]]/devstreamdev/jenkins - tag: 2.361.1-jdk11-dtm-0.1 - imagePullPolicy: "IfNotPresent" - sidecars: - configAutoReload: - image: [[ imageRepo ]]/kiwigrid/k8s-sidecar:1.15.0 - adminUser: [[ jenkinsAdminUser ]] - adminPassword: [[ jenkinsAdminPassword ]] - ingress: - enabled: true - hostName: [[ jenkinsHostname ]] - enableRawHtmlMarkupFormatter: true - JCasC: - defaultConfig: true -- name: helm-installer - instanceID: harbor-001 - dependsOn: [] - options: - chartPath: "./harbor-1.10.0.tgz" - valuesYaml: | - externalURL: [[ harborURL ]] - expose: - type: ingress - tls: - enabled: false - ingress: - hosts: - core: [[ harborHostname ]] - nginx: - image: - repository: [[ imageRepo ]]/goharbor/nginx-photon - tag: v2.5.3 - portal: - image: - repository: [[ imageRepo ]]/goharbor/harbor-portal - tag: v2.5.3 - core: - image: - repository: [[ imageRepo ]]/goharbor/harbor-core - tag: v2.5.3 - jobservice: - image: - repository: [[ imageRepo ]]/goharbor/harbor-jobservice - tag: v2.5.3 - registry: - registry: - image: - repository: [[ imageRepo ]]/goharbor/registry-photon - tag: v2.5.3 - controller: - image: - repository: [[ imageRepo ]]/goharbor/harbor-registryctl - tag: v2.5.3 - chartmuseum: - enabled: false - image: - repository: [[ imageRepo ]]/goharbor/chartmuseum-photon - tag: v2.5.3 - trivy: - enabled: false - image: - repository: [[ imageRepo ]]/goharbor/trivy-adapter-photon - tag: v2.5.3 - notary: - enabled: false - server: - image: - repository: [[ imageRepo ]]/goharbor/notary-server-photon - tag: v2.5.3 - signer: - image: - repository: [[ imageRepo ]]/goharbor/notary-signer-photon - tag: v2.5.3 - database: - internal: - image: - repository: [[ imageRepo ]]/goharbor/harbor-db - tag: v2.5.3 - redis: - internal: - image: - repository: [[ imageRepo ]]/goharbor/redis-photon - tag: v2.5.3 - exporter: - image: - repository: [[ imageRepo ]]/goharbor/harbor-exporter - tag: v2.5.3 - persistence: - persistentVolumeClaim: - registry: - storageClass: "" - accessMode: ReadWriteOnce - size: 5Gi - jobservice: - storageClass: "" - accessMode: ReadWriteOnce - size: 1Gi - database: - storageClass: "" - accessMode: ReadWriteOnce - size: 1Gi - redis: - storageClass: "" - accessMode: ReadWriteOnce - size: 1Gi -``` - -## 5、开始部署 - -现在你可以通过如下命令开始部署 Jenkins 和 Harbor 了: - -```shell -dtm apply -f config.yaml -y -``` - -剩下的步骤就联网部署没有什么区别了,你可以查看“[这个文档](./6-gitlab-jenkins-harbor-java-springboot.zh.md)”继续学习如何验证或清理这条工具链。 diff --git a/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-air-gapped/air-gapped-env.png b/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-air-gapped/air-gapped-env.png deleted file mode 100644 index 0810efbb9..000000000 Binary files a/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-air-gapped/air-gapped-env.png and /dev/null differ diff --git a/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-air-gapped/harbor-dashboard.png b/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-air-gapped/harbor-dashboard.png deleted file mode 100644 index 078fbdfe9..000000000 Binary files a/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-air-gapped/harbor-dashboard.png and /dev/null differ diff --git a/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-air-gapped/harbor-login.png b/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-air-gapped/harbor-login.png deleted file mode 100644 index b868e5a78..000000000 Binary files a/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-air-gapped/harbor-login.png and /dev/null differ diff --git a/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-air-gapped/jenkins-dashboard.png b/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-air-gapped/jenkins-dashboard.png deleted file mode 100644 index 842c6891f..000000000 Binary files a/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-air-gapped/jenkins-dashboard.png and /dev/null differ diff --git a/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-air-gapped/jenkins-login.png b/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-air-gapped/jenkins-login.png deleted file mode 100644 index dd0b4371a..000000000 Binary files a/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-air-gapped/jenkins-login.png and /dev/null differ diff --git a/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-java-springboot/gitlab-login.png b/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-java-springboot/gitlab-login.png deleted file mode 100644 index b27a36841..000000000 Binary files a/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-java-springboot/gitlab-login.png and /dev/null differ diff --git a/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-java-springboot/gitlab-status.png b/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-java-springboot/gitlab-status.png deleted file mode 100644 index 396504841..000000000 Binary files a/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-java-springboot/gitlab-status.png and /dev/null differ diff --git a/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-java-springboot/gitlab-token.png b/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-java-springboot/gitlab-token.png deleted file mode 100644 index 720ba9ae0..000000000 Binary files a/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-java-springboot/gitlab-token.png and /dev/null differ diff --git a/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-java-springboot/harbor-dashboard.png b/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-java-springboot/harbor-dashboard.png deleted file mode 100644 index 078fbdfe9..000000000 Binary files a/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-java-springboot/harbor-dashboard.png and /dev/null differ diff --git a/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-java-springboot/harbor-image.png b/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-java-springboot/harbor-image.png deleted file mode 100644 index 3ef21b2c6..000000000 Binary files a/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-java-springboot/harbor-image.png and /dev/null differ diff --git a/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-java-springboot/harbor-login.png b/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-java-springboot/harbor-login.png deleted file mode 100644 index c6e67ea0c..000000000 Binary files a/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-java-springboot/harbor-login.png and /dev/null differ diff --git a/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-java-springboot/jenkins-dashboard.png b/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-java-springboot/jenkins-dashboard.png deleted file mode 100644 index bd748649a..000000000 Binary files a/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-java-springboot/jenkins-dashboard.png and /dev/null differ diff --git a/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-java-springboot/jenkins-login.png b/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-java-springboot/jenkins-login.png deleted file mode 100644 index 5c5f1cbff..000000000 Binary files a/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-java-springboot/jenkins-login.png and /dev/null differ diff --git a/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-java-springboot/jenkins-pipeline.png b/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-java-springboot/jenkins-pipeline.png deleted file mode 100644 index 8235775de..000000000 Binary files a/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-java-springboot/jenkins-pipeline.png and /dev/null differ diff --git a/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-java-springboot/pipeline-success.png b/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-java-springboot/pipeline-success.png deleted file mode 100644 index d757f065d..000000000 Binary files a/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-java-springboot/pipeline-success.png and /dev/null differ diff --git a/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-java-springboot/repo-scaffolding.png b/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-java-springboot/repo-scaffolding.png deleted file mode 100644 index 367e89092..000000000 Binary files a/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-java-springboot/repo-scaffolding.png and /dev/null differ diff --git a/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-java-springboot/workflow.png b/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-java-springboot/workflow.png deleted file mode 100644 index c4d17db9d..000000000 Binary files a/docs/use-cases/gitlab-jenkins-harbor/gitlab-jenkins-harbor-java-springboot/workflow.png and /dev/null differ diff --git a/docs/use-cases/gitops-python-flask/1-overview.md b/docs/use-cases/gitops-python-flask/1-overview.md deleted file mode 100644 index 2f8adcdcf..000000000 --- a/docs/use-cases/gitops-python-flask/1-overview.md +++ /dev/null @@ -1,58 +0,0 @@ -# Overview - -## Is This for Me? - -- Are you creating a web application/backend API microservice? -- Are you using GitHub to store your source code? -- Are you eager to deploy your service fast and automatically in your development/test environment to have your work up and running in no time? - -If the answers to these questions are all "YES", the combo "GitHub Actions + Argo CD + Python + Flask" might just be the right tools for you. - -To be more specific, read on. - ---- - -## Is This Really for Me? - -This combo, "GitHub + GitHub Actions + Argo CD + Python + Flask" (could also be GitLab + GitLab CI), if installed/managed/integrated properly, might form the best DevOps platform for you, if: - -- You choose GitHub as your source code management (SCM) system. It can be either: - - GitHub cloud, or - - GitHub Enterprise (on-premise) -- Since you are using GitHub as SCM and CI interacts a lot with code repositories, using GitHub Actions as your choice of CI is convenient. - - You can, of course, choose other CI, such as Travis CI, CircleCI, etc., which require a little integration with GitHub; while using GitHub Actions, there is essentially little to no integration required. - - Plus, many users are already using it, so if you meet some issues while using it, chances are others have already met them, and you can easily find the solution based on a simple Google search. -- You want fast and automatic deployment in your development environment. - - GitOps might be the best choice for you because it's fast and automatic. - - You don't have many environments, which eliminates some tricky issues like version propagation you might have if you use GitOps with many environments. -- You want to build some backend API-typed applications. - - According to one popularity ranking, Python is now the most popular programming language in the world. Programming languages' popularity rises and falls over time, of course. But according to [TIOBE](https://www.tiobe.com/tiobe-index/), a Dutch software quality assurance company, which has been tracking the popularity of programming languages, notes that "for the first time in more than 20 years, we have a new leader of the pack: the Python programming language. The long-standing hegemony of Java and C is over." While Python might not be the perfect choice in every case, when you don't have all the necessary information and must make a guess, choosing the most popular language won't be the worst idea. - - Flask is a small and lightweight Python web framework that provides valuable tools, including a fast debugger, a built-in development server, and features that make creating web applications in Python easy. - ---- - -## The Big Picture (Literally) - -We want to build a DevOps platform around these tools, and what this platform can do could be complicated. - -A picture is worth a thousand words: - -![](../../images/gitops-workflow.png) - -If you still prefer words to pictures (I know some people do), here's the story we want to implement: - -- A repository is created automatically in your GitHub account, with generated code (a Python Flask app, with Dockerfile and helm chart and stuff). -- Continuous integration (CI) pipelines created, so that: - - When a pull request is created, a job will be triggered to run unit test. - - When a pull request is merged into the main branch, another job will be triggered to build the Docker image, push it to Dockerhub, and trigger the deployment process -- Argo CD is installed for continuous deployment (CD). -- CD is triggered in a GitOps fashion: when a pull request is merged into the main branch, it deploys the application into the Kubernetes cluster. - -OK, then let's just build that. - ---- - -## Prerequisites - -1. A working Kubernetes cluster, serving as the infrastructure to deploy tools needed for GitOps, and as well as your development environment. -2. A GitHub account & an access token for API access, and a Dockerhub account & an access token. diff --git a/docs/use-cases/gitops-python-flask/1-overview.zh.md b/docs/use-cases/gitops-python-flask/1-overview.zh.md deleted file mode 100644 index 75baebba9..000000000 --- a/docs/use-cases/gitops-python-flask/1-overview.zh.md +++ /dev/null @@ -1,58 +0,0 @@ -# 概览 - -## 这篇文章是为"我"准备的吗? - -- 你是否正准备创建一个 web 应用/后端 API 微服务? -- 你是否使用 GitHub 存储你的源代码? -- 你是否渴望快速而自动地在你的开发/测试环境中部署你的服务,一触即发? - -如果你回答"是",那么"GitHub Actions + Argo CD + Python + Flask" 工具集可解君愁。 - -请听我细细道来。 - ---- - -## 这篇文章真的是为"我"准备的吗? - -这个 "GitHub + GitHub Actions + Argo CD + Python + Flask" 的组合(也可以是 "GitLab + GitLab CI"),如果安装/管理/集成得当,可能会为你构建最佳的 DevOps 平台,如若: - -- 你选择 GitHub 作为你的源代码管理(SCM)系统。它可以是: - - GitHub cloud,或 - - GitHub Enterprise(本地) -- 由于你使用 GitHub 作为 SCM,而 CI 与代码仓库交互频繁,因此使用 GitHub Actions 作为你的 CI 选择是方便的。 - - 当然,你可以选择其他 CI,如 Travis CI、CircleCI 等,它们需要与 GitHub 进行一些额外集成操作;而使用 GitHub Actions,实际上几乎不需要集成。 - - 此外,因为用的人多了,你遇到的问题大概率别人已经遇到过,所以你可以轻松地通过简单的 Google 搜索找到解决方案。 -- 你希望在开发环境中快速且自动地部署你的服务。 - - GitOps 可能是你的最佳选择,因为它快速、自动化。 - - 你的环境不多,这可以避免一些棘手的问题,比如如果你使用 GitOps 部署到多个环境,你可能会遇到 Version propagation(多环境下的版本发布,例如将某个通过测试环境的版本发布到生产环境)的问题。 -- 你想构建一些后端 API 类型的应用。 - - 根据一份流行度排名,Python 现在是世界上最流行的编程语言。当然,编程语言的流行度会随时间而起伏。但根据 [TIOBE](https://www.tiobe.com/tiobe-index/),一家荷兰的软件质量保证公司,它一直在跟踪编程语言的流行度,指出“在过去 20 年中,我们第一次看到了一种新的领导者:Python 编程语言。Java 和 C 的长期霸权已经结束。”虽然 Python 可能不是每种情况下的最佳选择,但当你无法掌握全部的信息,且必须做出一个猜测时,选择最流行的语言也不是最糟糕的选择。 - - Flask 是一个小型且轻量级的 Python Web 框架,它提供了很多有用的工具,包括快速调试器、内置的开发服务器以及更多让 Python 创建 Web 应用程序变得简单的特性。 - ---- - -## 能做什么?怎么做? - -我们想要构建一个基于这些工具的 DevOps 平台,而这个平台可以做到非常复杂的事情: - -一个 "宏图" 胜过千言万语: - -![](../../images/gitops-workflow.png) - -如果你还是更喜欢文字而不是图片(我知道有些人是这样的),我便来讲讲这中间发生的故事(DevStream 为实现这一流程而帮你做的事情): - -- 在你的 GitHub 账户中创建一个仓库,并生成一些代码(一个 Python Flask 应用程序,包含 Dockerfile、helm chart 等)。 -- 创建持续集成(CI)流水线,于是: - - 当 pull request 被创建时,会自动触发一个任务,以运行单元测试。 - - 当 pull request 被合并到主分支时,另一个任务将被触发,来构建 Docker 镜像,并推送到 Dockerhub,再触发部署流程。 -- 安装 Argo CD 以进行持续部署(CD)。 -- 以 GitOps 的方式触发 CD:当 pull request 被合并到主分支时,它将应用程序部署到 Kubernetes 集群。 - -让我们"大展宏图"吧! - ---- - -## 建业之基(先决条件) - -1. 一个可用的 Kubernetes 集群,用于部署 GitOps 所需的工具,以及作为开发环境。 -2. 一个 GitHub 账户和一个用于 API 访问的访问令牌,以及一个 Dockerhub 账户和一个用于 API 访问的 access token。 diff --git a/docs/use-cases/gitops-python-flask/2-github-dtm-apps.md b/docs/use-cases/gitops-python-flask/2-github-dtm-apps.md deleted file mode 100644 index a4653c629..000000000 --- a/docs/use-cases/gitops-python-flask/2-github-dtm-apps.md +++ /dev/null @@ -1,72 +0,0 @@ -# GitHub + GitHub Actions with DTM Apps - -## ENV Vars - -The following environment variables are required for this to work: - -```bash -export GITHUB_TOKEN="YOUR_GITHUB_TOKEN_HERE" -export IMAGE_REPO_PASSWORD="YOUR_DOCKERHUB_TOKEN_HERE" -``` - ---- - -## Config File - -```yaml -config: - state: - backend: local - options: - stateFile: devstream.state - -vars: - GITHUB_USER: YOUR_GITHUB_USER - DOCKERHUB_USER: YOUR_DOCKERHUB_USER - -tools: -- name: helm-installer - instanceID: argocd - -apps: -- name: myapp1 - spec: - language: python - framework: django - repo: - url: github.com/[[ GITHUB_USER ]]/myapp1 - token: [[ env GITHUB_TOKEN ]] - repoTemplate: - url: github.com/devstream-io/dtm-repo-scaffolding-python-flask - ci: - - type: github-actions - options: - imageRepo: - user: [[ DOCKERHUB_USER ]] - password: [[ env IMAGE_REPO_PASSWORD ]] - cd: - - type: argocdapp -``` - -Update the "YOUR_GITHUB_USER" and "YOUR_DOCKERHUB_USER" in the above file accordingly. - ---- - -## Run - -First, initialize: - -```bash -# this downloads the required plugins, according to the config file, automatically. -dtm init -f config.yaml -``` - - - -Then we apply it by running: - -```bash -dtm apply -f config.yaml -y -``` - - diff --git a/docs/use-cases/gitops-python-flask/2-github-dtm-apps.zh.md b/docs/use-cases/gitops-python-flask/2-github-dtm-apps.zh.md deleted file mode 100644 index e75b66ade..000000000 --- a/docs/use-cases/gitops-python-flask/2-github-dtm-apps.zh.md +++ /dev/null @@ -1,72 +0,0 @@ -# 用 DTM Apps 实现基于 GitHub,Argo CD 和 GitHub Actions 的 CICD 流程 - -## 环境变量 - -你需要先设置如下环境变量: - -```bash -export GITHUB_TOKEN="YOUR_GITHUB_TOKEN_HERE" -export IMAGE_REPO_PASSWORD="YOUR_DOCKERHUB_TOKEN_HERE" -``` - ---- - -## 配置文件 - -```yaml -config: - state: - backend: local - options: - stateFile: devstream.state - -vars: - GITHUB_USER: YOUR_GITHUB_USER - DOCKERHUB_USER: YOUR_DOCKERHUB_USER - -tools: -- name: helm-installer - instanceID: argocd - -apps: -- name: myapp1 - spec: - language: python - framework: django - repo: - url: github.com/[[ GITHUB_USER ]]/myapp1 - token: [[ env GITHUB_TOKEN ]] - repoTemplate: - url: github.com/devstream-io/dtm-repo-scaffolding-python-flask - ci: - - type: github-actions - options: - imageRepo: - user: [[ DOCKERHUB_USER ]] - password: [[ env IMAGE_REPO_PASSWORD ]] - cd: - - type: argocdapp -``` - -你需要相应更新上述配置文件里的 "YOUR_GITHUB_USER" 和 "YOUR_DOCKERHUB_USER"。 - ---- - -## 运行 - -首先需要初始化: - -```bash -# this downloads the required plugins, according to the config file, automatically. -dtm init -f config.yaml -``` - - - -然后运行如下命令让配置生效: - -```bash -dtm apply -f config.yaml -y -``` - - diff --git a/docs/use-cases/gitops-python-flask/3-github-dtm-tools.md b/docs/use-cases/gitops-python-flask/3-github-dtm-tools.md deleted file mode 100644 index 974fc5798..000000000 --- a/docs/use-cases/gitops-python-flask/3-github-dtm-tools.md +++ /dev/null @@ -1,102 +0,0 @@ -# GitHub + GitHub Actions with DTM Tools - -DevStream has two abstractions: [Tools](../../core-concepts/tools.md) and [Apps](../../core-concepts/apps.md). - -[The previous use case](./2-github-dtm-apps.md) uses "Apps". We can also achieve the same result with "Tools", and here's how: - -## ENV Vars - -The following environment variables are required for this to work: - -```bash -export GITHUB_TOKEN="YOUR_GITHUB_TOKEN_HERE" -export IMAGE_REPO_PASSWORD="YOUR_DOCKERHUB_TOKEN_HERE" -``` - ---- - -## Config File - -```yaml -config: - state: - backend: local - options: - stateFile: devstream.state - -vars: - GITHUB_USER: YOUR_GITHUB_USER - DOCKERHUB_USER: YOUR_DOCKERHUB_USER - -tools: -- name: repo-scaffolding - instanceID: myapp1 - options: - destinationRepo: - owner: [[ GITHUB_USER ]] - name: myapp1 - branch: main - scmType: github - token: [[ env GITHUB_TOKEN ]] - sourceRepo: - org: devstream-io - name: dtm-repo-scaffolding-python-flask - scmType: github -- name: github-actions - instanceID: flask - dependsOn: [ repo-scaffolding.myapp1 ] - options: - scm: - owner: [[ GITHUB_USER ]] - name: myapp1 - scmType: github - token: [[ env GITHUB_TOKEN ]] - pipeline: - configLocation: https://raw.githubusercontent.com/devstream-io/dtm-pipeline-templates/main/github-actions/workflows/main.yml - language: - name: python - framework: flask - imageRepo: - user: [[ DOCKERHUB_USER ]] - password: [[ env IMAGE_REPO_PASSWORD ]] -- name: helm-installer - instanceID: argocd -- name: argocdapp - instanceID: default - dependsOn: [ "helm-installer.argocd", "github-actions.flask" ] - options: - app: - name: myapp1 - namespace: argocd - destination: - server: https://kubernetes.default.svc - namespace: default - source: - valuefile: values.yaml - path: helm/myapp1 - repoURL: ${{repo-scaffolding.myapp1.outputs.repoURL}} - token: [[ env GITHUB_TOKEN ]] - imageRepo: - user: [[ DOCKERHUB_USER ]] -``` - -Update the "YOUR_GITHUB_USER" and "YOUR_DOCKERHUB_USER" in the above file accordingly. - ---- - -## Run - -First, initialize: - -```bash -# this downloads the required plugins, according to the config file, automatically. -dtm init -f config.yaml -``` - -Then we apply it by running: - -```bash -dtm apply -f config.yaml -y -``` - -(Screenshot/video omitted.) diff --git a/docs/use-cases/gitops-python-flask/3-github-dtm-tools.zh.md b/docs/use-cases/gitops-python-flask/3-github-dtm-tools.zh.md deleted file mode 100644 index e912b1b6e..000000000 --- a/docs/use-cases/gitops-python-flask/3-github-dtm-tools.zh.md +++ /dev/null @@ -1,104 +0,0 @@ -# 用 DTM Tools 实现基于 GitHub,Argo CD 和 GitHub Actions 的 CICD 流程 - -DevStream 抽象了2个概念:[Tools](../../core-concepts/tools.md) 和 [Apps](../../core-concepts/apps.md)。 - -在[前一个用户场景](./2-github-dtm-apps.md) 里介绍了 "Apps",你可以用 "Tools" 实现一样的效果。具体方法如下: - -## 环境变量 - -你需要先设置如下环境变量: - -```bash -export GITHUB_TOKEN="YOUR_GITHUB_TOKEN_HERE" -export IMAGE_REPO_PASSWORD="YOUR_DOCKERHUB_TOKEN_HERE" -``` - ---- - -## 配置文件 - -```yaml -config: - state: - backend: local - options: - stateFile: devstream.state - -vars: - GITHUB_USER: YOUR_GITHUB_USER - DOCKERHUB_USER: YOUR_DOCKERHUB_USER - -tools: -- name: repo-scaffolding - instanceID: myapp1 - options: - destinationRepo: - owner: [[ GITHUB_USER ]] - name: myapp1 - branch: main - scmType: github - token: [[ env GITHUB_TOKEN ]] - sourceRepo: - org: devstream-io - name: dtm-repo-scaffolding-python-flask - scmType: github -- name: github-actions - instanceID: flask - dependsOn: [ repo-scaffolding.myapp1 ] - options: - scm: - owner: [[ GITHUB_USER ]] - name: myapp1 - scmType: github - token: [[ env GITHUB_TOKEN ]] - pipeline: - configLocation: https://raw.githubusercontent.com/devstream-io/dtm-pipeline-templates/main/github-actions/workflows/main.yml - language: - name: python - framework: flask - imageRepo: - user: [[ DOCKERHUB_USER ]] - password: [[ env IMAGE_REPO_PASSWORD ]] -- name: helm-installer - instanceID: argocd -- name: argocdapp - instanceID: default - dependsOn: [ "helm-installer.argocd", "github-actions.flask" ] - options: - app: - name: myapp1 - namespace: argocd - destination: - server: https://kubernetes.default.svc - namespace: default - source: - valuefile: values.yaml - path: helm/myapp1 - repoURL: ${{repo-scaffolding.myapp1.outputs.repoURL}} - token: [[ env GITHUB_TOKEN ]] - imageRepo: - user: [[ DOCKERHUB_USER ]] -``` - -你需要相应更新上述配置文件里的 "YOUR_GITHUB_USER" 和 "YOUR_DOCKERHUB_USER"。 - ---- - -## 运行 - -首先需要初始化: - -```bash -# this downloads the required plugins, according to the config file, automatically. -dtm init -f config.yaml -``` - - - -然后运行如下命令让配置生效: - -```bash -dtm apply -f config.yaml -y -``` - -(省略了动图和视频等) diff --git a/docs/use-cases/gitops-python-flask/4-gitlab-dtm-apps.md b/docs/use-cases/gitops-python-flask/4-gitlab-dtm-apps.md deleted file mode 100644 index cec569977..000000000 --- a/docs/use-cases/gitops-python-flask/4-gitlab-dtm-apps.md +++ /dev/null @@ -1,318 +0,0 @@ -# GitLab + GitLab CI with DTM Apps - -## 0 Goal - -In this tutorial, we will try to achieve the following goals with DevStream: - -1. Use Docker to install GitLab as a code warehouse (if GitLab is already installed on your server, you can skip this step); -2. Create a Python web application repository on GitLab, based on the [Flask](https://flask.palletsprojects.com/en/2.2.x/) framework; -3. Use GitHub CI to set up a basic CI pipeline for the warehouse we created; -4. Install [Argo CD](https://argo-cd.readthedocs.io/en/stable/) in _an existing Kubernetes cluster_ to implement GitOps; -5. Create an Argo CD application to deploy the web application generated in step 1. - -> prerequisites: -> -> - [Docker](https://www.docker.com/) (GitLab uses Docker for installation) -> - Kubernetes cluster (Argo CD is installed in the Kubernetes cluster) - -> If you want to follow along with this tutorial and try it yourself, but don't know how to get a Kubernetes cluster up and running locally, the following blog (also from DevStream) might help: -> -> - [Creating a Local Kubernetes Cluster from the Groud Up - a Tutorial of "Kind"](https://blog.devstream.io/posts/creating-a-local-k8s-cluster-with-kind/) -> - [Getting Started with minikube](https://blog.devstream.io/posts/getting-started-with-minikube/) - ---- - -## 1 Overview - -DevStream will use the following plugins to achieve the goals described in [Section 0](#): - -1. [gitlab-ce-docker](../../plugins/gitlab-ce-docker.md): used to install GitLab in Docker; -2. [repo-scaffolding](../../plugins/repo-scaffolding.md): used to create a Python web application repository on GitLab; -3. [gitlab-ci](../../plugins/gitlab-ci.md): used to set up the basic CI pipeline for the warehouse we created; -4. [helm-installer](../../plugins/helm-installer/helm-installer.md): used to install Argo CD in a Kubernetes cluster; -5. [argocdapp](../../plugins/argocdapp.md): Used to create an Argo CD application to deploy the web application generated in step 1. - -We will accomplish these goals in two steps: - -1. Write a configuration file to complete the installation of tools, GitLab and Argo CD; -2. Write a configuration file to complete the creation of subsequent pipelines and code warehouses, and deploy them to Argo CD. - -> Note: In fact, the installation and configuration of DevOps tools can be completed in the same configuration file, but GitLab is special, requiring the user to manually create a token after the installation is complete, so we split the tool installation separately. - ---- - -## 2 Getting Started: Download DevStream (`dtm`) - -Create a test directory for this tutorial: - -```bash -mkdir test -cd test/ -``` - -In the newly created directory, execute: - -```shell -sh -c "$(curl -fsSL https://download.devstream.io/download.sh) -``` - -This script will download the corresponding `dtm` binary file according to your operating system and save it to the current directory. Then, give it executable permissions. - -> Optional: You can move `dtm` to a directory in your $PATH environment variable. For example: `mv dtm /usr/local/bin/`. In this way, you can run `dtm` directly without prefixing it with `./`. -> -> For more installation methods, see [Install dtm](../../install.md). - -## 2 Install GitLab And Argo CD - -### 2.1 Prepare Config - -Create file `config-tools.yaml`. Modify `vars` to your values: - -```yaml title="config-tools.yaml" -config: - state: - backend: local - options: - stateFile: devstream-1.state -vars: - gitlabHostname: gitlab.example.com - gitlabSSHPort: 30022 - gitlabHttpPort: 80 - gitlabHttpsPort: 30443 -tools: - - name: gitlab-ce-docker - instanceID: default - dependsOn: [] - options: - hostname: [[ gitlabHostname ]] - gitlabHome: /srv/gitlab - sshPort: [[ gitlabSSHPort ]] - httpPort: [[ gitlabHttpPort ]] - httpsPort: [[ gitlabHttpsPort ]] - rmDataAfterDelete: false - imageTag: "rc" - - name: helm-installer - instanceID: argocd -``` - -And modify the `/etc/hosts` file of the server to add the domain name resolution of `gitlab.example.com`. If your server ip is 44.33.22.11, you can configure it like this: - -```text title="/etc/hosts" -44.33.22.11 gitlab.example.com -``` - -### 2.2 Init - -Run the following command to download the plugins required to install GitLab and Argo CD: - -```shell -dtm init -f config-tools.yaml -y -``` - -### 2.3 Apply - -Run the following command to install GitLab and Argo CD: - -```shell -dtm apply -f config-tools.yaml -y -``` - -You'll see similar outputs to: - - - -### 2.4 Check Installation Results - -#### 2.4.1 Access GitLab - -You can configure the `44.33.22.11 gitlab.example.com` static domain name resolution record in your PC, and then access GitLab through `http://gitlab.example.com` in the browser (if the browser reports: - -
- ![GitLab Login](./gitlab-apps/gitlab-login.png){ width="1000" } -
GitLab Login
-
- -Run the following command to get GitLab's root password: - -```shell title="get GitLab root Password" -docker exec -it gitlab bash -gitlab-rake "gitlab:password:reset" -``` - - - -Login with root/YOUR_PASSWORD. We will use GitLab token later, so let's create one now: - -
- ![GitLab token](./gitlab-apps/gitlab-token.png){ width="1000" } -
Generate GitLab token
-
- -#### 2.4.2 Check Argo CD - -We can see that Argo CD is installed into the `argocd` namespace of the Kubernetes cluster: - -```bash -[root@ip-10-18-13-200 devstream]# kubectl get ns -NAME STATUS AGE -argocd Active 36s -default Active 6d4h -kube-node-lease Active 6d4h -kube-public Active 6d4h -kube-system Active 6d4h -[root@ip-10-18-13-200 devstream]# kubectl get pods -n argocd -NAME READY STATUS RESTARTS AGE -argocd-application-controller-0 1/1 Running 0 49s -argocd-applicationset-controller-7f4577c5fd-8z926 1/1 Running 0 49s -argocd-dex-server-7cdb45c7c9-nspgz 1/1 Running 0 49s -argocd-notifications-controller-65b77fb646-phdwh 1/1 Running 0 49s -argocd-redis-577c6c8f5c-nf5xm 1/1 Running 0 49s -argocd-repo-server-7bd9fd899c-7f6cp 1/1 Running 0 49s -argocd-server-6686bbcf68-fms5w 1/1 Running 0 49s -``` - ---- - -## 3 Create and Deploy the App - -### 3.1 Prepare Config - -Create file `config-apps.yaml`. Modify `vars` to your values (pay extra attention to `dockerhubUser`): - -```yaml title="config-apps.yaml" -config: - state: - backend: local - options: - stateFile: devstream-2.state -vars: - appName: myapp - gitlabURL: http://gitlab.example.com - defaultBranch: main - dockerhubUser: DOCKERHUB_USER -apps: - - name: [[ appName ]] - spec: - language: python - framework: flask - repo: - url: [[ gitlabURL ]]/root/[[ appName ]].git - branch: [[ defaultBranch ]] - token: [[ env GITLAB_TOKEN ]] # use "GITLAB_TOKEN" env var - repoTemplate: - url: https://github.com/devstream-io/dtm-repo-scaffolding-python-flask.git - ci: - - type: template - templateName: ci-pipeline - cd: - - type: argocdapp -pipelineTemplates: - - name: ci-pipeline - type: gitlab-ci - options: - imageRepo: - user: [[ dockerhubUser ]] - password: [[ env DOCKERHUB_TOKEN ]] # use "DOCKERHUB_TOKEN" env var -``` - -You may have noticed that the above configuration has something like `[[ env XXX ]]`, which means that we reference the "XXX" environment variable to fill the configuration. So we also need to set the following two environment variables: - -```bash -export GITLAB_TOKEN="YOUR_GITLAB_TOKEN_HERE" -export DOCKERHUB_TOKEN="YOUR_DOCKERHUB_TOKEN_HERE" -``` - -> Note: -> -> If you don't know how to create a DockerHub token, you can refer to: [Manage access tokens](https://docs.docker.com/docker-hub/access-tokens/) - -### 3.2 Init - -Similarly, we need to download the required plugins from the config file. Run: - -```bash -dtm init -f config-apps.yaml -``` - -### 3.3 Apply - -Run: - -```bash -dtm apply -f config-apps.yaml -y -``` - -And you'll get similar outputs to: - - - -### 3.4 Check Results - -#### 3.4.1 Check the Repo Created in GitLab - -
- ![Flask repo](./gitlab-apps/repo-scaffolding.png){ width="1000" } -
Flask repo
-
- -#### 3.4.2 GitLab CI Workflow - -Access `http://gitlab.example.com` in your browser, and click `CI/CD`, then `Pipelines`: - -
- ![GitLab CI Overview](./gitlab-apps/gitlabci-1.png){ width="1000" } -
GitLab CI Overview
-
- -#### 3.4.3 ArgoCD-Based Continuous Deployment - -The CI workflow has already built a Docker image and pushed it to Dockerhub and Argo CD created by DevStream has deployed it: - -```bash -[root@ip-10-18-13-200 devstream]# kubectl get deployment -n default -NAME READY UP-TO-DATE AVAILABLE AGE -myapp 1/1 1 1 101s -[root@ip-10-18-13-200 devstream]# kubectl get pods -n default -NAME READY STATUS RESTARTS AGE -myapp-b65774f56-8cmjc 1/1 Running 0 106s -[root@ip-10-18-13-200 devstream]# kubectl get services -n default -NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE -kubernetes ClusterIP 10.96.0.1 443/TCP 12d -myapp ClusterIP 10.101.148.66 8080/TCP 110s -``` - -We can access this application through port forwarding: - -```bash -kubectl port-forward -n default svc/myapp 8080:8080 -``` - -Visit `localhost:8080` in your browser, and you can see that the application returns a "Hello, World!". You're done! - ---- - -## 4 Clean-Up - -### 4.1 Delete the Web App - -Run - -```bash -dtm delete -f config-apps.yaml -y -``` - -### 4.2 Delete GitLab and Argo CD - -Run - -```bash -dtm delete -f config-tools.yaml -y -``` - -### 4.3 Delete Other Files - -```bash -cd ../ -rm -rf test/ -rm -rf ~/.devstream/ -``` diff --git a/docs/use-cases/gitops-python-flask/4-gitlab-dtm-apps.zh.md b/docs/use-cases/gitops-python-flask/4-gitlab-dtm-apps.zh.md deleted file mode 100644 index 93faad53f..000000000 --- a/docs/use-cases/gitops-python-flask/4-gitlab-dtm-apps.zh.md +++ /dev/null @@ -1,314 +0,0 @@ -# 用 DevStream 搭建 Gitlab CI + Argo CD 工具链,管理 Python Flask 项目 - -## 0 目标 - -在本教程中,我们将尝试通过 DevStream 来实现以下目标: - -1. 使用 Docker 安装 GitLab,作为代码仓库(如果你的服务器上已经安装了 GitLab,可以跳过这一步); -2. 在 GitLab 上创建一个 Python Web 应用程序仓库,基于 [Flask](https://flask.palletsprojects.com/en/2.2.x/) 框架; -3. 使用 GitHub CI 为我们创建的仓库设置基本的 CI 流水线; -4. 在 _一个已有的 Kubernetes 集群_ 中安装 [Argo CD](https://argo-cd.readthedocs.io/en/stable/) 以实现 GitOps; -5. 创建一个 Argo CD 应用程序,用于部署第 1 步中生成的 Web 应用程序。 - -> 先决条件: -> -> - [Docker](https://www.docker.com/) (GitLab 使用 Docker 来安装) -> - Kubernetes 集群(Argo CD 安装在 Kubernetes 集群中) - -> 如果你想跟着本教程自己尝试一下,但不知道如何在本地启动和运行 Kubernetes 集群,下面的博客(也来自 DevStream)可能会有所帮助: -> -> - [用 Kind 从零开始快速搭建本地 Kubernetes 测试环节](https://blog.devstream.io/posts/%E7%94%A8kind%E9%83%A8%E7%BD%B2k8s%E7%8E%AF%E5%A2%83/) -> - [minikube结合阿里云镜像搭建本地开发测试环境](https://blog.devstream.io/posts/%E4%BD%BF%E7%94%A8minikube%E5%92%8C%E9%98%BF%E9%87%8C%E4%BA%91%E9%95%9C%E5%83%8F%E5%AE%89%E8%A3%85k8s/) - ---- - -## 1 概览 - -DevStream 将使用下面的插件来实现[第 0 节](#)中描述的目标: - -1. [gitlab-ce-docker](../../plugins/gitlab-ce-docker.md):用于在 Docker 中安装 GitLab; -2. [repo-scaffolding](../../plugins/repo-scaffolding.md): 用于在 GitLab 上创建一个 Python Web 应用程序仓库; -3. [gitlab-ci](../../plugins/gitlab-ci.md):用于为我们创建的仓库设置基本的 CI 流水线; -4. [helm-installer](../../plugins/helm-installer/helm-installer.md): 用于在 Kubernetes 集群中安装 Argo CD; -5. [argocdapp](../../plugins/argocdapp.md): 用于创建一个 Argo CD 应用程序,来部署第 1 步中生成的 Web 应用程序。 - -我们将分成两个步骤来完成这些目标: - -1. 编写一个配置文件,完成工具的安装,GitLab 和 Argo CD; -2. 编写一个配置文件,完成后续流水线的创建、代码仓库的创建,并将其部署到 Argo CD 中。 - -> 说明:实际上,DevOps 工具的安装和配置可以在同一个配置文件中完成,但 GitLab 较为特殊,需要在安装完成之后由用户手动创建 token,因此我们将工具的安装单独拆分出来了。 - -## 2 启程:下载 DevStream (`dtm`) - -为本教程创建一个临时工作目录: - -```bash -mkdir test -cd test/ -``` - -接着,在新创建的目录下,运行下面的命令: - -```shell -sh -c "$(curl -fsSL https://download.devstream.io/download.sh)" -``` - -这个脚本会根据你的操作系统来下载对应的 `dtm` 二进制文件,保存到当前目录。然后,赋予其可执行权限。 - -> 可选:你可以把 `dtm` 移动到 $PATH 环境变量中的某个目录下。例如:`mv dtm /usr/local/bin/`。这样,你就可以直接运行 `dtm` 而不需要再加上 `./` 前缀了。 -> -> 更多安装方式详见[安装 dtm](../../install.zh.md)。 - -## 2 安装 GitLab 和 Argo CD - -### 2.1 配置准备 - -创建 `config-tools.yaml` 文件,你可以修改 `vars` 中的值来适应你的环境: - -```yaml title="config-tools.yaml" -config: - state: - backend: local - options: - stateFile: devstream-1.state -vars: - gitlabHostname: gitlab.example.com - gitlabSSHPort: 30022 - gitlabHttpPort: 80 - gitlabHttpsPort: 30443 -tools: - - name: gitlab-ce-docker - instanceID: default - dependsOn: [] - options: - hostname: [[ gitlabHostname ]] - gitlabHome: /srv/gitlab - sshPort: [[ gitlabSSHPort ]] - httpPort: [[ gitlabHttpPort ]] - httpsPort: [[ gitlabHttpsPort ]] - rmDataAfterDelete: false - imageTag: "rc" - - name: helm-installer - instanceID: argocd -``` - -并修改服务器的 `/etc/hosts` 文件,添加 `gitlab.example.com` 的域名解析。如果你的服务器 ip 是 44.33.22.11,就可以这样配置: - -```text title="/etc/hosts" -44.33.22.11 gitlab.example.com -``` - -### 2.2 初始化(Init) - -运行下面的命令来下载安装 GitLab 和 Argo CD 所需的插件: - -```shell -dtm init -f config-tools.yaml -y -``` - -### 2.3 应用(Apply) - -运行下面的命令来通过配置文件来安装 GitLab 和 Argo CD: - -```shell -dtm apply -f config-tools.yaml -y -``` - -你会看到类似于下面的输出: - - - -### 2.4 检查安装结果 - -#### 2.4.1 访问 GitLab - -你可以在自己的 PC 里配置 `44.33.22.11 gitlab.example.com` 静态域名解析记录,然后在浏览器里通过 `http://gitlab.example.com` 访问到 GitLab(如果浏览器报了: - -
- ![GitLab 登录界面](./gitlab-apps/gitlab-login.png){ width="1000" } -
GitLab 登录界面
-
- -通过执行如下命令,你可以设置 GitLab 的 root 密码: - -```shell title="get GitLab root Password" -docker exec -it gitlab bash # 进入容器 -gitlab-rake "gitlab:password:reset" # 执行后按照提示输入用户名 root,回车后输入密码 -``` - - - -拿到 root 密码后,你可以尝试用 root/YOUR_PASSWORD 来登录 GitLab。因为后面你还需要用到 GitLab 的 token,所以这时候你可以顺手先创建一个 token: - -
- ![GitLab token](./gitlab-apps/gitlab-token.png){ width="1000" } -
Generate GitLab token
-
- -#### 2.4.2 查看 Argo CD - -可以看到 Argo CD 已经被安装到了 Kubernetes 的 `argocd` 命名空间中: - -```bash -[root@ip-10-18-13-200 devstream]# kubectl get ns -NAME STATUS AGE -argocd Active 36s -default Active 6d4h -kube-node-lease Active 6d4h -kube-public Active 6d4h -kube-system Active 6d4h -[root@ip-10-18-13-200 devstream]# kubectl get pods -n argocd -NAME READY STATUS RESTARTS AGE -argocd-application-controller-0 1/1 Running 0 49s -argocd-applicationset-controller-7f4577c5fd-8z926 1/1 Running 0 49s -argocd-dex-server-7cdb45c7c9-nspgz 1/1 Running 0 49s -argocd-notifications-controller-65b77fb646-phdwh 1/1 Running 0 49s -argocd-redis-577c6c8f5c-nf5xm 1/1 Running 0 49s -argocd-repo-server-7bd9fd899c-7f6cp 1/1 Running 0 49s -argocd-server-6686bbcf68-fms5w 1/1 Running 0 49s -``` - -## 3 创建、部署应用程序 - -### 3.1 配置准备 - -创建 `config-apps.yaml` 文件,你可以修改 `vars` 中的值来适应你的环境(尤其是`dockerhubUser`这个配置): - -```yaml title="config-apps.yaml" -config: - state: - backend: local - options: - stateFile: devstream-2.state -vars: - appName: myapp - gitlabURL: http://gitlab.example.com - defaultBranch: main - dockerhubUser: DOCKERHUB_USER -apps: - - name: [[ appName ]] - spec: - language: python - framework: flask - repo: - url: [[ gitlabURL ]]/root/[[ appName ]].git - branch: [[ defaultBranch ]] - token: [[ env GITLAB_TOKEN ]] # use "GITLAB_TOKEN" env var - repoTemplate: - url: https://github.com/devstream-io/dtm-repo-scaffolding-python-flask.git - ci: - - type: template - templateName: ci-pipeline - cd: - - type: argocdapp -pipelineTemplates: - - name: ci-pipeline - type: gitlab-ci - options: - runner: - enable: true - imageRepo: - user: [[ dockerhubUser ]] - password: [[ env DOCKERHUB_TOKEN ]] # use "DOCKERHUB_TOKEN" env var -``` - -你可能已经注意到了,上面的配置中有形如 `[[ env XXX ]]` 的内容,这表示我们引用了 "XXX" 环境变量来填充配置。所以我们还需要设置如下两个环境变量: - -```bash -export GITLAB_TOKEN="YOUR_GITLAB_TOKEN_HERE" -export DOCKERHUB_TOKEN="YOUR_DOCKERHUB_TOKEN_HERE" -``` - -> 提示: -> -> 如果你不知道如何创建 DockerHub 的 token,可以参考:[Manage access tokens](https://docs.docker.com/docker-hub/access-tokens/) - -### 3.2 初始化(Init) - -同样地,我们需要下载第二个配置文件中所需的插件,运行: - -```bash -dtm init -f config-apps.yaml -``` - -### 3.3 应用(Apply) - -运行: - -```bash -dtm apply -f config-apps.yaml -y -``` - -你会看到类似下面的输出: - - - -### 3.4 查看结果 - -#### 3.4.1 查看 在 GitLab 上创建的 Flask 仓库 - -
- ![Flask 仓库](./gitlab-apps/repo-scaffolding.png){ width="1000" } -
Flask 仓库
-
- -#### 3.4.2 基于 GitLab CI 的 CI 流水线 - -通过浏览器访问 `http://gitlab.example.com`,依次点击 `CI/CD`、`Pipelines`: - -
- ![GitLab CI 概览](./gitlab-apps/gitlabci-1.png){ width="1000" } -
GitLab CI 概览
-
- -#### 3.4.3 基于 Argo CD 的持续交付/部署 - -CI 流水线已经构建了一个 Docker 镜像并推送到了 Dockerhub,而 DevStream 创建的 Argo CD 应用也部署了这个应用: - -```bash -[root@ip-10-18-13-200 devstream]# kubectl get deployment -n default -NAME READY UP-TO-DATE AVAILABLE AGE -myapp 1/1 1 1 101s -[root@ip-10-18-13-200 devstream]# kubectl get pods -n default -NAME READY STATUS RESTARTS AGE -myapp-b65774f56-8cmjc 1/1 Running 0 106s -[root@ip-10-18-13-200 devstream]# kubectl get services -n default -NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE -kubernetes ClusterIP 10.96.0.1 443/TCP 12d -myapp ClusterIP 10.101.148.66 8080/TCP 110s -``` - -我们可以通过端口转发来访问这个应用: - -```bash -kubectl port-forward -n default svc/myapp 8080:8080 -``` - -在浏览器中访问 `localhost:8080`,你可以看到应用返回了一个 "Hello, World!"。大功告成! - -## 4 清理 - -### 4.1 删除 Web 应用 - -运行: - -```bash -dtm delete -f config-apps.yaml -y -``` - -### 4.2 删除 GitLab 和 Argo CD - -运行: - -```bash -dtm delete -f config-tools.yaml -y -``` - -### 4.3 删除其他文件 - -```bash -cd ../ -rm -rf test/ -rm -rf ~/.devstream/ -``` diff --git a/docs/use-cases/gitops-python-flask/5-gitlab-dtm-tools.md b/docs/use-cases/gitops-python-flask/5-gitlab-dtm-tools.md deleted file mode 100644 index bd74dfe69..000000000 --- a/docs/use-cases/gitops-python-flask/5-gitlab-dtm-tools.md +++ /dev/null @@ -1,129 +0,0 @@ -# GitLab + GitLab CI with DTM Tools - -DevStream has two abstractions: [Tools](../../core-concepts/tools.md) and [Apps](../../core-concepts/apps.md). - -[The previous use case](./4-gitlab-dtm-apps.md) uses `Apps`. We can also achieve the same result with `Tools`, and here's how: - -## ENV Vars - -The following environment variables are required for this to work: - -```bash -export GITLAB_TOKEN="YOUR_GITLAB_TOKEN" -export IMAGE_REPO_PASSWORD="YOUR_DOCKERHUB_TOKEN_HERE" -``` - ---- - -## Config File - -```yaml -config: - state: - backend: local - options: - stateFile: devstream.state - -vars: - gitlabUser: YOUR_GITLAB_USERNAME - dockerUser: YOUR_DOCKERHUB_USERNAME - app: testapp - -tools: -- name: helm-installer - instanceID: argocd -- name: repo-scaffolding - instanceID: myapp - options: - destinationRepo: - owner: [[ gitlabUser ]] - name: [[ app ]] - branch: master - scmType: gitlab - # set env GITLAB_TOKEN - token: [[ env GITLAB_TOKEN ]] - sourceRepo: - org: devstream-io - name: dtm-repo-scaffolding-python-flask - scmType: github -- name: gitlab-ci - instanceID: flask - dependsOn: [ "repo-scaffolding.myapp" ] - options: - scm: - owner: [[ gitlabUser ]] - name: [[ app ]] - branch: master - scmType: gitlab - token: [[ env GITLAB_TOKEN ]] - pipeline: - language: - framework: flask - name: python - imageRepo: - user: [[ dockerUser ]] - # set env IMAGE_REPO_PASSWORD - password: [[ env IMAGE_REPO_PASSWORD ]] -- name: argocdapp - instanceID: default - dependsOn: [ "helm-installer.argocd", "gitlab-ci.flask" ] - options: - app: - name: [[ app ]] - namespace: argocd - destination: - server: https://kubernetes.default.svc - namespace: default - source: - valuefile: values.yaml - path: helm/[[ app ]] - repoURL: ${{repo-scaffolding.myapp.outputs.repoURL}} - token: [[ env GITLAB_TOKEN ]] - imageRepo: - user: [[ dockerUser ]] - password: [[ env IMAGE_REPO_PASSWORD ]] -``` - -Update the "YOUR_GITLAB_USERNAME" and "YOUR_DOCKERHUB_USERNAME" in the above file accordingly. - -**Notes:** - -Your `GitLab` must have shared runners to run `gitlab-ci`, If you want to create a runner automatically, you can refer to [gitlab-ci plugin docs](../../plugins/gitlab-ci.md) about how to generate a runner by `DevStream`. - ---- - -## Run - -First, initialize: - -```bash -# this downloads the required plugins, according to the config file, automatically. -dtm init -f config.yaml -``` - -Then we apply it by running: - -```bash -dtm apply -f config.yaml -y -``` - -Now we can see the repo has been created in `GitLab` and the image has been uploaded to `Dockerhub`. - -
- ![GitLab CI ](./gitlab-tools/gitlab-tools-ci-page.png){ width="1000" } -
GitLab CI
-
- -In your `Kubernetes` cluster, the app pod is created in the default namespace. - -```bash -$ kubectl get application -n argocd -NAME SYNC STATUS HEALTH STATUS -testapp Synced Healthy -$ kubectl get deploy -n default -NAME READY UP-TO-DATE AVAILABLE AGE -testapp 1/1 1 1 4m27s -$ kubectl get pods -n default -NAME READY STATUS RESTARTS AGE -testapp-5f9c75b4f6-57d9p 1/1 Running 0 3m48s -``` \ No newline at end of file diff --git a/docs/use-cases/gitops-python-flask/5-gitlab-dtm-tools.zh.md b/docs/use-cases/gitops-python-flask/5-gitlab-dtm-tools.zh.md deleted file mode 100644 index ff3db8bfc..000000000 --- a/docs/use-cases/gitops-python-flask/5-gitlab-dtm-tools.zh.md +++ /dev/null @@ -1,129 +0,0 @@ -# 用 DTM Tools 实现基于 Gitlab,Argo CD 和 Gitlab CI 的 CICD 流程 - -DevStream 抽象了2个概念:[Tools](../../core-concepts/tools.md) 和 [Apps](../../core-concepts/apps.md)。 - -在[前一个用户场景](./4-gitlab-dtm-apps.zh.md) 里介绍了 "Apps",你可以用 "Tools" 实现一样的效果。具体方法如下: - -## 配置环境变量 - -你需要配置以下两个环境变量: - -```bash -export GITLAB_TOKEN="YOUR_GITLAB_TOKEN" -export IMAGE_REPO_PASSWORD="YOUR_DOCKERHUB_TOKEN_HERE" -``` - ---- - -## 配置文件 - -```yaml -config: - state: - backend: local - options: - stateFile: devstream.state - -vars: - gitlabUser: YOUR_GITLAB_USERNAME - dockerUser: YOUR_DOCKERHUB_USERNAME - app: testapp - -tools: -- name: helm-installer - instanceID: argocd -- name: repo-scaffolding - instanceID: myapp - options: - destinationRepo: - owner: [[ gitlabUser ]] - name: [[ app ]] - branch: master - scmType: gitlab - # set env GITLAB_TOKEN - token: [[ env GITLAB_TOKEN ]] - sourceRepo: - org: devstream-io - name: dtm-repo-scaffolding-python-flask - scmType: github -- name: gitlab-ci - instanceID: flask - dependsOn: [ "repo-scaffolding.myapp" ] - options: - scm: - owner: [[ gitlabUser ]] - name: [[ app ]] - branch: master - scmType: gitlab - token: [[ env GITLAB_TOKEN ]] - pipeline: - language: - framework: flask - name: python - imageRepo: - user: [[ dockerUser ]] - # set env IMAGE_REPO_PASSWORD - password: [[ env IMAGE_REPO_PASSWORD ]] -- name: argocdapp - instanceID: default - dependsOn: [ "helm-installer.argocd", "gitlab-ci.flask" ] - options: - app: - name: [[ app ]] - namespace: argocd - destination: - server: https://kubernetes.default.svc - namespace: default - source: - valuefile: values.yaml - path: helm/[[ app ]] - repoURL: ${{repo-scaffolding.myapp.outputs.repoURL}} - token: [[ env GITLAB_TOKEN ]] - imageRepo: - user: [[ dockerUser ]] - password: [[ env IMAGE_REPO_PASSWORD ]] -``` - -需要相应更新上述配置文件里的 "YOUR_GITLAB_USERNAME" 和 "YOUR_DOCKERHUB_USERNAME"。 - -**注意:** - -上述配置运行的前提是你的 `GitLab` 已经配置好了共享 runner 来执行 `ci` 操作,如果你想要自动创建一个 runner, 你可以参考 [gitlab插件文档](../../plugins/github-actions.zh.md) 来配置由 `DevStream` 来创建一个项目 runner。 - ---- - -## 执行 - -首先需要基于配置文件来初始化插件: - -```bash -# this downloads the required plugins, according to the config file, automatically. -dtm init -f config.yaml -``` - -然后运行如下命令让配置生效: - -```bash -dtm apply -f config.yaml -y -``` - -现在我们就可以看到 `GitLab` 中已经创建了对应的仓库并且 `CI` 把生成的项目镜像推送到了 `Dockerhub` 仓库。 - -
- ![GitLab CI ](./gitlab-tools/gitlab-tools-ci-page.png){ width="1000" } -
GitLab CI
-
- -在你的 `Kubernetes` 集群中已经创建了对应的应用。 - -```bash -$ kubectl get application -n argocd -NAME SYNC STATUS HEALTH STATUS -testapp Synced Healthy -$ kubectl get deploy -n default -NAME READY UP-TO-DATE AVAILABLE AGE -testapp 1/1 1 1 4m27s -$ kubectl get pods -n default -NAME READY STATUS RESTARTS AGE -testapp-5f9c75b4f6-57d9p 1/1 Running 0 3m48s -``` \ No newline at end of file diff --git a/docs/use-cases/gitops-python-flask/gitlab-apps/gitlab-login.png b/docs/use-cases/gitops-python-flask/gitlab-apps/gitlab-login.png deleted file mode 100644 index b27a36841..000000000 Binary files a/docs/use-cases/gitops-python-flask/gitlab-apps/gitlab-login.png and /dev/null differ diff --git a/docs/use-cases/gitops-python-flask/gitlab-apps/gitlab-token.png b/docs/use-cases/gitops-python-flask/gitlab-apps/gitlab-token.png deleted file mode 100644 index 720ba9ae0..000000000 Binary files a/docs/use-cases/gitops-python-flask/gitlab-apps/gitlab-token.png and /dev/null differ diff --git a/docs/use-cases/gitops-python-flask/gitlab-apps/gitlabci-1.png b/docs/use-cases/gitops-python-flask/gitlab-apps/gitlabci-1.png deleted file mode 100644 index 73c3c7ad2..000000000 Binary files a/docs/use-cases/gitops-python-flask/gitlab-apps/gitlabci-1.png and /dev/null differ diff --git a/docs/use-cases/gitops-python-flask/gitlab-apps/repo-scaffolding.png b/docs/use-cases/gitops-python-flask/gitlab-apps/repo-scaffolding.png deleted file mode 100644 index 90a92adac..000000000 Binary files a/docs/use-cases/gitops-python-flask/gitlab-apps/repo-scaffolding.png and /dev/null differ diff --git a/docs/use-cases/gitops-python-flask/gitlab-tools/gitlab-tools-ci-page.png b/docs/use-cases/gitops-python-flask/gitlab-tools/gitlab-tools-ci-page.png deleted file mode 100644 index 5b5c35a49..000000000 Binary files a/docs/use-cases/gitops-python-flask/gitlab-tools/gitlab-tools-ci-page.png and /dev/null differ diff --git a/docs/use-cases/gitops/1-overview.md b/docs/use-cases/gitops/1-overview.md deleted file mode 100644 index 07dd0c5c7..000000000 --- a/docs/use-cases/gitops/1-overview.md +++ /dev/null @@ -1 +0,0 @@ -# Overview diff --git a/docs/use-cases/gitops/1-overview.zh.md b/docs/use-cases/gitops/1-overview.zh.md deleted file mode 100644 index 60d297217..000000000 --- a/docs/use-cases/gitops/1-overview.zh.md +++ /dev/null @@ -1 +0,0 @@ -# 概览 diff --git a/docs/use-cases/gitops/2-gitops-tools.md b/docs/use-cases/gitops/2-gitops-tools.md deleted file mode 100644 index 558eaf729..000000000 --- a/docs/use-cases/gitops/2-gitops-tools.md +++ /dev/null @@ -1,308 +0,0 @@ -# GitOps - -## 0 Goal - -In this tutorial, we will try to use DevStream to achieve the following: - -1. create a new repository for a Python web application written with [Flask](https://flask.palletsprojects.com/en/2.2.x/); -2. setup basic CI pipelines for the repo we created with GitHub Actions; -3. install [Argo CD](https://argo-cd.readthedocs.io/en/stable/) for GitOps in _an existing Kubernetes cluster_; -4. create an Argo CD application that deploys the web application generated in step 1. - -> Note: -> -> in step 3, Argo CD is installed in an existing Kubernetes cluster. Setting up infrastructure like a Kubernetes cluster is something DevStream chooses not to do. -> -> If you want to follow this tutorial and give it a try yourself but don't know how to get a Kubernetes cluster up and running locally, maybe the following blogs (also from DevStream) would help: -> -> - [Creating a Local Kubernetes Cluster from the Ground Up - a Tutorial of "Kind"](https://blog.devstream.io/posts/creating-a-local-k8s-cluster-with-kind/) -> - [Getting Started with minikube](https://blog.devstream.io/posts/getting-started-with-minikube/) - ---- - -## 1 TL;DR: See It in Action - -If you prefer to see this GitOps doc in action, check out the video demo below: - - - -It's from an older version of DevStream with slightly different configuration files, but you get the gist. We will update the video with the latest version of DevStream soon; bear with us for a little while. - -For Chinese readers, watch this one: - - - -However, if you are like us, who prefer to do things hands-on and get their hands dirty, read on, follow the steps, and have a go yourself! - ---- - -## 2 Overview - -DevStream will use the following plugins to achieve the goal described in [Section 0](#0-goal): - -1. [repo-scaffolding](../../plugins/repo-scaffolding.md) -2. [github-actions](../../plugins/github-actions.md) -3. [helm-installer](../../plugins/helm-installer/helm-installer.md) -4. [argocdapp](../../plugins/argocdapp.md) - -However, you do not have to worry about these plugins because DevStream will manage them automatically for you. - ---- - -## 3 Getting Started: Download DevStream (`dtm`) - -Create a temporary working directory for this tutorial: - -```bash -mkdir test -cd test/ -``` - -Then, under the newly created directory, execute the following command: - -```shell -sh -c "$(curl -fsSL https://download.devstream.io/download.sh)" -``` - -This script checks your system and downloads the corresponding `dtm` binary. Then the binary will be granted execution permission. - -If you do an `ls`, you can see the binary `dtm` has been downloaded: - -```bash -tiexin@mbp ~/work/devstream-io/test $ ls -dtm -``` - -And as a test, try to execute it, and you will get similar output to the following: - -```bash -tiexin@mbp ~/work/devstream-io/test $ ./dtm -DevStream is an open-source DevOps toolchain manager - -###### ##### -# # ###### # # # # ##### ##### ###### ## # # -# # # # # # # # # # # # ## ## -# # ##### # # ##### # # # ##### # # # ## # -# # # # # # # ##### # ###### # # -# # # # # # # # # # # # # # # -###### ###### ## ##### # # # ###### # # # # - -Usage: - dtm [command] - -Available Commands: - apply Create or update DevOps tools according to DevStream configuration file - completion Generate the autocompletion script for dtm for the specified shell - delete Delete DevOps tools according to DevStream configuration file - destroy Destroy DevOps tools deployment according to DevStream configuration file & state file - develop Develop is used for develop a new plugin - help Help about any command - init Download needed plugins according to the config file - list This command only supports listing plugins now - show Show is used to print plugins' configuration templates or status - upgrade Upgrade dtm to the latest release version - verify Verify DevOps tools according to DevStream config file and state - version Print the version number of DevStream - -Flags: - --debug debug level log - -h, --help help for dtm - -Use "dtm [command] --help" for more information about a command. -``` - -> Optional: you can move `dtm` to a directory which is in your $PATH. For example: `mv dtm /usr/local/bin/`. This will allow you to run `dtm` directly without having to prefix it with the dot and slash (`./dtm`). -> -> For more methods on how to install DevStream, see [install dtm](../../install.md). - ---- - -## 4 Config File - -Run command below to get a valid `config.yaml` file: - -```shell -./dtm show config -t gitops > config.yaml -``` - -Then modify the `vars` section in the `config.yaml` file accordingly. Please update the values for `githubUser` and `dockerUser` to your real users. - -In the example above, I set these vars like the following: - -| Variable | Example | Note | -|------------|-------------|-----------------------------------------------------------| -| githubUser | IronCore864 | case-sensitive, use your GitHub username strictly here | -| dockerUser | ironcore864 | case-sensitive, use your DockerHub username strictly here | - -## 5 Environment Variables - -The following environment variables are required for this to work: - -```bash -export GITHUB_TOKEN="YOUR_GITHUB_TOKEN_HERE" -export IMAGE_REPO_PASSWORD="YOUR_DOCKERHUB_TOKEN_HERE" -``` - -> Note: -> -> if you don't know how to create these two tokens, check out: -> -> - GITHUB_TOKEN: [Manage API tokens for your Atlassian account](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) -> - IMAGE_REPO_PASSWORD: [Manage access tokens](https://docs.docker.com/docker-hub/access-tokens/) - ---- - -## 6 Init - -Run: - -```bash -./dtm init -f config.yaml -``` - -This downloads the required plugins, according to the config file, automatically. - -You'll get some outputs similar to the following: - -```bash -2022-12-05 17:46:01 ℹ [INFO] Using dir to store plugins. -2022-12-05 17:46:01 ℹ [INFO] -------------------- [ repo-scaffolding-darwin-arm64_0.10.2 ] -------------------- -... (omitted) -... (omitted) -2022-12-05 17:46:51 ✔ [SUCCESS] Initialize finished. -``` - ---- - -## 7 Apply - -Run: - -```bash -./dtm apply -f config.yaml -y -``` - -You will see similar outputs as the following: - -``` -2022-12-05 17:49:49 ℹ [INFO] Apply started. -2022-12-05 17:49:49 ℹ [INFO] Using local backend. State file: devstream.state. -2022-12-05 17:49:49 ℹ [INFO] Tool (repo-scaffolding/myapp) found in config but doesn't exist in the state, will be created. -2022-12-05 17:49:49 ℹ [INFO] Tool (helm-installer/argocd) found in config but doesn't exist in the state, will be created. -2022-12-05 17:49:49 ℹ [INFO] Tool (github-actions/flask) found in config but doesn't exist in the state, will be created. -2022-12-05 17:49:49 ℹ [INFO] Tool (argocdapp/default) found in config but doesn't exist in the state, will be created. -2022-12-05 17:49:49 ℹ [INFO] Start executing the plan. -2022-12-05 17:49:49 ℹ [INFO] Changes count: 4. -... (omitted) -... (omitted) -2022-12-05 17:51:51 ℹ [INFO] -------------------- [ Processing progress: 4/4. ] -------------------- -2022-12-05 17:51:51 ℹ [INFO] Processing: (argocdapp/default) -> Create ... -2022-12-05 17:51:52 ℹ [INFO] application.argoproj.io/helloworld created -2022-12-05 17:51:52 ✔ [SUCCESS] Tool (argocdapp/default) Create done. -2022-12-05 17:51:52 ℹ [INFO] -------------------- [ Processing done. ] -------------------- -2022-12-05 17:51:52 ✔ [SUCCESS] All plugins applied successfully. -2022-12-05 17:51:52 ✔ [SUCCESS] Apply finished. -``` - ---- - -## 8 Check the Results - -Let's continue to look at the results of the `apply` command. - -### 8.1 Repository - -The repository is created automatically by DevStream with scaffolding code: - -![](../../images/gitops-a.png) - -### 8.2 CI Pipelines with GitHub Actions - -GitHub Actions pipelines are created and executed: - -![](../../images/gitops-b.png) - -### 8.3 Argo CD Installation - -Argo CD is installed in your Kubernetes cluster: - -```bash -tiexin@mbp ~/work/devstream-io/test $ kubectl get namespaces -NAME STATUS AGE -argocd Active 5m42s -default Active 6m28s -kube-node-lease Active 6m29s -kube-public Active 6m29s -kube-system Active 6m29s -local-path-storage Active 6m25s -tiexin@mbp ~/work/devstream-io/test $ kubectl get pods -n argocd -NAME READY STATUS RESTARTS AGE -argocd-application-controller-0 1/1 Running 0 5m43s -argocd-applicationset-controller-66687659f-dsrtd 1/1 Running 0 5m43s -argocd-dex-server-6944757486-clshl 1/1 Running 0 5m43s -argocd-notifications-controller-7944945879-b9878 1/1 Running 0 5m43s -argocd-redis-7887bbdbbb-xzppj 1/1 Running 0 5m43s -argocd-repo-server-d4f5cc7cb-8gj24 1/1 Running 0 5m43s -argocd-server-5bb75c4bd9-g948r 1/1 Running 0 5m43s -``` - -### 8.4 Continuous Deployment with Argo CD - -The CI pipelines build a Docker image and push it into Dockerhub, and an Argo CD application created by DevStream deploys the app already: - -```bash -tiexin@mbp ~/work/devstream-io/test $ kubectl get deployment -n default -NAME READY UP-TO-DATE AVAILABLE AGE -helloworld 1/1 1 1 5m16s -tiexin@mbp ~/work/devstream-io/test $ kubectl get pods -n default -NAME READY STATUS RESTARTS AGE -helloworld-69b5586b94-wjwd9 1/1 Running 0 5m18s -tiexin@mbp ~/work/devstream-io/test $ kubectl get services -n default -NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE -helloworld ClusterIP 10.96.73.97 8080/TCP 5m27s -kubernetes ClusterIP 10.96.0.1 443/TCP 8m2s -``` - -If you do a port-forwarding: - -```bash -kubectl port-forward -n default svc/helloworld 8080:8080 -``` - -And accesses `localhost:8080` in your browser, you can see the deployed app return a "Hello, World!" to you. Hooray! - ---- - -## 9 Clean Up - -Run: - -```bash -./dtm delete -f config.yaml -y -``` - -And you will get similar outputs to the following: - -```bash -2022-12-05 17:59:25 ℹ [INFO] Delete started. -2022-12-05 17:59:26 ℹ [INFO] Using local backend. State file: devstream.state. -2022-12-05 17:59:26 ℹ [INFO] Tool (argocdapp/default) will be deleted. -2022-12-05 17:59:26 ℹ [INFO] Tool (github-actions/flask) will be deleted. -2022-12-05 17:59:26 ℹ [INFO] Tool (repo-scaffolding/myapp) will be deleted. -2022-12-05 17:59:26 ℹ [INFO] Tool (helm-installer/argocd) will be deleted. -2022-12-05 17:59:26 ℹ [INFO] Start executing the plan. -2022-12-05 17:59:26 ℹ [INFO] Changes count: 4. -... (omitted) -... (omitted) -2022-12-05 17:59:35 ℹ [INFO] -------------------- [ Processing done. ] -------------------- -2022-12-05 17:59:35 ✔ [SUCCESS] All plugins deleted successfully. -2022-12-05 17:59:35 ✔ [SUCCESS] Delete finished. -``` - -Then you can delete what we created: - -```bash -cd ../ -rm -rf test/ -rm -rf ~/.devstream/ -``` diff --git a/docs/use-cases/gitops/2-gitops-tools.zh.md b/docs/use-cases/gitops/2-gitops-tools.zh.md deleted file mode 100644 index 97bb90046..000000000 --- a/docs/use-cases/gitops/2-gitops-tools.zh.md +++ /dev/null @@ -1,305 +0,0 @@ -# GitOps - -## 0 目标 - -在本教程中,我们将尝试通过 DevStream 来实现以下目标: - -1. 创建一个 Python Web 应用程序仓库,基于 [Flask](https://flask.palletsprojects.com/en/2.2.x/) 框架; -2. 使用 GitHub Actions 为我们创建的仓库设置基本的 CI 流水线; -3. 在 _一个已有的 Kubernetes 集群_ 中安装 [Argo CD](https://argo-cd.readthedocs.io/en/stable/) 以实现 GitOps; -4. 创建一个 Argo CD 应用程序,用于部署第 1 步中生成的 Web 应用程序。 - -> 注意: -> -> 在第 3 步中,Argo CD 安装在一个已有的 Kubernetes 集群中。DevStream 不配置基础设施,例如 Kubernetes 集群。 -> -> 如果你想跟着本教程自己尝试一下,但不知道如何在本地启动和运行 Kubernetes 集群,下面的博客(也来自 DevStream)可能会有所帮助: -> -> - [用 Kind 从零开始快速搭建本地 Kubernetes 测试环节](https://blog.devstream.io/posts/%E7%94%A8kind%E9%83%A8%E7%BD%B2k8s%E7%8E%AF%E5%A2%83/) -> - [minikube结合阿里云镜像搭建本地开发测试环境](https://blog.devstream.io/posts/%E4%BD%BF%E7%94%A8minikube%E5%92%8C%E9%98%BF%E9%87%8C%E4%BA%91%E9%95%9C%E5%83%8F%E5%AE%89%E8%A3%85k8s/) - ---- - -## 1 太长不看版:Demo 演示 - -如果你想看看 GitOps 的实际运行效果,可以看看下面的视频演示: - - - -这个演示录制于 DevStream 的旧版本,配置文件略有不同,但你还是能从中领略 DevStream GitOps 流程的魅力与要点。我们会尽快更新 DevStream 的最新版本的视频演示,敬请期待~ - -对于中文读者,可以看看这个: - - - -光看完全不尽兴吧?跟着后面的步骤一起试一试吧! - ---- - -## 2 概览 - -DevStream 将使用下面的插件来实现[第 0 节](#)中描述的目标: - -1. [repo-scaffolding](../../plugins/repo-scaffolding.md) -2. [github-actions](../../plugins/github-actions.md) -3. [helm-installer](../../plugins/helm-installer/helm-installer.md) -4. [argocdapp](../../plugins/argocdapp.md) - -不过,你不需要担心这些插件,因为 DevStream 会帮你自动管理它们。 - ---- - -## 3 启程:下载 DevStream (`dtm`) - -为本教程创建一个临时工作目录: - -```bash -mkdir test -cd test/ -``` - -接着,在新创建的目录下,运行下面的命令: - -```shell -sh -c "$(curl -fsSL https://download.devstream.io/download.sh)" -``` - -这个脚本会根据你的操作系统来下载对应的 `dtm` 二进制文件。然后,赋予其可执行权限。 - -如果你执行 `ls` 命令,你会看到 `dtm` 二进制文件已经被下载下来了: - -```bash -tiexin@mbp ~/work/devstream-io/test $ ls -dtm -``` - -然后,出于测试目的,我们可以尝试运行它,你会看到类似下面的输出: - -```bash -tiexin@mbp ~/work/devstream-io/test $ ./dtm -DevStream is an open-source DevOps toolchain manager - -###### ##### -# # ###### # # # # ##### ##### ###### ## # # -# # # # # # # # # # # # ## ## -# # ##### # # ##### # # # ##### # # # ## # -# # # # # # # ##### # ###### # # -# # # # # # # # # # # # # # # -###### ###### ## ##### # # # ###### # # # # - -Usage: - dtm [command] - -Available Commands: - apply Create or update DevOps tools according to DevStream configuration file - completion Generate the autocompletion script for dtm for the specified shell - delete Delete DevOps tools according to DevStream configuration file - destroy Destroy DevOps tools deployment according to DevStream configuration file & state file - develop Develop is used for develop a new plugin - help Help about any command - init Download needed plugins according to the config file - list This command only supports listing plugins now - show Show is used to print plugins' configuration templates or status - upgrade Upgrade dtm to the latest release version - verify Verify DevOps tools according to DevStream config file and state - version Print the version number of DevStream - -Flags: - --debug debug level log - -h, --help help for dtm - -Use "dtm [command] --help" for more information about a command. -``` - -> 可选:你可以把 `dtm` 移动到 $PATH 环境变量中的某个目录下。例如:`mv dtm /usr/local/bin/`。这样,你就可以直接运行 `dtm` 而不需要再加上 `./` 前缀了。 -> -> 更多安装方式详见[安装 dtm](../../install.zh.md)。 - ---- - -## 4 配置文件 - -运行以下命令来生成 gitops 的模板配置文件 `config.yaml` 。 - -```shell -./dtm show config -t gitops > config.yaml -``` - -按需修改 `config.yaml` 文件中的 `vars` 部分。记得修改 `githubUser` 和 `dockerUser` 的值为你自己的用户名。 - -在上面的例子中,我把这些变量设置成了下面的值: - -| 变量 | 例子 | 说明 | -|------------|-------------|------------------------------------| -| githubUser | IronCore864 | 大小写敏感,请改成你的 GitHub 用户名 | -| dockerUser | ironcore864 | 大小写敏感,请改成你的 DockerHub 用户名 | - -## 5 环境变量 - -我们还需要设置以下环境变量: - -```bash -export GITHUB_TOKEN="YOUR_GITHUB_TOKEN_HERE" -export IMAGE_REPO_PASSWORD="YOUR_DOCKERHUB_TOKEN_HERE" -``` - -> 提示: -> 如果你不知道如何创建这两个 token,可以参考: -> -> - GITHUB_TOKEN:[Manage API tokens for your Atlassian account](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) -> - IMAGE_REPO_PASSWORD:[Manage access tokens](https://docs.docker.com/docker-hub/access-tokens/) - ---- - -## 6 初始化(Init) - -运行以下命令,以根据配置文件自动下载所需插件: - -```bash -./dtm init -f config.yaml -``` - -你会看到类似下面的输出: - -```bash -2022-12-05 17:46:01 ℹ [INFO] Using dir to store plugins. -2022-12-05 17:46:01 ℹ [INFO] -------------------- [ repo-scaffolding-darwin-arm64_0.10.1 ] -------------------- -... (略) -... (略) -2022-12-05 17:46:51 ✔ [SUCCESS] Initialize finished. -``` - ---- - -## 7 应用(Apply) - -运行: - -```bash -./dtm apply -f config.yaml -y -``` - -你会看到类似下面的输出: - -``` -2022-12-05 17:49:49 ℹ [INFO] Apply started. -2022-12-05 17:49:49 ℹ [INFO] Using local backend. State file: devstream.state. -2022-12-05 17:49:49 ℹ [INFO] Tool (repo-scaffolding/myapp) found in config but doesn't exist in the state, will be created. -2022-12-05 17:49:49 ℹ [INFO] Tool (helm-installer/argocd) found in config but doesn't exist in the state, will be created. -2022-12-05 17:49:49 ℹ [INFO] Tool (github-actions/flask) found in config but doesn't exist in the state, will be created. -2022-12-05 17:49:49 ℹ [INFO] Tool (argocdapp/default) found in config but doesn't exist in the state, will be created. -2022-12-05 17:49:49 ℹ [INFO] Start executing the plan. -2022-12-05 17:49:49 ℹ [INFO] Changes count: 4. -... (略) -... (略) -2022-12-05 17:51:51 ℹ [INFO] -------------------- [ Processing progress: 4/4. ] -------------------- -2022-12-05 17:51:51 ℹ [INFO] Processing: (argocdapp/default) -> Create ... -2022-12-05 17:51:52 ℹ [INFO] application.argoproj.io/helloworld created -2022-12-05 17:51:52 ✔ [SUCCESS] Tool (argocdapp/default) Create done. -2022-12-05 17:51:52 ℹ [INFO] -------------------- [ Processing done. ] -------------------- -2022-12-05 17:51:52 ✔ [SUCCESS] All plugins applied successfully. -2022-12-05 17:51:52 ✔ [SUCCESS] Apply finished. -``` - ---- - -## 8 查看结果 - -让我们来看看 `apply` 命令的结果。 - -### 8.1 GitHub 仓库 - -DevStream 已经通过 `repo-scaffolding` 插件自动创建了一个仓库: - -![](../../images/gitops-a.png) - -### 8.2 基于 GitHub Actions 的 CI 流水线 - -GitHub Actions 流水线已经被创建并运行: - -![](../../images/gitops-b.png) - -### 8.3 Argo CD 的安装 - -Argo CD 已经被安装到了 Kubernetes 集群中: - -```bash -tiexin@mbp ~/work/devstream-io/test $ kubectl get namespaces -NAME STATUS AGE -argocd Active 5m42s -default Active 6m28s -kube-node-lease Active 6m29s -kube-public Active 6m29s -kube-system Active 6m29s -local-path-storage Active 6m25s -tiexin@mbp ~/work/devstream-io/test $ kubectl get pods -n argocd -NAME READY STATUS RESTARTS AGE -argocd-application-controller-0 1/1 Running 0 5m43s -argocd-applicationset-controller-66687659f-dsrtd 1/1 Running 0 5m43s -argocd-dex-server-6944757486-clshl 1/1 Running 0 5m43s -argocd-notifications-controller-7944945879-b9878 1/1 Running 0 5m43s -argocd-redis-7887bbdbbb-xzppj 1/1 Running 0 5m43s -argocd-repo-server-d4f5cc7cb-8gj24 1/1 Running 0 5m43s -argocd-server-5bb75c4bd9-g948r 1/1 Running 0 5m43s -``` - -### 8.4 使用 Argo CD 持续部署 - -CI 流水线已经构建了一个 Docker 镜像并推送到了 Dockerhub,而 DevStream 创建的 Argo CD 应用也部署了这个应用: - -```bash -tiexin@mbp ~/work/devstream-io/test $ kubectl get deployment -n default -NAME READY UP-TO-DATE AVAILABLE AGE -helloworld 1/1 1 1 5m16s -tiexin@mbp ~/work/devstream-io/test $ kubectl get pods -n default -NAME READY STATUS RESTARTS AGE -helloworld-69b5586b94-wjwd9 1/1 Running 0 5m18s -tiexin@mbp ~/work/devstream-io/test $ kubectl get services -n default -NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE -helloworld ClusterIP 10.96.73.97 8080/TCP 5m27s -kubernetes ClusterIP 10.96.0.1 443/TCP 8m2s -``` - -我们可以通过端口转发来访问这个应用: - -```bash -kubectl port-forward -n default svc/helloworld 8080:8080 -``` - -在浏览器中访问 `localhost:8080`,你可以看到应用返回了一个 "Hello, World!"。大功告成! - ---- - -## 9 清理 - -运行: - -```bash -./dtm delete -f config.yaml -y -``` - -你会看到如下的输出: - -```bash -2022-12-05 17:59:25 ℹ [INFO] Delete started. -2022-12-05 17:59:26 ℹ [INFO] Using local backend. State file: devstream.state. -2022-12-05 17:59:26 ℹ [INFO] Tool (argocdapp/default) will be deleted. -2022-12-05 17:59:26 ℹ [INFO] Tool (github-actions/flask) will be deleted. -2022-12-05 17:59:26 ℹ [INFO] Tool (repo-scaffolding/myapp) will be deleted. -2022-12-05 17:59:26 ℹ [INFO] Tool (helm-installer/argocd) will be deleted. -2022-12-05 17:59:26 ℹ [INFO] Start executing the plan. -2022-12-05 17:59:26 ℹ [INFO] Changes count: 4. -... (略) -... (略) -2022-12-05 17:59:35 ℹ [INFO] -------------------- [ Processing done. ] -------------------- -2022-12-05 17:59:35 ✔ [SUCCESS] All plugins deleted successfully. -2022-12-05 17:59:35 ✔ [SUCCESS] Delete finished. -``` - -后面我们就能删除创建的所有文件了: - -```bash -cd ../ -rm -rf test/ -rm -rf ~/.devstream/ -``` diff --git a/docs/use-cases/gitops/3-gitops-apps.md b/docs/use-cases/gitops/3-gitops-apps.md deleted file mode 100644 index 83d57176c..000000000 --- a/docs/use-cases/gitops/3-gitops-apps.md +++ /dev/null @@ -1,155 +0,0 @@ -# GitOps with "Apps" - -## 0 Goal - -In this tutorial, we will try to use DevStream's new feature "Apps" to achieve similar result to the [GitOps](./2-gitops-tools.md), but using much less configuration, to show the power of "Apps" in the config. If you haven't read the original GitOps best practice, click the above link first. - -Two applications will be created (one Python, one Golang), CI/CD pipelines will be set up for both, and both apps will be deployed via Argo CD, just like the GitOps best practice. - ---- - -## 1 Overview - -DevStream will use the "Apps" concept to create repositories with scaffolding code, GitHub Actions CI pipelines, install Argo CD, and deploy the apps via Argo CD. - ---- - -## 2 Create the Config File - -Create a temporary working directory for this tutorial: - -```bash -mkdir test -cd test/ -``` - -Download dtm (see the [GitOps](./2-gitops-tools.md) best practice if you haven't). - -Then generate the config file by running: - -```bash -./dtm show config --template=apps > config.yaml -``` - -Then set the following environment variables by running (replace values within the double quotes): - -```shell -export GITHUB_USER="" -export DOCKERHUB_USERNAME="" -``` - -Then we run the following commands to update our config file with those env vars: - -=== "**macOS** or **FreeBSD** based systems" - - ```shell - sed -i.bak "s@YOUR_GITHUB_USER@${GITHUB_USER}@g" config.yaml - sed -i.bak "s@YOUR_DOCKERHUB_USER@${DOCKERHUB_USERNAME}@g" config.yaml - ``` - -=== "**GNU** Linux users" - - ```shell - sed -i "s@YOUR_GITHUB_USERNAME_CASE_SENSITIVE@${GITHUB_USER}@g" config.yaml - sed -i "s@YOUR_DOCKER_USERNAME@${DOCKERHUB_USERNAME}@g" config.yaml - ``` - ---- - -## 3 Init and Apply - -Run: - -```bash -./dtm init -f config.yaml -``` - -This downloads the required plugins, according to the config file, automatically. - -Then we apply it by running: - -```bash -./dtm apply -f config.yaml -y -``` - -You will see similar outputs as the following: - -```bash -tiexin@mbp ~/work/devstream-io/test $ ./dtm apply -f config.yaml -y -2022-12-16 14:30:27 ℹ [INFO] Apply started. -2022-12-16 14:30:28 ℹ [INFO] Using local backend. State file: devstream.state. -2022-12-16 14:30:28 ℹ [INFO] Tool (helm-installer/argocd) found in config but doesn't exist in the state, will be created. -2022-12-16 14:30:28 ℹ [INFO] Tool (repo-scaffolding/myapp1) found in config but doesn't exist in the state, will be created. -2022-12-16 14:30:28 ℹ [INFO] Tool (repo-scaffolding/myapp2) found in config but doesn't exist in the state, will be created. -2022-12-16 14:30:28 ℹ [INFO] Tool (github-actions/myapp1) found in config but doesn't exist in the state, will be created. -2022-12-16 14:30:28 ℹ [INFO] Tool (argocdapp/myapp1) found in config but doesn't exist in the state, will be created. -2022-12-16 14:30:28 ℹ [INFO] Tool (github-actions/myapp2) found in config but doesn't exist in the state, will be created. -2022-12-16 14:30:28 ℹ [INFO] Tool (argocdapp/myapp2) found in config but doesn't exist in the state, will be created. -2022-12-16 14:30:28 ℹ [INFO] Start executing the plan. -2022-12-16 14:30:28 ℹ [INFO] Changes count: 7. -2022-12-16 14:30:28 ℹ [INFO] -------------------- [ Processing progress: 1/7. ] -------------------- -2022-12-16 14:30:28 ℹ [INFO] Processing: (helm-installer/argocd) -> Create ... -2022-12-16 14:30:29 ℹ [INFO] Filling default config with instance: argocd. -2022-12-16 14:30:29 ℹ [INFO] Creating or updating helm chart ... -... (omitted) -... (omitted) -2022-12-16 14:32:09 ℹ [INFO] -------------------- [ Processing progress: 7/7. ] -------------------- -2022-12-16 14:32:09 ℹ [INFO] Processing: (argocdapp/myapp2) -> Create ... -2022-12-16 14:32:19 ℹ [INFO] application.argoproj.io/myapp2 created -2022-12-16 14:32:19 ✔ [SUCCESS] Tool (argocdapp/myapp2) Create done. -2022-12-16 14:32:19 ℹ [INFO] -------------------- [ Processing done. ] -------------------- -2022-12-16 14:32:19 ✔ [SUCCESS] All plugins applied successfully. -2022-12-16 14:32:19 ✔ [SUCCESS] Apply finished. -``` - ---- - -## 4 Check the Results - -Let's continue to look at the results of the `apply` command. - -Similar to what we did in the [GitOps](./2-gitops-tools.md) best practice, we can check that repositories for two applications are created, CI pipelins are created for both apps too, Argo CD is installed, and both apps are deployed by Argo CD into our Kubernetes cluster. - ---- - -## 5 Clean Up - -Run: - -```bash -./dtm delete -f config.yaml -y -``` - -And you will get similar outputs to the following: - -```bash -2022-12-16 14:34:30 ℹ [INFO] Delete started. -2022-12-16 14:34:31 ℹ [INFO] Using local backend. State file: devstream.state. -2022-12-16 14:34:31 ℹ [INFO] Tool (github-actions/myapp1) will be deleted. -2022-12-16 14:34:31 ℹ [INFO] Tool (argocdapp/myapp1) will be deleted. -2022-12-16 14:34:31 ℹ [INFO] Tool (github-actions/myapp2) will be deleted. -2022-12-16 14:34:31 ℹ [INFO] Tool (argocdapp/myapp2) will be deleted. -2022-12-16 14:34:31 ℹ [INFO] Tool (repo-scaffolding/myapp1) will be deleted. -2022-12-16 14:34:31 ℹ [INFO] Tool (repo-scaffolding/myapp2) will be deleted. -2022-12-16 14:34:31 ℹ [INFO] Tool (helm-installer/argocd) will be deleted. -2022-12-16 14:34:31 ℹ [INFO] Start executing the plan. -2022-12-16 14:34:31 ℹ [INFO] Changes count: 7. -2022-12-16 14:34:31 ℹ [INFO] -------------------- [ Processing progress: 1/7. ] -------------------- -2022-12-16 14:34:31 ℹ [INFO] Processing: (github-actions/myapp1) -> Delete ... -2022-12-16 14:34:33 ℹ [INFO] Prepare to delete 'github-actions_myapp1' from States. -2022-12-16 14:34:33 ✔ [SUCCESS] Tool (github-actions/myapp1) delete done. -... (omitted) -... (omitted) -2022-12-16 14:34:40 ✔ [SUCCESS] Tool (helm-installer/argocd) delete done. -2022-12-16 14:34:40 ℹ [INFO] -------------------- [ Processing done. ] -------------------- -2022-12-16 14:34:40 ✔ [SUCCESS] All plugins deleted successfully. -2022-12-16 14:34:40 ✔ [SUCCESS] Delete finished. -``` - -Then you can delete what we created: - -```bash -cd ../ -rm -rf test/ -rm -rf ~/.devstream/ -``` diff --git a/docs/use-cases/gitops/3-gitops-apps.zh.md b/docs/use-cases/gitops/3-gitops-apps.zh.md deleted file mode 100644 index 0972091a2..000000000 --- a/docs/use-cases/gitops/3-gitops-apps.zh.md +++ /dev/null @@ -1,153 +0,0 @@ -# 使用 DevStream 的新特性 Apps 来实现 GitOps - -## 0 目标 - -在本教程中,我们会使用 DevStream 的新特性 应用(Apps),来达到与 [GitOps](2-gitops-tools.zh.md) 相似的效果。但它的配置更短,来展示 应用 的强大能力。如果你还没有读过原始的 GitOps 最佳实践,可以先点击前面的链接。 - -我们会创建两个应用程序(一个基于 Python,另一个是 Go 语言),并且创建共用的 CI/CD 流水线,即两个应用程序都会通过 Argo CD 来部署,就像前面的 GitOps 做到的那样。 - ---- - -## 1 概览 - -DevStream 使用 应用 的概念来创建带有代码脚手架和 GitHub Actions 流水线的仓库,并安装 Argo CD,最后使用 Arco CD 部署应用程序。 - ---- - -## 2 创建配置文件 - -为本教程创建临时工作目录: - -```bash -mkdir test -cd test/ -``` - -下载 dtm(详见 [GitOps](./2-gitops-tools.zh.md) 最佳实践,如果你还没有下载过的话) - -运行以下命令以生成配置文件: - -```bash -./dtm show config --template=apps > config.yaml -``` - -替换下面命令中的双引号内里面的内容,并运行,以设置环境变量: - -```shell -export GITHUB_USER="" -export DOCKERHUB_USERNAME="" -``` - -现在我们就可以使用前面设置的环境变量来更新配置文件了: - -=== "基于 **macOS** 或 **FreeBSD** 的系统" - - ```shell - sed -i.bak "s@YOUR_GITHUB_USER@${GITHUB_USER}@g" config.yaml - sed -i.bak "s@YOUR_DOCKERHUB_USER@${DOCKERHUB_USERNAME}@g" config.yaml - ``` - -=== "**GNU** Linux 用户" - - ```shell - sed -i "s@YOUR_GITHUB_USERNAME_CASE_SENSITIVE@${GITHUB_USER}@g" config.yaml - sed -i "s@YOUR_DOCKER_USERNAME@${DOCKERHUB_USERNAME}@g" config.yaml - ``` - ---- - -## 3 初始化(Init)和应用(Apply) - -运行下面这个命令,以根据配置文件来自动下载对应的插件: - -```bash -./dtm init -f config.yaml -``` - -再运行 apply 命令: - -```bash -./dtm apply -f config.yaml -y -``` - -你会看到类似下面的输出: - -```bash -tiexin@mbp ~/work/devstream-io/test $ ./dtm apply -f config.yaml -y -2022-12-16 14:30:27 ℹ [INFO] Apply started. -2022-12-16 14:30:28 ℹ [INFO] Using local backend. State file: devstream.state. -2022-12-16 14:30:28 ℹ [INFO] Tool (helm-installer/argocd) found in config but doesn't exist in the state, will be created. -2022-12-16 14:30:28 ℹ [INFO] Tool (repo-scaffolding/myapp1) found in config but doesn't exist in the state, will be created. -2022-12-16 14:30:28 ℹ [INFO] Tool (repo-scaffolding/myapp2) found in config but doesn't exist in the state, will be created. -2022-12-16 14:30:28 ℹ [INFO] Tool (github-actions/myapp1) found in config but doesn't exist in the state, will be created. -2022-12-16 14:30:28 ℹ [INFO] Tool (argocdapp/myapp1) found in config but doesn't exist in the state, will be created. -2022-12-16 14:30:28 ℹ [INFO] Tool (github-actions/myapp2) found in config but doesn't exist in the state, will be created. -2022-12-16 14:30:28 ℹ [INFO] Tool (argocdapp/myapp2) found in config but doesn't exist in the state, will be created. -2022-12-16 14:30:28 ℹ [INFO] Start executing the plan. -2022-12-16 14:30:28 ℹ [INFO] Changes count: 7. -2022-12-16 14:30:28 ℹ [INFO] -------------------- [ Processing progress: 1/7. ] -------------------- -2022-12-16 14:30:28 ℹ [INFO] Processing: (helm-installer/argocd) -> Create ... -2022-12-16 14:30:29 ℹ [INFO] Filling default config with instance: argocd. -2022-12-16 14:30:29 ℹ [INFO] Creating or updating helm chart ... -... (略) -... (略) -2022-12-16 14:32:09 ℹ [INFO] -------------------- [ Processing progress: 7/7. ] -------------------- -2022-12-16 14:32:09 ℹ [INFO] Processing: (argocdapp/myapp2) -> Create ... -2022-12-16 14:32:19 ℹ [INFO] application.argoproj.io/myapp2 created -2022-12-16 14:32:19 ✔ [SUCCESS] Tool (argocdapp/myapp2) Create done. -2022-12-16 14:32:19 ℹ [INFO] -------------------- [ Processing done. ] -------------------- -2022-12-16 14:32:19 ✔ [SUCCESS] All plugins applied successfully. -2022-12-16 14:32:19 ✔ [SUCCESS] Apply finished. -``` - ---- - -## 4 查看结果 - -让我们来继续看看 `apply` 命令的结果: - -与我们在[GitOps](./2-gitops-tools.zh.md)最佳实践中所做的类似,我们可以检查 dtm 是否为两个应用程序创建了代码仓库并为其创建了 CI 流水线,同时安装了 Argo CD,而且两个应用程序都使用了 Argo CD 来部署到了 Kubernetes 集群中。 - ---- - -## 5 清理 - -运行: - -```bash -./dtm delete -f config.yaml -y -``` - -你会看到类似下面的输出: - -```bash -2022-12-16 14:34:30 ℹ [INFO] Delete started. -2022-12-16 14:34:31 ℹ [INFO] Using local backend. State file: devstream.state. -2022-12-16 14:34:31 ℹ [INFO] Tool (github-actions/myapp1) will be deleted. -2022-12-16 14:34:31 ℹ [INFO] Tool (argocdapp/myapp1) will be deleted. -2022-12-16 14:34:31 ℹ [INFO] Tool (github-actions/myapp2) will be deleted. -2022-12-16 14:34:31 ℹ [INFO] Tool (argocdapp/myapp2) will be deleted. -2022-12-16 14:34:31 ℹ [INFO] Tool (repo-scaffolding/myapp1) will be deleted. -2022-12-16 14:34:31 ℹ [INFO] Tool (repo-scaffolding/myapp2) will be deleted. -2022-12-16 14:34:31 ℹ [INFO] Tool (helm-installer/argocd) will be deleted. -2022-12-16 14:34:31 ℹ [INFO] Start executing the plan. -2022-12-16 14:34:31 ℹ [INFO] Changes count: 7. -2022-12-16 14:34:31 ℹ [INFO] -------------------- [ Processing progress: 1/7. ] -------------------- -2022-12-16 14:34:31 ℹ [INFO] Processing: (github-actions/myapp1) -> Delete ... -2022-12-16 14:34:33 ℹ [INFO] Prepare to delete 'github-actions_myapp1' from States. -2022-12-16 14:34:33 ✔ [SUCCESS] Tool (github-actions/myapp1) delete done. -... (略) -... (略) -2022-12-16 14:34:40 ✔ [SUCCESS] Tool (helm-installer/argocd) delete done. -2022-12-16 14:34:40 ℹ [INFO] -------------------- [ Processing done. ] -------------------- -2022-12-16 14:34:40 ✔ [SUCCESS] All plugins deleted successfully. -2022-12-16 14:34:40 ✔ [SUCCESS] Delete finished. -``` - -后面我们就能删除创建的所有文件了: - -```bash -cd ../ -rm -rf test/ -rm -rf ~/.devstream/ -``` diff --git a/docs/use-cases/helm-installer.md b/docs/use-cases/helm-installer.md deleted file mode 100644 index aed4c1515..000000000 --- a/docs/use-cases/helm-installer.md +++ /dev/null @@ -1 +0,0 @@ -# Replacing Helm with DevStream Makes Application Deployment Easier diff --git a/docs/use-cases/helm-installer.zh.md b/docs/use-cases/helm-installer.zh.md deleted file mode 100644 index e2e6a12b9..000000000 --- a/docs/use-cases/helm-installer.zh.md +++ /dev/null @@ -1,69 +0,0 @@ -# 用 DevStream 替代 Helm 让应用部署更加简单 - -helm-installer 插件实现了比 helm 更加简单和容易上手的方式来快速部署提供了 Helm Chart 的应用。 - -下面以 Argo CD 为例,介绍如何使用 DevStream 部署 Argo CD。 - -## 1 下载 - -进入你的工作目录,运行: - -```shell -sh -c "$(curl -fsSL https://download.devstream.io/download.sh)" -``` - -!!! quote "可选" - 你可以将 `dtm` 移到 PATH 中。例如:`mv dtm /usr/local/bin/`。 - - 更多安装方式详见[安装dtm](../install.zh.md)。 - -## 2 配置 - -创建一个 `config.yaml` 文件,内容如下: - -```yaml title="config.yaml" -config: - state: - backend: local - options: - stateFile: devstream.state -tools: -- name: helm-installer - instanceID: argocd -``` - -其中 `instanceID` 为 "argocd",匹配了 "argocd" 前缀,DevStream 会识别这个前缀,尝试寻找 Argo CD 应用对应的 Chart,并设置一系列默认值,然后开始部署。 - -通过 DevStream 安装 Helm 应用,你不需要搜索/阅读应用的官方文档,也不需要依次运行 `helm repo add` 等命令。你只需要知道应用的名称,将其作为 `instanceID` 的前缀,然后运行即可。这里是 [DevStream 支持的所有应用列表及前缀对应关系](../plugins/helm-installer/helm-installer.zh.md#3)。 - -## 3 初始化 - -运行以下命令以下载相应的插件: - -```shell -./dtm init -f config.yaml -``` - -## 4 应用 - -运行以下命令以安装 Argo CD: - -```shell -./dtm apply -f config.yaml -y -``` - - - -## 5 检查结果 - -运行以下命令,可以看到 Argo CD 已经安装成功: - -```shell -kubectl get pods -n argocd -``` - - - -## 6 更进一步 - -DevStream 除了通过提供默认配置来简化应用部署,还提供了完整的 Helm values 文件的配置能力,详见[自定义Chart配置](../plugins/helm-installer/helm-installer.zh.md#22-chart) diff --git a/docs/use-cases/reference/image-registry.md b/docs/use-cases/reference/image-registry.md deleted file mode 100644 index a623f4b92..000000000 --- a/docs/use-cases/reference/image-registry.md +++ /dev/null @@ -1,3 +0,0 @@ -# Private Image Registry Deployment - -//TODO(daniel-hutao): Chinese version first. diff --git a/docs/use-cases/reference/image-registry.zh.md b/docs/use-cases/reference/image-registry.zh.md deleted file mode 100644 index b0049e100..000000000 --- a/docs/use-cases/reference/image-registry.zh.md +++ /dev/null @@ -1,58 +0,0 @@ -# 部署私有镜像仓库 - -如果在离线环境中使用 DevStream,你需要准备一个私有镜像仓库用于保存相关容器镜像。 -如果你的环境里还没有一个可用的镜像仓库,那么可以参考本文步骤快速部署一个简易的容器镜像仓库用于支持离线使用 DevStream。 - -*提示:本文基于 CentOS 7 编写。* - -## 一、准备证书 - -下文域名均以 `registry.devstream.io` 为例,你在实际执行的时候需要按需修改。 - -```shell -cd ~ && mkdir certs -openssl genrsa -out certs/ca.key 2048 -openssl req -new -x509 -days 3650 -key certs/ca.key -subj "/C=CN/ST=GD/L=SZ/O=DevStream, Inc./CN=DevStream Root CA" -out certs/ca.crt -openssl req -newkey rsa:2048 -nodes -keyout certs/domain.key -subj "/C=CN/ST=GD/L=SZ/O=DevStream, Inc./CN=*.devstream.io" -out certs/domain.csr -openssl x509 -req -extfile <(printf "subjectAltName=DNS:devstream.io,DNS:registry.devstream.io") \ - -days 3650 -in certs/domain.csr -CA certs/ca.crt -CAkey certs/ca.key -CAcreateserial -out certs/domain.crt -``` - -## 二、启动 Docker Registry - -```shell -docker run -d \ - --restart=always \ - --name registry \ - -v $(pwd)/certs:/certs \ - -v $(pwd)/registry:/var/lib/registry \ - -e REGISTRY_HTTP_ADDR=0.0.0.0:443 \ - -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt \ - -e REGISTRY_HTTP_TLS_KEY=/certs/domain.key \ - -p 443:443 \ - registry:2 -``` - -# 三、配置 Docker Registry - -1、在 `/etc/hosts` 中配置本机 IP 到自定义域名(如:registry.devstream.io)之间的映射,如: - -```shell -# docker registry -192.168.39.100 registry.devstream.io -``` - -2、配置 Docker 信任刚才生成的证书(域名以 registry.devstream.io 为例) - -```shell -sudo mkdir -p /etc/docker/certs.d/registry.devstream.io -sudo cp ~/certs/ca.crt /etc/docker/certs.d/registry.devstream.io/ca.crt -``` - -3. 验证 Docker Registry 可用 - -```shell -docker pull busybox -docker tag busybox registry.devstream.io/busybox -docker push registry.devstream.io/busybox -``` diff --git a/examples/apps.yaml b/examples/apps.yaml deleted file mode 100644 index 8559f9c1c..000000000 --- a/examples/apps.yaml +++ /dev/null @@ -1,49 +0,0 @@ -config: - state: - backend: local - options: - stateFile: devstream.state - -vars: - GITHUB_USER: YOUR_GITHUB_USER - DOCKERHUB_USER: YOUR_DOCKERHUB_USER - -tools: -- name: helm-installer - instanceID: argocd - -apps: -- name: myapp1 - spec: - language: python - framework: django - repo: - url: github.com/[[ GITHUB_USER ]]/myapp1 - token: [[ env GITHUB_TOKEN ]] - repoTemplate: - url: github.com/devstream-io/dtm-repo-scaffolding-python-flask - ci: - - type: github-actions - options: - imageRepo: - user: [[ DOCKERHUB_USER ]] - password: [[ env IMAGE_REPO_PASSWORD ]] - cd: - - type: argocdapp -- name: myapp2 - spec: - language: golang - framework: gin - repo: - url: github.com/[[ GITHUB_USER ]]/myapp2 - token: [[ env GITHUB_TOKEN ]] - repoTemplate: - url: github.com/devstream-io/dtm-repo-scaffolding-golang-gin - ci: - - type: github-actions - options: - imageRepo: - user: [[ DOCKERHUB_USER ]] - password: [[ env IMAGE_REPO_PASSWORD ]] - cd: - - type: argocdapp diff --git a/examples/gitops.yaml b/examples/gitops.yaml deleted file mode 100644 index 5a06ee774..000000000 --- a/examples/gitops.yaml +++ /dev/null @@ -1,60 +0,0 @@ -config: - state: - backend: local - options: - stateFile: devstream.state -vars: - githubUser: GITHUB_USER - dockerUser: DOCKERHUB_USER - app: helloworld - -tools: -- name: repo-scaffolding - instanceID: myapp - options: - destinationRepo: - owner: [[ githubUser ]] - name: [[ app ]] - branch: main - scmType: github - token: [[ env GITHUB_TOKEN ]] - sourceRepo: - org: devstream-io - name: dtm-repo-scaffolding-python-flask - scmType: github -- name: github-actions - instanceID: flask - dependsOn: [ repo-scaffolding.myapp ] - options: - scm: - owner: [[ githubUser ]] - name: [[ app ]] - scmType: github - token: [[ env GITHUB_TOKEN ]] - pipeline: - configLocation: https://raw.githubusercontent.com/devstream-io/dtm-pipeline-templates/main/github-actions/workflows/main.yml - language: - name: python - framework: flask - imageRepo: - user: [[ dockerUser ]] - password: [[ env IMAGE_REPO_PASSWORD ]] -- name: helm-installer - instanceID: argocd -- name: argocdapp - instanceID: default - dependsOn: [ "helm-installer.argocd", "github-actions.flask" ] - options: - app: - name: [[ app ]] - namespace: argocd - destination: - server: https://kubernetes.default.svc - namespace: default - source: - valuefile: values.yaml - path: helm/[[ app ]] - repoURL: ${{repo-scaffolding.myapp.outputs.repoURL}} - token: [[ env GITHUB_TOKEN ]] - imageRepo: - user: [[ dockerUser ]] diff --git a/examples/quickstart.yaml b/examples/quickstart.yaml deleted file mode 100644 index d09435166..000000000 --- a/examples/quickstart.yaml +++ /dev/null @@ -1,42 +0,0 @@ -config: - state: # state config, backend can be local, s3 or k8s - backend: local - options: - stateFile: devstream.state - -vars: - RepoOwner: YOUR_GITHUB_USERNAME_CASE_SENSITIVE - ImageRepoUser: YOUR_DOCKER_USERNAME - -tools: -- name: repo-scaffolding - instanceID: golang-github - options: - destinationRepo: - owner: [[ RepoOwner ]] - name: go-webapp-devstream-demo - branch: main - scmType: github - token: [[ env GITHUB_TOKEN ]] - sourceRepo: - org: devstream-io - name: dtm-repo-scaffolding-golang-gin - scmType: github -- name: github-actions - instanceID: default - dependsOn: ["repo-scaffolding.golang-github"] - options: - scm: - owner: [[ RepoOwner ]] - name: go-webapp-devstream-demo - branch: main - scmType: github - token: [[ env GITHUB_TOKEN ]] - pipeline: - configLocation: https://raw.githubusercontent.com/devstream-io/dtm-pipeline-templates/main/github-actions/workflows/main.yml - language: - name: go - framework: gin - imageRepo: - user: [[ ImageRepoUser ]] - password: [[ env IMAGE_REPO_PASSWORD ]] diff --git a/go.mod b/go.mod index 505c22e6f..8269855c8 100644 --- a/go.mod +++ b/go.mod @@ -1,276 +1,35 @@ module github.com/devstream-io/devstream -go 1.18 +go 1.20 require ( - github.com/Masterminds/semver v1.5.0 - github.com/adlio/trello v1.9.0 - github.com/argoproj/argo-cd/v2 v2.2.2 - github.com/argoproj/gitops-engine v0.5.2 - github.com/aws/aws-sdk-go-v2 v1.16.3 - github.com/aws/aws-sdk-go-v2/config v1.15.5 - github.com/aws/aws-sdk-go-v2/service/s3 v1.26.9 - github.com/bndr/gojenkins v1.1.0 - github.com/briandowns/spinner v1.20.0 - github.com/cenkalti/backoff v2.2.1+incompatible - github.com/cheggaaa/pb v1.0.29 - github.com/deckarep/golang-set/v2 v2.1.0 - github.com/go-playground/validator/v10 v10.11.0 - github.com/goccy/go-yaml v1.9.6 - github.com/google/go-cmp v0.5.8 - github.com/google/go-github/v42 v42.0.0 - github.com/hashicorp/go-cleanhttp v0.5.2 - github.com/hashicorp/go-getter v1.6.2 - github.com/imdario/mergo v0.3.12 - github.com/manifoldco/promptui v0.9.0 - github.com/mitchellh/mapstructure v1.5.0 - github.com/mittwald/go-helm-client v0.8.4 - github.com/onsi/ginkgo/v2 v2.1.4 - github.com/onsi/gomega v1.19.0 - github.com/pkg/errors v0.9.1 - github.com/sirupsen/logrus v1.8.1 - github.com/spf13/cobra v1.5.0 - github.com/spf13/viper v1.8.1 - github.com/stretchr/testify v1.8.0 - github.com/tcnksm/go-input v0.0.0-20180404061846-548a7d7a8ee8 - github.com/withfig/autocomplete-tools/integrations/cobra v1.2.1 - github.com/xanzy/go-gitlab v0.74.0 - go.uber.org/multierr v1.6.0 - golang.org/x/crypto v0.1.0 - golang.org/x/exp v0.0.0-20230131160201-f062dba9d201 - golang.org/x/oauth2 v0.0.0-20220722155238-128564f6959c + github.com/onsi/ginkgo v1.16.5 + github.com/onsi/gomega v1.27.6 + github.com/sirupsen/logrus v1.9.0 + github.com/spf13/cobra v1.7.0 + github.com/spf13/viper v1.15.0 gopkg.in/gookit/color.v1 v1.1.6 gopkg.in/yaml.v3 v3.0.1 - gotest.tools v2.2.0+incompatible - helm.sh/helm/v3 v3.7.2 - k8s.io/api v0.22.4 - k8s.io/apimachinery v0.22.4 - k8s.io/client-go v0.22.4 - k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b ) require ( - cloud.google.com/go v0.81.0 // indirect - cloud.google.com/go/storage v1.10.0 // indirect - github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/BurntSushi/toml v0.3.1 // indirect - github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd // indirect - github.com/Masterminds/goutils v1.1.1 // indirect - github.com/Masterminds/semver/v3 v3.1.1 // indirect - github.com/Masterminds/sprig/v3 v3.2.2 // indirect - github.com/Masterminds/squirrel v1.5.2 // indirect - github.com/Microsoft/go-winio v0.4.17 // indirect - github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 // indirect - github.com/acomagu/bufpipe v1.0.3 // indirect - github.com/argoproj/pkg v0.11.1-0.20211203175135-36c59d8fafe0 // indirect - github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 // indirect - github.com/aws/aws-sdk-go v1.44.114 // indirect - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.1 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.12.0 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.4 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.10 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.4 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.3.11 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.1 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.1 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.5 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.4 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.4 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.11.4 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.16.4 // indirect - github.com/aws/smithy-go v1.11.2 // indirect - github.com/beorn7/perks v1.0.1 // indirect - github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect - github.com/bombsimon/logrusr v1.0.0 // indirect - github.com/bradleyfalzon/ghinstallation/v2 v2.0.3 // indirect - github.com/cespare/xxhash/v2 v2.1.2 // indirect - github.com/chai2010/gettext-go v0.0.0-20170215093142-bf70f2a70fb1 // indirect - github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect - github.com/containerd/containerd v1.5.7 // indirect - github.com/cyphar/filepath-securejoin v0.2.2 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/docker/cli v20.10.7+incompatible // indirect - github.com/docker/distribution v2.7.1+incompatible // indirect - github.com/docker/docker v20.10.2+incompatible // indirect - github.com/docker/docker-credential-helpers v0.6.3 // indirect - github.com/docker/go-connections v0.4.0 // indirect - github.com/docker/go-metrics v0.0.1 // indirect - github.com/docker/go-units v0.4.0 // indirect - github.com/emicklei/go-restful v2.9.5+incompatible // indirect - github.com/emirpasic/gods v1.12.0 // indirect - github.com/evanphx/json-patch v4.12.0+incompatible // indirect - github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect - github.com/fatih/camelcase v1.0.0 // indirect - github.com/fatih/color v1.13.0 // indirect - github.com/fsnotify/fsnotify v1.5.1 // indirect - github.com/fvbommel/sortorder v1.0.1 // indirect - github.com/ghodss/yaml v1.0.0 // indirect - github.com/go-errors/errors v1.4.2 // indirect - github.com/go-git/gcfg v1.5.0 // indirect - github.com/go-git/go-billy/v5 v5.3.1 // indirect - github.com/go-git/go-git/v5 v5.4.2 // indirect - github.com/go-logr/logr v0.4.0 // indirect - github.com/go-openapi/jsonpointer v0.19.5 // indirect - github.com/go-openapi/jsonreference v0.20.0 // indirect - github.com/go-openapi/swag v0.21.1 // indirect - github.com/go-playground/locales v0.14.0 // indirect - github.com/go-playground/universal-translator v0.18.0 // indirect - github.com/go-redis/cache/v8 v8.4.2 // indirect - github.com/go-redis/redis/v8 v8.11.3 // indirect - github.com/go-sql-driver/mysql v1.6.0 // indirect - github.com/gobwas/glob v0.2.3 // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt/v4 v4.0.0 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.2 // indirect - github.com/google/btree v1.0.1 // indirect - github.com/google/go-github/v39 v39.0.0 // indirect - github.com/google/go-querystring v1.1.0 // indirect - github.com/google/gofuzz v1.1.0 // indirect - github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect - github.com/google/uuid v1.3.0 // indirect - github.com/googleapis/gax-go/v2 v2.0.5 // indirect - github.com/googleapis/gnostic v0.5.5 // indirect - github.com/gorilla/mux v1.8.0 // indirect - github.com/gosuri/uitable v0.0.4 // indirect - github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect - github.com/hashicorp/go-retryablehttp v0.7.1 // indirect - github.com/hashicorp/go-safetemp v1.0.0 // indirect - github.com/hashicorp/go-version v1.2.0 // indirect + github.com/frankban/quicktest v1.14.4 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/google/go-cmp v0.5.9 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/huandu/xstrings v1.3.2 // indirect - github.com/inconshreveable/mousetrap v1.0.0 // indirect - github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect - github.com/jmespath/go-jmespath v0.4.0 // indirect - github.com/jmoiron/sqlx v1.3.1 // indirect - github.com/jonboulle/clockwork v0.2.2 // indirect - github.com/josharian/intern v1.0.0 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/jstemmer/go-junit-report v0.9.1 // indirect - github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect - github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect - github.com/klauspost/compress v1.13.6 // indirect - github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect - github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect - github.com/leodido/go-urn v1.2.1 // indirect - github.com/lib/pq v1.10.2 // indirect - github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect - github.com/magiconair/properties v1.8.5 // indirect - github.com/mailru/easyjson v0.7.7 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.16 // indirect - github.com/mattn/go-runewidth v0.0.9 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect - github.com/mitchellh/copystructure v1.1.1 // indirect - github.com/mitchellh/go-homedir v1.1.0 // indirect - github.com/mitchellh/go-testing-interface v1.0.0 // indirect - github.com/mitchellh/go-wordwrap v1.0.0 // indirect - github.com/mitchellh/reflectwalk v1.0.1 // indirect - github.com/moby/locker v1.0.1 // indirect - github.com/moby/spdystream v0.2.0 // indirect - github.com/moby/term v0.0.0-20210610120745-9d4ed1856297 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect - github.com/morikuni/aec v1.0.0 // indirect - github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.0.1 // indirect - github.com/patrickmn/go-cache v2.1.0+incompatible // indirect - github.com/pelletier/go-toml v1.9.3 // indirect - github.com/peterbourgon/diskv v2.0.1+incompatible // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.11.0 // indirect - github.com/prometheus/client_model v0.2.0 // indirect - github.com/prometheus/common v0.26.0 // indirect - github.com/prometheus/procfs v0.6.0 // indirect - github.com/robfig/cron v1.2.0 // indirect - github.com/rogpeppe/go-internal v1.8.1 // indirect - github.com/rubenv/sql-migrate v0.0.0-20210614095031-55d5740dbbcc // indirect - github.com/russross/blackfriday v1.5.2 // indirect - github.com/sergi/go-diff v1.1.0 // indirect - github.com/shopspring/decimal v1.2.0 // indirect - github.com/spf13/afero v1.6.0 // indirect - github.com/spf13/cast v1.4.1 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/nxadm/tail v1.4.8 // indirect + github.com/pelletier/go-toml/v2 v2.0.6 // indirect + github.com/spf13/afero v1.9.3 // indirect + github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect - github.com/spf13/pflag v1.0.6-0.20200504143853-81378bbcd8a1 // indirect - github.com/subosito/gotenv v1.2.0 // indirect - github.com/ulikunitz/xz v0.5.8 // indirect - github.com/vmihailenco/go-tinylfu v0.2.1 // indirect - github.com/vmihailenco/msgpack/v5 v5.3.4 // indirect - github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect - github.com/xanzy/ssh-agent v0.3.0 // indirect - github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect - github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect - github.com/xeipuuv/gojsonschema v1.2.0 // indirect - github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca // indirect - go.opencensus.io v0.23.0 // indirect - go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect - go.uber.org/atomic v1.9.0 // indirect - golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect - golang.org/x/mod v0.6.0 // indirect - golang.org/x/net v0.1.0 // indirect - golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect - golang.org/x/sys v0.2.0 // indirect - golang.org/x/term v0.1.0 // indirect - golang.org/x/text v0.4.0 // indirect - golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect - golang.org/x/tools v0.2.0 // indirect - golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect - google.golang.org/api v0.44.0 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf // indirect - google.golang.org/grpc v1.44.0 // indirect - google.golang.org/protobuf v1.28.1 // indirect - gopkg.in/gorp.v1 v1.7.2 // indirect - gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/ini.v1 v1.62.0 // indirect - gopkg.in/warnings.v0 v0.1.2 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/apiextensions-apiserver v0.22.4 // indirect - k8s.io/apiserver v0.22.4 // indirect - k8s.io/cli-runtime v0.22.4 // indirect - k8s.io/component-base v0.22.4 // indirect - k8s.io/component-helpers v0.22.4 // indirect - k8s.io/klog/v2 v2.10.0 // indirect - k8s.io/kube-aggregator v0.22.2 // indirect - k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect - k8s.io/kubectl v0.22.4 // indirect - k8s.io/kubernetes v1.22.4 // indirect - oras.land/oras-go v0.4.0 // indirect - sigs.k8s.io/kustomize/api v0.8.11 // indirect - sigs.k8s.io/kustomize/kyaml v0.11.0 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect - sigs.k8s.io/yaml v1.3.0 // indirect -) - -replace ( - // More info => https://github.com/kubernetes/kubernetes/issues/79384#issuecomment-505627280 - k8s.io/api => k8s.io/api v0.22.4 - k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.22.4 // indirect - k8s.io/apimachinery => k8s.io/apimachinery v0.22.5-rc.0 // indirect - k8s.io/apiserver => k8s.io/apiserver v0.22.4 - k8s.io/cli-runtime => k8s.io/cli-runtime v0.22.4 - k8s.io/client-go => k8s.io/client-go v0.22.4 - k8s.io/cloud-provider => k8s.io/cloud-provider v0.22.4 - k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.22.4 - k8s.io/code-generator => k8s.io/code-generator v0.22.5-rc.0 - k8s.io/component-base => k8s.io/component-base v0.22.4 - k8s.io/component-helpers => k8s.io/component-helpers v0.22.4 - k8s.io/controller-manager => k8s.io/controller-manager v0.22.4 - k8s.io/cri-api => k8s.io/cri-api v0.23.0-alpha.0 - k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.22.4 - k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.22.4 - k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.22.4 - k8s.io/kube-proxy => k8s.io/kube-proxy v0.22.4 - k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.22.4 - k8s.io/kubectl => k8s.io/kubectl v0.22.4 - k8s.io/kubelet => k8s.io/kubelet v0.22.4 - k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.22.4 - k8s.io/metrics => k8s.io/metrics v0.22.4 - k8s.io/mount-utils => k8s.io/mount-utils v0.22.5-rc.0 - k8s.io/pod-security-admission => k8s.io/pod-security-admission v0.22.4 - k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.22.4 - k8s.io/sample-cli-plugin => k8s.io/sample-cli-plugin v0.22.4 - k8s.io/sample-controller => k8s.io/sample-controller v0.22.4 + github.com/spf13/pflag v1.0.5 // indirect + github.com/subosito/gotenv v1.4.2 // indirect + golang.org/x/net v0.8.0 // indirect + golang.org/x/sys v0.7.0 // indirect + golang.org/x/text v0.8.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect ) diff --git a/go.sum b/go.sum index fc5df36a5..0b18626e5 100644 --- a/go.sum +++ b/go.sum @@ -1,14 +1,12 @@ -bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= -bitbucket.org/bertimus9/systemstat v0.0.0-20180207000608-0eeff89b0690/go.mod h1:Ulb78X89vxKYgdL24HMTiXYHlyHEvruOj1ZPlqeNEZM= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw= cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= @@ -18,10 +16,7 @@ cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOY cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= -cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= -cloud.google.com/go v0.81.0 h1:at8Tk2zUz63cLPR0JPWm5vp77pEZmzxEQBEfRKn1VV8= -cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -30,7 +25,6 @@ cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4g cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= @@ -39,620 +33,46 @@ cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiy cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0 h1:STgFzyU5/8miMl0//zKh2aQeTyeaUH3WN9bSUiJ09bA= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go v55.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= -github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= -github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= -github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= -github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= -github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= -github.com/Azure/go-autorest/autorest/validation v0.1.0/go.mod h1:Ha3z/SqBeaalWQvokg3NZAlQTalVMtOIAs1aGK7G6u8= -github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= -github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= -github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= -github.com/GoogleCloudPlatform/k8s-cloud-provider v0.0.0-20200415212048-7901bc822317/go.mod h1:DF8FZRxMHMGv/vP2lQP6h+dYzzjpuRn24VeRiYn3qjQ= -github.com/JeffAshton/win_pdh v0.0.0-20161109143554-76bb4ee9f0ab/go.mod h1:3VYc5hodBMJ5+l/7J4xAyMeuM2PNuepvHlGs8yilUCA= -github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= -github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd h1:sjQovDkwrZp8u+gxLtPgKGjk5hCxuy2hrRejBTA9xFU= -github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= -github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= -github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= -github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= -github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= -github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= -github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= -github.com/Masterminds/squirrel v1.5.2 h1:UiOEi2ZX4RCSkpiNDQN5kro/XIBpSRk9iTqdIRPzUXE= -github.com/Masterminds/squirrel v1.5.2/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= -github.com/Masterminds/vcs v1.13.1/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA= -github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= -github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= -github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= -github.com/Microsoft/go-winio v0.4.15/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= -github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= -github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= -github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Microsoft/go-winio v0.4.17 h1:iT12IBVClFevaf8PuVyi3UmZOVh4OqnaLxDTW2O6j3w= -github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= -github.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= -github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ= -github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8= -github.com/Microsoft/hcsshim v0.8.10-0.20200715222032-5eafd1556990/go.mod h1:ay/0dTb7NsG8QMDfsRfLHgZo/6xAJShLe1+ePPflihk= -github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg= -github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00= -github.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600= -github.com/Microsoft/hcsshim v0.8.21 h1:btRfUDThBE5IKcvI8O8jOiIkujUsAMBSRsYDYmEi6oM= -github.com/Microsoft/hcsshim v0.8.21/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= -github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= -github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= -github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 h1:YoJbenK9C67SkzkDfmQuVln04ygHj3vjZfd9FL+GmQQ= -github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= -github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= -github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= -github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= -github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= -github.com/TomOnTime/utfutil v0.0.0-20180511104225-09c41003ee1d/go.mod h1:WML6KOYjeU8N6YyusMjj2qRvaPNUEvrQvaxuFcMRFJY= -github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= -github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= -github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= -github.com/adlio/trello v1.9.0 h1:b8R1oic2yksok5McAd+kcfsvTq5sX+Fv8rMTSpBBRq8= -github.com/adlio/trello v1.9.0/go.mod h1:I4Lti4jf2KxjTNgTqs5W3lLuE78QZZdYbbPnQQGwjOo= -github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= -github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= -github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0= -github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk= -github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= -github.com/alicebob/miniredis v2.5.0+incompatible h1:yBHoLpsyjupjz3NL3MhKMVkR41j82Yjf3KFv7ApYzUI= -github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk= -github.com/alicebob/miniredis/v2 v2.14.2/go.mod h1:gquAfGbzn92jvtrSC69+6zZnwSODVXVpYDRaGhWaL6I= -github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= -github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= -github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= -github.com/argoproj/argo-cd/v2 v2.2.2 h1:sKfiJuGiG85dBRYZz20n+y3tqs+F7yuLAKkxTH0Y260= -github.com/argoproj/argo-cd/v2 v2.2.2/go.mod h1:J51If+krhtBEbNP/Y/l9sh9HctPHiiGueqPAkfFXXBo= -github.com/argoproj/gitops-engine v0.5.2 h1:UQ2ajVyUPCSgFyqidzlTXddh/Xf6cE3I0s9uu92BoJg= -github.com/argoproj/gitops-engine v0.5.2/go.mod h1:K2RYpGXh11VdFwDksS23SyFTOJaPcsF+MVJ/FHlqEOE= -github.com/argoproj/pkg v0.11.1-0.20211203175135-36c59d8fafe0 h1:Cfp7rO/HpVxnwlRqJe0jHiBbZ77ZgXhB6HWlYD02Xdc= -github.com/argoproj/pkg v0.11.1-0.20211203175135-36c59d8fafe0/go.mod h1:ra+bQPmbVAoEL+gYSKesuigt4m49i3Qa3mE/xQcjCiA= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= -github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 h1:4daAzAu0S6Vi7/lbWECcX0j45yZReDZ56BQsrVBOEEY= -github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= -github.com/auth0/go-jwt-middleware v1.0.1/go.mod h1:YSeUX3z6+TF2H+7padiEqNJ73Zy9vXW72U//IgN0BIM= -github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= -github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= -github.com/aws/aws-sdk-go v1.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM= -github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.33.16/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= -github.com/aws/aws-sdk-go v1.34.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= -github.com/aws/aws-sdk-go v1.35.24/go.mod h1:tlPOdRjfxPBpNIwqDj61rmsnA85v9jc0Ps9+muhnW+k= -github.com/aws/aws-sdk-go v1.38.49/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= -github.com/aws/aws-sdk-go v1.44.114 h1:plIkWc/RsHr3DXBj4MEw9sEW4CcL/e2ryokc+CKyq1I= -github.com/aws/aws-sdk-go v1.44.114/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= -github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= -github.com/aws/aws-sdk-go-v2 v1.16.3 h1:0W1TSJ7O6OzwuEvIXAtJGvOeQ0SGAhcpxPN2/NK5EhM= -github.com/aws/aws-sdk-go-v2 v1.16.3/go.mod h1:ytwTPBG6fXTZLxxeeCCWj2/EMYp/xDUgX+OET6TLNNU= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.1 h1:SdK4Ppk5IzLs64ZMvr6MrSficMtjY2oS0WOORXTlxwU= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.1/go.mod h1:n8Bs1ElDD2wJ9kCRTczA83gYbBmjSwZp3umc6zF4EeM= -github.com/aws/aws-sdk-go-v2/config v1.15.5 h1:P+xwhr6kabhxDTXTVH9YoHkqjLJ0wVVpIUHtFNr2hjU= -github.com/aws/aws-sdk-go-v2/config v1.15.5/go.mod h1:ZijHHh0xd/A+ZY53az0qzC5tT46kt4JVCePf2NX9Lk4= -github.com/aws/aws-sdk-go-v2/credentials v1.12.0 h1:4R/NqlcRFSkR0wxOhgHi+agGpbEr5qMCjn7VqUIJY+E= -github.com/aws/aws-sdk-go-v2/credentials v1.12.0/go.mod h1:9YWk7VW+eyKsoIL6/CljkTrNVWBSK9pkqOPUuijid4A= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.4 h1:FP8gquGeGHHdfY6G5llaMQDF+HAf20VKc8opRwmjf04= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.4/go.mod h1:u/s5/Z+ohUQOPXl00m2yJVyioWDECsbpXTQlaqSlufc= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.10 h1:uFWgo6mGJI1n17nbcvSc6fxVuR3xLNqvXt12JCnEcT8= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.10/go.mod h1:F+EZtuIwjlv35kRJPyBGcsA4f7bnSoz15zOQ2lJq1Z4= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.4 h1:cnsvEKSoHN4oAN7spMMr0zhEW2MHnhAVpmqQg8E6UcM= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.4/go.mod h1:8glyUqVIM4AmeenIsPo0oVh3+NUwnsQml2OFupfQW+0= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.11 h1:6cZRymlLEIlDTEB0+5+An6Zj1CKt6rSE69tOmFeu1nk= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.11/go.mod h1:0MR+sS1b/yxsfAPvAESrw8NfwUoxMinDyw6EYR9BS2U= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.1 h1:C21IDZCm9Yu5xqjb3fKmxDoYvJXtw1DNlOmLZEIlY1M= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.1/go.mod h1:l/BbcfqDCT3hePawhy4ZRtewjtdkl6GWtd9/U+1penQ= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.1 h1:T4pFel53bkHjL2mMo+4DKE6r6AuoZnM0fg7k1/ratr4= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.1/go.mod h1:GeUru+8VzrTXV/83XyMJ80KpH8xO89VPoUileyNQ+tc= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.5 h1:9LSZqt4v1JiehyZTrQnRFf2mY/awmyYNNY/b7zqtduU= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.5/go.mod h1:S8TVP66AAkMMdYYCNZGvrdEq9YRm+qLXjio4FqRnrEE= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.4 h1:b16QW0XWl0jWjLABFc1A+uh145Oqv+xDcObNk0iQgUk= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.4/go.mod h1:uKkN7qmSIsNJVyMtxNQoCEYMvFEXbOg9fwCJPdfp2u8= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.4 h1:RE/DlZLYrz1OOmq8F28IXHLksuuvlpzUbvJ+SESCZBI= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.4/go.mod h1:oudbsSdDtazNj47z1ut1n37re9hDsKpk2ZI3v7KSxq0= -github.com/aws/aws-sdk-go-v2/service/s3 v1.26.9 h1:LCQKnopq2t4oQS3VKivlYTzAHCTJZZoQICM9fny7KHY= -github.com/aws/aws-sdk-go-v2/service/s3 v1.26.9/go.mod h1:iMYipLPXlWpBJ0KFX7QJHZ84rBydHBY8as2aQICTPWk= -github.com/aws/aws-sdk-go-v2/service/sso v1.11.4 h1:Uw5wBybFQ1UeA9ts0Y07gbv0ncZnIAyw858tDW0NP2o= -github.com/aws/aws-sdk-go-v2/service/sso v1.11.4/go.mod h1:cPDwJwsP4Kff9mldCXAmddjJL6JGQqtA3Mzer2zyr88= -github.com/aws/aws-sdk-go-v2/service/sts v1.16.4 h1:+xtV90n3abQmgzk1pS++FdxZTrPEDgQng6e4/56WR2A= -github.com/aws/aws-sdk-go-v2/service/sts v1.16.4/go.mod h1:lfSYenAXtavyX2A1LsViglqlG9eEFYxNryTZS5rn3QE= -github.com/aws/smithy-go v1.11.2 h1:eG/N+CcUMAvsdffgMvjMKwfyDzIkjM6pfxMJ8Mzc6mE= -github.com/aws/smithy-go v1.11.2/go.mod h1:3xHYmszWVx2c0kIwQeEVf9uSm4fYZt67FBJnwub1bgM= -github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= -github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= -github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= -github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= -github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= -github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= -github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= -github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= -github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= -github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= -github.com/bndr/gojenkins v1.1.0 h1:TWyJI6ST1qDAfH33DQb3G4mD8KkrBfyfSUoZBHQAvPI= -github.com/bndr/gojenkins v1.1.0/go.mod h1:QeskxN9F/Csz0XV/01IC8y37CapKKWvOHa0UHLLX1fM= -github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= -github.com/bombsimon/logrusr v1.0.0 h1:CTCkURYAt5nhCCnKH9eLShYayj2/8Kn/4Qg3QfiU+Ro= -github.com/bombsimon/logrusr v1.0.0/go.mod h1:Jq0nHtvxabKE5EMwAAdgTaz7dfWE8C4i11NOltxGQpc= -github.com/bradleyfalzon/ghinstallation/v2 v2.0.2/go.mod h1:GhRUp70E+QFvNemlFd4unyHZ8ryBiMQkJm6KgdilpUo= -github.com/bradleyfalzon/ghinstallation/v2 v2.0.3 h1:ywF/8q+GVpvlsEuvRb1SGSDQDUxntW1d4kFu/9q/YAE= -github.com/bradleyfalzon/ghinstallation/v2 v2.0.3/go.mod h1:tlgi+JWCXnKFx/Y4WtnDbZEINo31N5bcvnCoqieefmk= -github.com/briandowns/spinner v1.20.0 h1:GQq1Yf1KyzYT8CY19GzWrDKP6hYOFB6J72Ks7d8aO1U= -github.com/briandowns/spinner v1.20.0/go.mod h1:TcwZHb7Wb6vn/+bcVv1UXEzaA4pLS7yznHlkY/HzH44= -github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= -github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= -github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= -github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= -github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd h1:rFt+Y/IK1aEZkEHchZRSq9OQbsSzIT/OrI8YFFmRIng= -github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= -github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembjv71DPz3uX/V/6MMlSyD9JBQ6kQ= -github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= -github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o= -github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= -github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/casbin/casbin/v2 v2.39.1/go.mod h1:sEL80qBYTbd+BPeL4iyvwYzFT3qwLaESq5aFKVLbLfA= -github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= -github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= -github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= -github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= -github.com/chai2010/gettext-go v0.0.0-20170215093142-bf70f2a70fb1 h1:HD4PLRzjuCVW79mQ0/pdsalOLHJ+FaEoqJLxfltpb2U= -github.com/chai2010/gettext-go v0.0.0-20170215093142-bf70f2a70fb1/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= -github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= -github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= -github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= -github.com/cheggaaa/pb v1.0.29 h1:FckUN5ngEk2LpvuG0fw1GEFx6LtyY2pWI/Z2QgCnEYo= -github.com/cheggaaa/pb v1.0.29/go.mod h1:W40334L7FMC5JKWldsTWbdGjLo0RxUKK73K+TuPxX30= -github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg= -github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc= -github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= -github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= -github.com/cilium/ebpf v0.5.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= -github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= -github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/clusterhq/flocker-go v0.0.0-20160920122132-2b8b7259d313/go.mod h1:P1wt9Z3DP8O6W3rvwCt0REIlshg1InHImaLW0t3ObY0= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= -github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= -github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= -github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= -github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= -github.com/container-storage-interface/spec v1.5.0/go.mod h1:8K96oQNkJ7pFcC2R9Z1ynGGBB1I93kcS6PGg3SsOk8s= -github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= -github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= -github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= -github.com/containerd/aufs v1.0.0/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= -github.com/containerd/btrfs v0.0.0-20201111183144-404b9149801e/go.mod h1:jg2QkJcsabfHugurUvvPhS3E08Oxiuh5W/g1ybB4e0E= -github.com/containerd/btrfs v0.0.0-20210316141732-918d888fb676/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= -github.com/containerd/btrfs v1.0.0/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= -github.com/containerd/cgroups v0.0.0-20190717030353-c4b9ac5c7601/go.mod h1:X9rLEHIqSf/wfK8NsPqxJmeZgW4pcfzdXITDrUSJ6uI= -github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= -github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM= -github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= -github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= -github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= -github.com/containerd/cgroups v1.0.1 h1:iJnMvco9XGvKUvNQkv88bE4uJXxRQH18efbKo9w5vHQ= -github.com/containerd/cgroups v1.0.1/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU= -github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= -github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= -github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= -github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw= -github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= -github.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.3.1-0.20191213020239-082f7e3aed57/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.0-beta.2.0.20200729163537-40b22ef07410/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.4/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7V960Tmcumvqn8Mc+pCYQ= -github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU= -github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI= -github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s= -github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= -github.com/containerd/containerd v1.5.2/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= -github.com/containerd/containerd v1.5.7 h1:rQyoYtj4KddB3bxG6SAqd4+08gePNyJjRqvOIfV3rkM= -github.com/containerd/containerd v1.5.7/go.mod h1:gyvv6+ugqY25TiXxcZC3L5yOeYgEw0QMhscqVp1AR9c= -github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= -github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= -github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= -github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo= -github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y= -github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ= -github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM= -github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= -github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= -github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= -github.com/containerd/fifo v0.0.0-20201026212402-0724c46b320c/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= -github.com/containerd/fifo v0.0.0-20210316144830-115abcc95a1d/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= -github.com/containerd/fifo v1.0.0/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= -github.com/containerd/go-cni v1.0.1/go.mod h1:+vUpYxKvAF72G9i1WoDOiPGRtQpqsNW/ZHtSlv++smU= -github.com/containerd/go-cni v1.0.2/go.mod h1:nrNABBHzu0ZwCug9Ije8hL2xBCYh/pjfMb1aZGrrohk= -github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= -github.com/containerd/go-runc v0.0.0-20190911050354-e029b79d8cda/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= -github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g= -github.com/containerd/go-runc v0.0.0-20201020171139-16b287bc67d0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= -github.com/containerd/go-runc v1.0.0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= -github.com/containerd/imgcrypt v1.0.1/go.mod h1:mdd8cEPW7TPgNG4FpuP3sGBiQ7Yi/zak9TYCG3juvb0= -github.com/containerd/imgcrypt v1.0.4-0.20210301171431-0ae5c75f59ba/go.mod h1:6TNsg0ctmizkrOgXRNQjAPFWpMYRWuiB6dSF4Pfa5SA= -github.com/containerd/imgcrypt v1.1.1-0.20210312161619-7ed62a527887/go.mod h1:5AZJNI6sLHJljKuI9IHnw1pWqo/F0nGDOuR9zgTs7ow= -github.com/containerd/imgcrypt v1.1.1/go.mod h1:xpLnwiQmEUJPvQoAapeb2SNCxz7Xr6PJrXQb0Dpc4ms= -github.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFYfE5+So4M5syatU0N0f0LbWpuqyMi4/BE8c= -github.com/containerd/nri v0.0.0-20210316161719-dbaa18c31c14/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= -github.com/containerd/nri v0.1.0/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= -github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= -github.com/containerd/ttrpc v0.0.0-20190828172938-92c8520ef9f8/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= -github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8= -github.com/containerd/ttrpc v1.0.1/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= -github.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= -github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= -github.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk= -github.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg= -github.com/containerd/typeurl v1.0.2/go.mod h1:9trJWW2sRlGub4wZJRTW83VtbOLS6hwcDZXTn6oPz9s= -github.com/containerd/zfs v0.0.0-20200918131355-0a33824f23a2/go.mod h1:8IgZOBdv8fAgXddBT4dBXJPtxyRsejFIpXoklgxgEjw= -github.com/containerd/zfs v0.0.0-20210301145711-11e8f1707f62/go.mod h1:A9zfAbMlQwE+/is6hi0Xw8ktpL+6glmqZYtevJgaB8Y= -github.com/containerd/zfs v0.0.0-20210315114300-dde8f0fda960/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= -github.com/containerd/zfs v0.0.0-20210324211415-d5c4544f0433/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= -github.com/containerd/zfs v1.0.0/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= -github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= -github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= -github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= -github.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHVlzhJpcY6TQxn/fUyDDM= -github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8= -github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc= -github.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgUV4GP9qXPfu4= -github.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= -github.com/coredns/caddy v1.1.0/go.mod h1:A6ntJQlAWuQfFlsd9hvigKbo2WS0VUs2l1e2F+BawD4= -github.com/coredns/corefile-migration v1.0.12/go.mod h1:NJOI8ceUF/NTgEwtjD+TUq3/BnH/GF7WAM3RzCa3hBo= -github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= -github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= -github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20161114122254-48702e0da86b/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= -github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= -github.com/coreos/go-systemd/v22 v22.3.1/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= -github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg= -github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= -github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ= -github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s= -github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= -github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE= -github.com/deckarep/golang-set/v2 v2.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6dtGktsI= -github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= -github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= -github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= -github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/distribution/distribution/v3 v3.0.0-20210804104954-38ab4c606ee3 h1:rEK0juuU5idazw//KzUcL3yYwUU3DIe2OnfJwjDBqno= -github.com/distribution/distribution/v3 v3.0.0-20210804104954-38ab4c606ee3/go.mod h1:gt38b7cvVKazi5XkHvINNytZXgTEntyhtyM3HQz46Nk= -github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= -github.com/docker/cli v20.10.7+incompatible h1:pv/3NqibQKphWZiAskMzdz8w0PRbtTaEB+f6NwdU7Is= -github.com/docker/cli v20.10.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= -github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= -github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v20.10.2+incompatible h1:vFgEHPqWBTp4pTjdLwjAA4bSo3gvIGOYwuJTlEjVBCw= -github.com/docker/docker v20.10.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker-credential-helpers v0.6.3 h1:zI2p9+1NQYdnG6sMU26EX4aVGlqbInSQxQXLvzJ4RPQ= -github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= -github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= -github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= -github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= -github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= -github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= -github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= -github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= -github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4= -github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= -github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= -github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= -github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= -github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc= -github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao2r4iyvLdACqsl/Ljk= -github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= -github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= -github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/euank/go-kmsg-parser v2.0.0+incompatible/go.mod h1:MhmAMZ8V4CYH4ybgdRwPr2TU5ThnS43puaKEMpja1uw= -github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= -github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= -github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= -github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8= -github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= -github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= -github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= -github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= -github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= -github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= -github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= -github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= -github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= +github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= +github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= -github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= -github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= -github.com/fvbommel/sortorder v1.0.1 h1:dSnXLt4mJYH25uDDGa3biZNQsozaUWDSWeKJ0qqFfzE= -github.com/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= -github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7 h1:LofdAjjjqCSXMwLGgOgnE+rdPuvX9DxCqaHwKy7i/ko= -github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= -github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= -github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= -github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= -github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= -github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= -github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= -github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= -github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= -github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= -github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= -github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= -github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= -github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= -github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34= -github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= -github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw= -github.com/go-git/go-git-fixtures/v4 v4.2.1 h1:n9gGL1Ct/yIw+nfsfr8s4+sbhT+Ncu2SubfXjIWgci8= -github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0= -github.com/go-git/go-git/v5 v5.2.0/go.mod h1:kh02eMX+wdqqxgNMEyq8YgwlIOsDOa9homkUq1PoTMs= -github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4= -github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= -github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/logr v0.3.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc= -github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/zapr v0.2.0/go.mod h1:qhKdvif7YF5GI9NWEpyxTSSBdGmzkNguibrdCNVPunU= -github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= -github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= -github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= -github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= -github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU= -github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= -github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= -github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= -github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= -github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= -github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= -github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= -github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= -github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= -github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= -github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= -github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= -github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= -github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= -github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= -github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= -github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= -github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs= -github.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCsn0N0ZrQsk= -github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= -github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64= -github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4= -github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= -github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= -github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= -github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= -github.com/go-openapi/spec v0.19.5/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= -github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= -github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= -github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= -github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= -github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= -github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= -github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrKU= -github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= -github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= -github.com/go-ozzo/ozzo-validation v3.5.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU= -github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= -github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= -github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= -github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= -github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= -github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw= -github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= -github.com/go-redis/cache/v8 v8.4.2 h1:8YbsmnU1Ws3TKS6T+qALzYE/MlGE+A/lrlx1XTA3p6M= -github.com/go-redis/cache/v8 v8.4.2/go.mod h1:X7Jjd69Ssbrf3xBQLtIDE0g3WcSbFoQiSGeb8QfEJ+g= -github.com/go-redis/redis/v8 v8.11.3 h1:GCjoYp8c+yQTJfc0n69iwSiHjvuAdruxl7elnZCxgt8= -github.com/go-redis/redis/v8 v8.11.3/go.mod h1:xNJ9xDG09FsIPwh3bWdk+0oDWHbtF9rPN0F/oD9XeKc= -github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= -github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/gobuffalo/logger v1.0.3 h1:YaXOTHNPCvkqqA7w05A4v0k2tCdpr+sgFlgINbQ6gqc= -github.com/gobuffalo/logger v1.0.3/go.mod h1:SoeejUwldiS7ZsyCBphOGURmWdwUFXs0J7TCjEhjKxM= -github.com/gobuffalo/packd v1.0.0 h1:6ERZvJHfe24rfFmA9OaoKBdC7+c9sydrytMg8SdFGBM= -github.com/gobuffalo/packd v1.0.0/go.mod h1:6VTc4htmJRFB7u1m/4LeMTWjFoYrUiBkU9Fdec9hrhI= -github.com/gobuffalo/packr/v2 v2.8.1 h1:tkQpju6i3EtMXJ9uoF5GT6kB+LMTimDWD8Xvbz6zDVA= -github.com/gobuffalo/packr/v2 v2.8.1/go.mod h1:c/PLlOuTU+p3SybaJATW3H6lX/iK7xEz5OeMf+NnJpg= -github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= -github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/goccy/go-yaml v1.9.6 h1:KhAu1zf9JXnm3vbG49aDE0E5uEBUsM4uwD31/58ZWyI= -github.com/goccy/go-yaml v1.9.6/go.mod h1:JubOolP3gh0HpiBc4BLRD4YmjEjHAmIIB2aaXKkTfoE= -github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= -github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= -github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= -github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/godror/godror v0.24.2/go.mod h1:wZv/9vPiUib6tkoDl+AZ/QLf5YZgMravZ7jxH2eQWAE= -github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= -github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gogits/go-gogs-client v0.0.0-20190616193657-5a05380e4bc2/go.mod h1:cY2AIrMgHm6oOHmR7jY+9TtjzSjQ3iG7tURJG3Y6XH0= -github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= -github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU= -github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v4 v4.0.0 h1:RAqyYixv1p7uEnocuy8P1nru5wprCh/MH2BIlW5z5/o= -github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= -github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= -github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -660,8 +80,6 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= -github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -676,20 +94,9 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk= -github.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= -github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0= -github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= -github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= -github.com/google/cadvisor v0.39.2/go.mod h1:kN93gpdevu+bpS227TyHVZyCU5bbqCzTj5T9drl34MI= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -698,29 +105,11 @@ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-github/v38 v38.0.0/go.mod h1:cStvrz/7nFr0FoENgG6GLbp53WaelXucT+BBz/3VKx4= -github.com/google/go-github/v39 v39.0.0 h1:pygGA5ySwxEez1N39GnDauD0PaWWuGgayudyZAc941s= -github.com/google/go-github/v39 v39.0.0/go.mod h1:C1s8C5aCC9L+JXIYpJM5GYytdX52vC1bLvHEF1IhBrE= -github.com/google/go-github/v42 v42.0.0 h1:YNT0FwjPrEysRkLIiKuEfSvBPCGKphW5aS5PxwaoLec= -github.com/google/go-github/v42 v42.0.0/go.mod h1:jgg/jvyI0YlDOM1/ps6XYh04HNQ3vKf0CVko62/EhRg= -github.com/google/go-jsonnet v0.17.0/go.mod h1:sOcuej3UW1vpPTZOr8L7RQimqai1a57bt5j22LzGZCw= -github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= -github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= -github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= -github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0 h1:wCKgOCHuUEVfsaQLpPSJb7VdYCdTVZQAuOdYm1yc/60= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -731,751 +120,108 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= -github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= -github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw= -github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= -github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 h1:l5lAOZEym3oK3SQ2HBHWsJUfbNBiTXJDeW2QDxw9AQ0= -github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= -github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= -github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= -github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= -github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= -github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= -github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-gateway v1.14.6/go.mod h1:zdiPV4Yse/1gnckTHtghG4GkDEdKCRJduHpTxT3/jcw= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= -github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= -github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-getter v1.6.2 h1:7jX7xcB+uVCliddZgeKyNxv0xoT7qL5KDtH7rU4IqIk= -github.com/hashicorp/go-getter v1.6.2/go.mod h1:IZCrswsZPeWv9IkVnLElzRU/gz/QPi6pZHn4tv6vbwA= -github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= -github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-retryablehttp v0.7.0/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= -github.com/hashicorp/go-retryablehttp v0.7.1 h1:sUiuQAnLlbvmExtFQs72iFW/HXeUn8Z1aJLQ4LJJbTQ= -github.com/hashicorp/go-retryablehttp v0.7.1/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= -github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= -github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= -github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E= -github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/heketi/heketi v10.3.0+incompatible/go.mod h1:bB9ly3RchcQqsQ9CpyaQwvva7RS5ytVoSoholZQON6o= -github.com/heketi/tests v0.0.0-20151005000721-f3775cbcefd6/go.mod h1:xGMAM8JLi7UkZt1i4FQeQy0R2T8GLUwQhOP5M1gBhy4= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= -github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= -github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/improbable-eng/grpc-web v0.0.0-20181111100011-16092bd1d58a/go.mod h1:6hRR09jOEG81ADP5wCQju1z71g6OL4eEvELdran/3cs= -github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= -github.com/ishidawataru/sctp v0.0.0-20190723014705-7c296d48a2b5/go.mod h1:DM4VvS+hD/kDi1U1QsX2fnZowwBhqD0Dk3bRPKF/Oc8= -github.com/itchyny/go-flags v1.5.0/go.mod h1:lenkYuCobuxLBAd/HGFE4LRoW8D3B6iXRQfWYJ+MNbA= -github.com/itchyny/gojq v0.12.3/go.mod h1:mi4PdXSlFllHyByM68JKUrbiArtEdEnNEmjbwxcQKAg= -github.com/itchyny/timefmt-go v0.1.2/go.mod h1:0osSSCQSASBJMsIZnhAaF1C2fCBTJZXrnj37mG8/c+A= -github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= -github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= -github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= -github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= -github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= -github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/jmoiron/sqlx v1.3.1 h1:aLN7YINNZ7cYOPK3QC83dbM6KT0NMqVMw961TqrejlE= -github.com/jmoiron/sqlx v1.3.1/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= -github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= -github.com/karrick/godirwalk v1.15.8/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= -github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw= -github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= -github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= -github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck= -github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= -github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= -github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.11.2/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= -github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kortschak/utter v1.0.1/go.mod h1:vSmSjbyrlKjjsL71193LmzBOKgwePk9DH6uFaWHIInc= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= -github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= -github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= -github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= -github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= -github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= -github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= -github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= -github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/libopenstorage/openstorage v1.0.0/go.mod h1:Sp1sIObHjat1BeXhfMqLZ14wnOzEhNx2YQedreMcUyc= -github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= -github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= -github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= -github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= -github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= -github.com/lpabon/godbc v0.1.1/go.mod h1:Jo9QV0cf3U6jZABgiJ2skINAXb9j8m51r07g4KI92ZA= -github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= -github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= -github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/malexdev/utfutil v0.0.0-20180510171754-00c8d4a8e7a8/go.mod h1:UtpLyb/EupVKXF/N0b4NRe1DNg+QYJsnsHQ038romhM= -github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= -github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= -github.com/markbates/errx v1.1.0 h1:QDFeR+UP95dO12JgW+tgi2UVfo0V8YBHiUIOaeBPiEI= -github.com/markbates/errx v1.1.0/go.mod h1:PLa46Oex9KNbVDZhKel8v1OT7hD5JZ2eI7AHhA0wswc= -github.com/markbates/oncer v1.0.0 h1:E83IaVAHygyndzPimgUYJjbshhDTALZyXxvk9FOlQRY= -github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI= -github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= -github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= -github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= -github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= -github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-oci8 v0.1.1/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI= -github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= -github.com/mattn/go-shellwords v1.0.11/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= -github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= -github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/mattn/go-zglob v0.0.3/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= -github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= -github.com/mindprince/gonvml v0.0.0-20190828220739-9ebdce4bb989/go.mod h1:2eu9pRWp8mo84xCg6KswZ+USQHjwgRhNp06sozOdsTY= -github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw= -github.com/minio/minio-go/v7 v7.0.2/go.mod h1:dJ80Mv2HeGkYLH1sqS/ksz07ON6csH3S6JUMSQ2zAns= -github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= -github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/cli v1.1.2/go.mod h1:6iaV0fGdElS6dPBx0EApTxHrcWvmJphyh2n8YBLPPZ4= -github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= -github.com/mitchellh/copystructure v1.1.1 h1:Bp6x9R1Wn16SIz3OfeDr0b7RnCG2OB66Y7PQyC/cvq4= -github.com/mitchellh/copystructure v1.1.1/go.mod h1:EBArHfARyrSWO/+Wyr9zwEkc6XMFB9XyNgFNmRkZZU4= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= -github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= -github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE= -github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/mittwald/go-helm-client v0.8.4 h1:2T/MUkgUjeP+h2P0YYyuwXN8jB/l90oQ12JQ5+9h9tI= -github.com/mittwald/go-helm-client v0.8.4/go.mod h1:GtL4+taCH4ADXO+aimCEQ5EtPRSGPSHcT6d/fB3coFk= -github.com/moby/ipvs v1.0.1/go.mod h1:2pngiyseZbIKXNv7hsKj3O9UEz30c53MT9005gt2hxQ= -github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= -github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= -github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= -github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= -github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= -github.com/moby/sys/mountinfo v0.4.1 h1:1O+1cHA1aujwEwwVMa2Xm2l+gIpUHyd3+D+d7LZh1kM= -github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= -github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ= -github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= -github.com/moby/term v0.0.0-20210610120745-9d4ed1856297 h1:yH0SvLzcbZxcJXho2yh7CqdENGMQe73Cw3woZBpPli0= -github.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/mohae/deepcopy v0.0.0-20170603005431-491d3605edfb/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= -github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= -github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= -github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= -github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= -github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/mvdan/xurls v1.1.0/go.mod h1:tQlNn3BED8bE/15hnSL2HLkDeLWpNPAwtw7wkEq44oU= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= -github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= -github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= -github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= -github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= -github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= -github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= -github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= -github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= -github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852/go.mod h1:eqOVx5Vwu4gd2mmMZvVZsgIqNSaW3xxRThUJ0k/TPk4= -github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= -github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= -github.com/onsi/ginkgo/v2 v2.1.4 h1:GNapqRSid3zijZ9H77KrgVG4/8KqiyRsxcSxe+7ApXY= -github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU= -github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= -github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= -github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= -github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= -github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= -github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/opencontainers/go-digest v1.0.0-rc1.0.20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= -github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= -github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0= -github.com/opencontainers/runc v1.0.0-rc95/go.mod h1:z+bZxa/+Tz/FmYVWkhUajJdzFeOqjc5vrqskhVyHGUM= -github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= -github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= -github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= -github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= -github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8= -github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= -github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= -github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= -github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= -github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= -github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= -github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= -github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= -github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= -github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ= -github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= -github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= -github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= -github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc= -github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= -github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= -github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= +github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= +github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= +github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= -github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= -github.com/pquerna/cachecontrol v0.0.0-20180306154005-525d0eb5f91d/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= -github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= -github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= -github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ= -github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= -github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ= -github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/quobyte/api v0.1.8/go.mod h1:jL7lIHrmqQ7yh05OJ+eEEdHr0u/kmT1Ff9iHd+4H6VI= -github.com/r3labs/diff v1.1.0/go.mod h1:7WjXasNzi0vJetRcB/RqNl5dlIsmXcTTLmF5IoH6Xig= -github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= -github.com/robfig/cron v1.1.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= -github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= -github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= -github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= -github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= -github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= -github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= -github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= -github.com/rs/zerolog v1.21.0/go.mod h1:ZPhntP/xmq1nnND05hhpAh2QMhSsA4UN3MGZ6O2J3hM= -github.com/rubenv/sql-migrate v0.0.0-20210614095031-55d5740dbbcc h1:BD7uZqkN8CpjJtN/tScAKiccBikU4dlqe/gNrkRaPY4= -github.com/rubenv/sql-migrate v0.0.0-20210614095031-55d5740dbbcc/go.mod h1:HFLT6i9iR4QBOF5rdCyjddC9t59ArqWJV2xx+jwcCMo= -github.com/rubiojr/go-vhd v0.0.0-20200706105327-02e210299021/go.mod h1:DM5xW0nvfNNm2uytzsvhI3OnX8uzaRAg8UX/CnDqbto= -github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= -github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= -github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= -github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= -github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= -github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= -github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= -github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/skratchdot/open-golang v0.0.0-20160302144031-75fb7ed4208c/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/assertions v1.1.0 h1:MkTeG1DMwsrdH7QtLXy5W+fUxWq+vmb6cLmyJ7aRtF0= -github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= -github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= -github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= -github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= -github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= -github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= -github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= -github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= -github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= -github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= +github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= +github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= +github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/pflag v1.0.6-0.20200504143853-81378bbcd8a1 h1:zrNp7OPtn2fjeNHI9CghvwxqQvvkK0RxUo86hE86vhU= -github.com/spf13/pflag v1.0.6-0.20200504143853-81378bbcd8a1/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= -github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= -github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44= -github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= -github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= -github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= -github.com/storageos/go-api v2.2.0+incompatible/go.mod h1:ZrLn+e0ZuF3Y65PNF6dIwbJPZqfmtCXxFm9ckv0agOY= -github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= -github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= -github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= -github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU= +github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= -github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= -github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= -github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= -github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I= -github.com/tcnksm/go-input v0.0.0-20180404061846-548a7d7a8ee8 h1:RB0v+/pc8oMzPsN97aZYEwNuJ6ouRJ2uhjxemJ9zvrY= -github.com/tcnksm/go-input v0.0.0-20180404061846-548a7d7a8ee8/go.mod h1:IlWNj9v/13q7xFbaK4mbyzMNwrZLaWSHx/aibKIZuIg= -github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= -github.com/ulikunitz/xz v0.5.8 h1:ERv8V6GKqVi23rgu5cj9pVfVzJbOqAY2Ntl88O6c2nQ= -github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/undefinedlabs/go-mpatch v1.0.6/go.mod h1:TyJZDQ/5AgyN7FSLiBJ8RO9u2c6wbtRvK827b6AVqY4= -github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= -github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= -github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= -github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= -github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= -github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= -github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= -github.com/vmihailenco/go-tinylfu v0.2.1 h1:78/wH+STtgM8+fN2GdjvvKoxF3mkdzoOoKQTchQRj+g= -github.com/vmihailenco/go-tinylfu v0.2.1/go.mod h1:CutYi2Q9puTxfcolkliPq4npPuofg9N9t8JVrjzwa3Q= -github.com/vmihailenco/msgpack/v5 v5.3.4 h1:qMKAwOV+meBw2Y8k9cVwAy7qErtYCwBzZ2ellBfvnqc= -github.com/vmihailenco/msgpack/v5 v5.3.4/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= -github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= -github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= -github.com/vmware/govmomi v0.20.3/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= -github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= -github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= -github.com/withfig/autocomplete-tools/integrations/cobra v1.2.1 h1:+dBg5k7nuTE38VVdoroRsT0Z88fmvdYrI2EjzJst35I= -github.com/withfig/autocomplete-tools/integrations/cobra v1.2.1/go.mod h1:nmuySobZb4kFgFy6BptpXp/BBw+xFSyvVPP6auoJB4k= -github.com/xanzy/go-gitlab v0.74.0 h1:Ha1cokbjn0PXy6B19t3W324dwM4AOT52fuHr7nERPrc= -github.com/xanzy/go-gitlab v0.74.0/go.mod h1:d/a0vswScO7Agg1CZNz15Ic6SSvBG9vfw8egL99t4kA= -github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= -github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI= -github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= -github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca h1:1CFlNzQhALwjS9mBAUkycX616GzgsuYUOCHA5+HSlXI= -github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= +github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da h1:NimzV1aGyq29m5ukMK0AMWEhFaL/lrEOaephfuoiARg= -github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA= -github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI= -github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= -github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMztlGpl/VA+Zm1AcTPHYkHJPbHqE6WJUXE= -github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= -github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY= -github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= -github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= -github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= -go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= -go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= -go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= -go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= -go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= -go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= -go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= -go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE= -go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc= -go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4= -go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= -go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= -go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= -go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= -go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= -go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= -go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= -go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM= -go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= -go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= -go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= -go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE= -go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE= -go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= -go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 h1:+FNtrFTmVw0YZGpBGX56XDee331t6JAXeK2bcyhLOOc= -go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= -go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= -go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= -go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.8.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= -go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= -go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= -go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= -golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= -golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= -golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= @@ -1483,11 +229,6 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20210220032938-85be41e4509f/go.mod h1:I6l2HNBLBZEcrOoCpyKLdY2lHoRZ8lI4x60KMCQDft4= -golang.org/x/exp v0.0.0-20210901193431-a062eea981d2/go.mod h1:a3o/VtDNHN+dCVLEpzjjUHOzR+Ln3DHX056ZPzoZGGA= -golang.org/x/exp v0.0.0-20230131160201-f062dba9d201 h1:BEABXpNXLEz0WxtA+6CQIz2xkg80e+1zrhWyMcq8VzE= -golang.org/x/exp v0.0.0-20230131160201-f062dba9d201/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= -golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -1501,53 +242,29 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mobile v0.0.0-20201217150744-e6ae53a27f4f/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.5.1-0.20210830214625-1b1db11ec8f4/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= -golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I= -golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -1563,25 +280,13 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= -golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1591,12 +296,6 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20220722155238-128564f6959c h1:q3gFqPqH7NVofKo3c3yETAP//pPI+G5mvB7qqj1Y5kY= -golang.org/x/oauth2 v0.0.0-20220722155238-128564f6959c/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1607,60 +306,26 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190812073006-9eafafc0a87e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1668,107 +333,49 @@ golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210301091718-77cc2087c03b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw= -golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 h1:ftMN5LMiBFjbzleLqtoBZk7KdJwhuybIU+FckUHgoyQ= -golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -1776,8 +383,6 @@ golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -1787,15 +392,12 @@ golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200308013534-11ec41452d41/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= @@ -1805,27 +407,13 @@ golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE= -golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= +golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -gomodules.xyz/jsonpatch/v2 v2.1.0/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= -gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= -gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= -gonum.org/v1/gonum v0.6.2/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU= -gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= -gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ= -gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= -google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= -google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -1833,7 +421,6 @@ google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEn google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.1-0.20200106000736-b8fc810ca6b5/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= @@ -1846,27 +433,18 @@ google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz513 google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= -google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= -google.golang.org/api v0.44.0 h1:URs6qR1lAxDsqWITsQXI4ZkGiYJ5dHtRNiCpfs2OeKA= -google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190522204451-c2c4e71fbf69/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= -google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= @@ -1875,7 +453,6 @@ google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvx google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200117163144-32f20d992d24/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= @@ -1884,10 +461,8 @@ google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= @@ -1895,31 +470,16 @@ google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf h1:SVYXkUz2yZS9FWb2Gm8ivSlbNQzL2Z/NpPKE3RG2jWk= -google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= -google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= @@ -1929,16 +489,9 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.44.0 h1:weqSxi/TMs1SqFRMHCtBgXRs8k3X39QIDEZ0pRcttUg= -google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1949,71 +502,24 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= -gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/gcfg.v1 v1.2.0/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= -gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= -gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= -gopkg.in/go-playground/webhooks.v5 v5.11.0/go.mod h1:LZbya/qLVdbqDR1aKrGuWV6qbia2zCYSR5dpom2SInQ= gopkg.in/gookit/color.v1 v1.1.6 h1:5fB10p6AUFjhd2ayq9JgmJWr9WlTrguFdw3qlYtKNHk= gopkg.in/gookit/color.v1 v1.1.6/go.mod h1:IcEkFGaveVShJ+j8ew+jwe9epHyGpJ9IrptHmW3laVY= -gopkg.in/gorp.v1 v1.7.2 h1:j3DWlAyGVv8whO7AcIWznQ2Yj7yJkn34B8s63GViAAw= -gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw= -gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= -gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= -gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/warnings.v0 v0.1.1/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= -gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= -gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= -gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= -gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= -gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= -helm.sh/helm/v3 v3.7.2 h1:xn1OxcZEpgKpp4CCpPz1KKUyb9gAtTouXV2E3S8ChYQ= -helm.sh/helm/v3 v3.7.2/go.mod h1:UXuiAn0+FfBpqbiMuwWt8/aAKkfJvnWLBJ6f4HcFs0M= -honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -2021,94 +527,6 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= -k8s.io/api v0.22.4 h1:UvyHW0ezB2oIgHAxlYoo6UJQObYXU7awuNarwoHEOjw= -k8s.io/api v0.22.4/go.mod h1:Rgs+9gIGYC5laXQSZZ9JqT5NevNgoGiOdVWi1BAB3qk= -k8s.io/apiextensions-apiserver v0.22.4 h1:2iGpcVyw4MnAyyXVJU2Xg6ZsbIxAOfRHo0LF5A5J0RA= -k8s.io/apiextensions-apiserver v0.22.4/go.mod h1:kH9lxD8dbJ+k0ZizGET55lFgdGjO8t45fgZnCVdZEpw= -k8s.io/apimachinery v0.22.5-rc.0 h1:1FjuFoteAGA8PtPlXoSCtv+rbuHLFaQOzji0XgmDcow= -k8s.io/apimachinery v0.22.5-rc.0/go.mod h1:yU6oA6Gnax9RrxGzVvPFFJ+mpnW6PBSqp0sx0I0HHW0= -k8s.io/apiserver v0.22.4 h1:L+220cy+94UWmyBl1kiVTklBXrBtKsbjlPV60eL2u6s= -k8s.io/apiserver v0.22.4/go.mod h1:38WmcUZiiy41A7Aty8/VorWRa8vDGqoUzDf2XYlku0E= -k8s.io/cli-runtime v0.22.4 h1:uFSVSdW14JP53BCtMRsw1hB9ba21TBuUb5m7RvEsH0Y= -k8s.io/cli-runtime v0.22.4/go.mod h1:x35r0ERHXr/MrbR1C6MPJxQ3xKG6+hXi9m2xLzlMPZA= -k8s.io/client-go v0.22.4 h1:aAQ1Wk+I3bjCNk35YWUqbaueqrIonkfDPJSPDDe8Kfg= -k8s.io/client-go v0.22.4/go.mod h1:Yzw4e5e7h1LNHA4uqnMVrpEpUs1hJOiuBsJKIlRCHDA= -k8s.io/cloud-provider v0.22.4/go.mod h1:lTaIKDEqJt7UPbsz9sk1Aa719ADIWuFtbh/mgq72UE8= -k8s.io/cluster-bootstrap v0.22.4/go.mod h1:fTQZ6u9G6fg2LHhB8nEgZLnXIhCDSRYuLUUS5pgW8RY= -k8s.io/code-generator v0.22.5-rc.0/go.mod h1:qjYl54pQ/emhkT0UxbufbREYJMWsHNNV/jSVwhYZQGw= -k8s.io/component-base v0.22.4 h1:7qwLJnua2ppGNZrRGDQ0vhsFebI39VGbZ4zdR5ArViI= -k8s.io/component-base v0.22.4/go.mod h1:MrSaQy4a3tFVViff8TZL6JHYSewNCLshZCwHYM58v5A= -k8s.io/component-helpers v0.22.4 h1:Pso4iXoY6aYLCYQlNkME2MSJvAXo/7lnJYsWHdC6tvE= -k8s.io/component-helpers v0.22.4/go.mod h1:A50qTyczDFbhZDifIfS2zFrHuPk9UNOWPpvNZ+3RSIs= -k8s.io/controller-manager v0.22.4/go.mod h1:DcJNoo4OvXCh9KfESIrX9C9dNQj1OfQrAZrEkFbNMRw= -k8s.io/cri-api v0.23.0-alpha.0/go.mod h1:mj5DGUtElRyErU5AZ8EM0ahxbElYsaLAMTPhLPQ40Eg= -k8s.io/csi-translation-lib v0.22.4/go.mod h1:8ZHJ0R2rSiL+0OC7WEF9MTMW4+CV4YEzXDng3rogEY4= -k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= -k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= -k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= -k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/klog/v2 v2.5.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= -k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= -k8s.io/klog/v2 v2.10.0 h1:R2HDMDJsHVTHA2n4RjwbeYXdOcBymXdX/JRb1v0VGhE= -k8s.io/klog/v2 v2.10.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= -k8s.io/kube-aggregator v0.22.4 h1:2aJMJMhWyKFRtVlj2ofGHewfjODXrMkjwkNlN1jn2jU= -k8s.io/kube-aggregator v0.22.4/go.mod h1:nH2L1wiG9pMqYV7P8XIMb9RbIEZPBwxz0iJqPPrtALU= -k8s.io/kube-controller-manager v0.22.4/go.mod h1:BLoqqosh47s25JarHCC5ghmV24AlYp5/tRjatt/YjUY= -k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= -k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= -k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 h1:E3J9oCLlaobFUqsjG9DfKbP2BmgwBL2p7pn0A3dG9W4= -k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= -k8s.io/kube-proxy v0.22.4/go.mod h1:TTzZmcecSHXUL/3d6P4puVrZt4h0UNhT2RmxSdmg7B0= -k8s.io/kube-scheduler v0.22.4/go.mod h1:2q5YGJngwFZ/9witl/n8Dij9qf52T3nR1g6OD6+pvLM= -k8s.io/kubectl v0.22.4 h1:ECUO1QWyZ70DiIKEfgBx+8i9D98uspVOwgc1APs/07w= -k8s.io/kubectl v0.22.4/go.mod h1:ok2qRT6y2Gy4+y+mniJVyUMKeBHP4OWS9Rdtf/QTM5I= -k8s.io/kubelet v0.22.4/go.mod h1:9dCtyqqDnXJYF9E2mejBmDQb+flkAGFBzGgnlW/goyo= -k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= -k8s.io/kubernetes v1.22.2/go.mod h1:Snea7fgIObGgHmLbUJ3OgjGEr5bjj16iEdp5oHS6eS8= -k8s.io/kubernetes v1.22.4 h1:N5kU4bJEghcB2226/GH9Bca+oNcH6JTplcr9euN5ti8= -k8s.io/kubernetes v1.22.4/go.mod h1:cMy6DFG4E+/jxMgxw1aWMwZqvI1AueV3HCcG9S7QNIk= -k8s.io/legacy-cloud-providers v0.22.4/go.mod h1:Kw5X3DTa1/skHsKVgcrcK9d1JVXrdQpG77kWg/JPV68= -k8s.io/metrics v0.22.4/go.mod h1:6F/iwuYb1w2QDCoHkeMFLf4pwHBcYKLm4mPtVHKYrIw= -k8s.io/mount-utils v0.22.5-rc.0/go.mod h1:dHl6c2P60T5LHUnZxVslyly9EDCMzvhtISO5aY+Z4sk= -k8s.io/pod-security-admission v0.22.4/go.mod h1:R6VgmZm77Ik1qWRBBExuiUIRXR6kGQIRM/Zh5yVpYyA= -k8s.io/sample-apiserver v0.22.4/go.mod h1:QIrXoUymVFpdy0Ei5WQjOa/Ewi3Ni+5XEhG8WD/D4iI= -k8s.io/system-validators v1.5.0/go.mod h1:bPldcLgkIUK22ALflnsXk8pvkTEndYdNuaHH6gRrl0Q= -k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20210111153108-fddb29f9d009/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b h1:wxEMGetGMur3J1xuGLQY7GEQYg9bZxKn3tKo5k/eYcs= -k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -layeh.com/gopher-json v0.0.0-20190114024228-97fed8db8427/go.mod h1:ivKkcY8Zxw5ba0jldhZCYYQfGdb2K6u9tbYK1AwMIBc= -modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= -modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= -modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= -modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= -modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= -oras.land/oras-go v0.4.0 h1:u6+7D+raZDYHwlz/uOwNANiRmyYDSSMW7A9E1xXycUQ= -oras.land/oras-go v0.4.0/go.mod h1:VJcU+VE4rkclUbum5C0O7deEZbBYnsnpbGSACwTjOcg= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/letsencrypt v0.0.3 h1:H7xDfhkaFFSYEJlKeq38RwX2jYcnTeHuDQyT+mMNMwM= -rsc.io/letsencrypt v0.0.3/go.mod h1:buyQKZ6IXrRnB7TdkHP0RyEybLx18HHyOSoTyoOLqNY= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= -sigs.k8s.io/controller-runtime v0.8.3/go.mod h1:U/l+DUopBc1ecfRZ5aviA9JDmGFQKvLf5YkZNx2e0sU= -sigs.k8s.io/kustomize/api v0.8.11 h1:LzQzlq6Z023b+mBtc6v72N2mSHYmN8x7ssgbf/hv0H8= -sigs.k8s.io/kustomize/api v0.8.11/go.mod h1:a77Ls36JdfCWojpUqR6m60pdGY1AYFix4AH83nJtY1g= -sigs.k8s.io/kustomize/cmd/config v0.9.13/go.mod h1:7547FLF8W/lTaDf0BDqFTbZxM9zqwEJqCKN9sSR0xSs= -sigs.k8s.io/kustomize/kustomize/v4 v4.2.0/go.mod h1:MOkR6fmhwG7hEDRXBYELTi5GSFcLwfqwzTRHW3kv5go= -sigs.k8s.io/kustomize/kyaml v0.11.0 h1:9KhiCPKaVyuPcgOLJXkvytOvjMJLoxpjodiycb4gHsA= -sigs.k8s.io/kustomize/kyaml v0.11.0/go.mod h1:GNMwjim4Ypgp/MueD3zXHLRJEjz7RvtPae0AwlvEMFM= -sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.1.2 h1:Hr/htKFmJEbtMgS/UD0N+gtgctAqz81t3nu+sPzynno= -sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= -sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= -sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= -sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= -sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/hack/README.md b/hack/README.md deleted file mode 100644 index 699364482..000000000 --- a/hack/README.md +++ /dev/null @@ -1,30 +0,0 @@ -# DevStream hack GuideLines - -This document describes how you can use the scripts from [`hack`](.) directory -and gives a brief introduction and explanation of these scripts. - -## Overview - -The [`hack`](.) directory contains many scripts that ensure continuous development of DevStream. - -## Key scripts - -- [e2e](./e2e): This directory holds the scripts used for e2e testing. - - [e2e-up.sh](./e2e/e2e-up.sh): This script used for setup e2e testing environment. - - [e2e-down.sh](./e2e/e2e-down.sh): This script used for clear e2e testing kind cluster. -- [switch_k8s_dep_version.sh](./switch_k8s_dep_version.sh): This script used to switch the version of kubernetes dependency. - -## Examples - -- Setup e2e testing environment - -```shell -cd hack/e2e -sh e2e-up.sh -``` - -- Update Kubernetes dependency to v1.22.4 in go.mod - -```shell -sh hack/switch_k8s_dep_version.sh v1.22.4 -``` diff --git a/hack/cf-ip-monitor/ISSUE_TEMPLATE.md b/hack/cf-ip-monitor/ISSUE_TEMPLATE.md deleted file mode 100644 index 0f8b6229e..000000000 --- a/hack/cf-ip-monitor/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: :bomb: `Bug`: Cloudflare IPs-v4 Changed assignees: IronCore864, daniel-hutao labels: bug ---- -Please see the [ips-v4](https://github.com/devstream-io/devstream/blob/main/hack/cf-ip-monitor/ips-v4) file diff --git a/hack/cf-ip-monitor/check.sh b/hack/cf-ip-monitor/check.sh deleted file mode 100644 index d073c767d..000000000 --- a/hack/cf-ip-monitor/check.sh +++ /dev/null @@ -1,13 +0,0 @@ -curl -sL https://www.cloudflare.com/ips-v4 | grep -v '^$' >ips-v4 -cat ips-v4 -if git status --porcelain | grep -q "^ M";then - echo "ips-v4 changed" - echo "CHANGED=1" >> "$GITHUB_ENV" - git config user.name 'github-actions[bot]' - git config user.email 'github-actions[bot]@users.noreply.github.com' - git add ips-v4 - git commit -m "chore: ips-v4 changed" - git push -else - echo "ips-v4 not changed" -fi diff --git a/hack/cf-ip-monitor/ips-v4 b/hack/cf-ip-monitor/ips-v4 deleted file mode 100644 index eb8553f62..000000000 --- a/hack/cf-ip-monitor/ips-v4 +++ /dev/null @@ -1,15 +0,0 @@ -173.245.48.0/20 -103.21.244.0/22 -103.22.200.0/22 -103.31.4.0/22 -141.101.64.0/18 -108.162.192.0/18 -190.93.240.0/20 -188.114.96.0/20 -197.234.240.0/22 -198.41.128.0/17 -162.158.0.0/15 -104.16.0.0/13 -104.24.0.0/14 -172.64.0.0/13 -131.0.72.0/22 diff --git a/hack/commitlint.config.js b/hack/commitlint.config.js deleted file mode 100644 index 174f2a765..000000000 --- a/hack/commitlint.config.js +++ /dev/null @@ -1,31 +0,0 @@ -const Configuration = { - /* - * Resolve and load @commitlint/config-conventional from node_modules. - * Referenced packages must be installed - */ - extends: ['@commitlint/config-conventional'], - - rules: { - 'type-enum': [ - 2, - 'always', - [ - 'build', - 'chore', - 'ci', - 'docs', - 'doc', - 'feat', - 'fix', - 'perf', - 'refactor', - 'revert', - 'style', - 'test', - ], - ], - }, - - }; - - module.exports = Configuration; diff --git a/hack/e2e/e2e-down.sh b/hack/e2e/e2e-down.sh deleted file mode 100755 index 100bf94c8..000000000 --- a/hack/e2e/e2e-down.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash - -set -o errexit -set -o nounset -set -o pipefail - -ROOT_DIR=$(dirname "${BASH_SOURCE[0]}")/../.. -WORK_DIR=${ROOT_DIR}/testbin - -cd ${WORK_DIR} -./kind delete cluster --name=devstream-e2e diff --git a/hack/e2e/e2e-run.sh b/hack/e2e/e2e-run.sh deleted file mode 100755 index 974c6c976..000000000 --- a/hack/e2e/e2e-run.sh +++ /dev/null @@ -1,142 +0,0 @@ -#!/usr/bin/env bash - -set -o pipefail - -ROOT_DIR=$(dirname "${BASH_SOURCE[0]}")/../.. -SCRIPT_DIR=${ROOT_DIR}/hack/e2e -CONFIG_DIR=${ROOT_DIR}/test/e2e/yaml -CONFIG_FILENAME=e2e-test-local.yaml - -function check() { - if [ ! -f "${ROOT_DIR}/dtm" ]; then - echo "Binary dtm not found. Maybe you forgot to 'make build' first?" - exit 1 - fi - - if [ -z ${GITHUB_USER} ]; then - echo "You have to set environment variable 'GITHUB_USER' first!" - usage - exit 1 - fi - - if [ -z ${GITHUB_TOKEN} ]; then - echo "You have to set environment variable 'GITHUB_TOKEN' first!" - usage - exit 1 - fi - - if [ -z ${DOCKERHUB_USERNAME} ]; then - echo "You have to set environment variable 'DOCKERHUB_USERNAME' first!" - usage - exit 1 - fi - - if [ -z ${DOCKERHUB_TOKEN} ]; then - echo "You have to set environment variable 'DOCKERHUB_TOKEN' first!" - usage - exit 1 - fi -} - -# setup k8s cluster and setup config yaml files -function set_up() { - check - - set -u - echo "[dtm e2e test script] Create k8s cluster by kind!" - bash ${SCRIPT_DIR}/e2e-down.sh - bash ${SCRIPT_DIR}/e2e-up.sh - - gen_config -} - -# generate temporary config files -function gen_config() { - set -u - - # modify e2e-test template config file and generate temporary config file in devstream root path - sed -e "s/GITHUBUSERNAME/${GITHUB_USER}/g" ${CONFIG_DIR}/${CONFIG_FILENAME} >${ROOT_DIR}/${CONFIG_FILENAME}.tmp - sed -e "s/DOCKERUSERNAME/${DOCKERHUB_USERNAME}/g" ${ROOT_DIR}/${CONFIG_FILENAME}.tmp >${ROOT_DIR}/${CONFIG_FILENAME} -} - -# dtm e2e test -function run_test() { - set -u - cd ${ROOT_DIR} - - echo "[dtm e2e test script] Start dtm e2e test locally!" - ./dtm apply -f ${CONFIG_FILENAME} -y - if [ $? -ne 0 ]; then - echo "[dtm e2e test script] Execute dtm apply failed!" - clean_up - exit 1 - fi - - check_status - - ./dtm verify -f ${CONFIG_FILENAME} - if [ $? -ne 0 ]; then - echo "[dtm e2e test script] Execute dtm verify failed!" - clean_up - exit 1 - fi - - ./dtm delete -f ${CONFIG_FILENAME} -y - if [ $? -ne 0 ]; then - echo "[dtm e2e test script] Execute dtm delete failed!" - clean_up - exit 1 - fi - - cd - -} - -function check_status() { - set -u - pod_ready=1 - time=0 - timeout=600 - echo "[dtm e2e test script] Start check pod status!" - while [ "$(kubectl get pods -l app=dtm-e2e-test-golang -o 'jsonpath={..status.conditions[?(@.type=="Ready")].status}')" != "True" ]; do - echo "pod not ready yet..." - sleep 10 - time=$((time + 10)) - if [ ${time} -ge ${timeout} ]; then - pod_ready=0 - break - fi - done - - if [ ${pod_ready} -eq 1 ]; then - echo "[dtm e2e test script] Pod is ready!" - else - echo "[dtm e2e test script] Pod is not ready!" - echo "[dtm e2e test script] E2E test failed!" - clean_up - exit 1 - fi -} - -function clean_up() { - echo "[dtm e2e test script] Start to clean test environment and configuration files!" - echo "[dtm e2e test script] Remove k8s cluster!" - bash ${SCRIPT_DIR}/e2e-down.sh - - echo "[dtm e2e test script] Remove yaml files!" - rm -f ${ROOT_DIR}/${CONFIG_FILENAME} - rm -f ${ROOT_DIR}/${CONFIG_FILENAME}.tmp -} - -function usage() { - echo "Usage: bash $0. - Before start e2e test locally, you have to set some environment variables, including: - - 'GITHUB_USER' - - 'GITHUB_TOKEN' - - 'DOCKERHUB_USERNAME' - - 'DOCKERHUB_TOKEN'." -} - -set_up -run_test -clean_up -echo "[dtm e2e test script] E2E test success!" diff --git a/hack/e2e/e2e-up.sh b/hack/e2e/e2e-up.sh deleted file mode 100755 index 6498b27f0..000000000 --- a/hack/e2e/e2e-up.sh +++ /dev/null @@ -1,93 +0,0 @@ -#!/usr/bin/env bash - -# This script is mainly used to start a k8s 1-node cluster environment for e2e testing. -# Docker is the only dependency that needs to be prepared in advance. -# `kubectl` and `kind` will be obtained through `curl`, maybe this way has the best compatibility. - -# All materials required for testing are downloaded to the testbin directory, -# which is initialized only when it is executed for the first time, and then cached locally. -# The reason for not judging whether there are related tools like kubectl locally -# is because these tools may not match the versions required and are not easy to manage. - -set -o errexit -set -o nounset -set -o pipefail - -ROOT_DIR=$(dirname "${BASH_SOURCE[0]}")/../.. -WORK_DIR=${ROOT_DIR}/testbin -K8S_VERSION=${TESTENV_K8S_VERSION:-1.22.0} -KIND_VERSION=${TESTENV_KIND_VERSION:-0.11.1} - -function init() { - if [ "$(uname)" == "Darwin" ];then - HOST_OS="darwin" - elif [ "$(uname)" == "Linux" ];then - HOST_OS="linux" - else - echo "Support Darwin/Linux OS only" - exit 1 - fi - - if [ "$(uname -m)" == "amd64" ] || [ "$(uname -m)" == "x86_64" ];then - HOST_ARCH="amd64" - elif [ "$(uname -m)" == "arm64" ];then - HOST_ARCH="arm64" - else - echo "Support amd64/arm64 CPU arch only" - exit 1 - fi - - echo "Got OS type: ${HOST_OS} and CPU arch: ${HOST_ARCH}" -} - -# docker -function check_deps() { - echo "Requires having Docker installed" - docker ps > /dev/null - if [ $? -ne 0 ]; then - exit 1 - fi -} - -# kubectl & kind -function fetch_tools() { - echo "All tools save in ${WORK_DIR} directory" - if [ ! -f ${WORK_DIR} ]; then - mkdir -p ${WORK_DIR} - fi - cd ${WORK_DIR} - - if [ ! -f kind ]; then - echo "kind doesn't exist, download it now" - kind_uri="https://kind.sigs.k8s.io/dl/v${KIND_VERSION}/kind-${HOST_OS}-${HOST_ARCH}" - echo "kind uri: ${kind_uri}" - curl -Lo ./kind "${kind_uri}" - fi - echo "kind already downloaded" - chmod +x ./kind - - if [ ! -f kubectl ]; then - echo "kubectl doesn't exist, download it now" - kubectl_uri="https://dl.k8s.io/release/v${K8S_VERSION}/bin/${HOST_OS}/${HOST_ARCH}/kubectl" - echo "kubectl uri: ${kubectl_uri}" - curl -Lo ./kubectl "${kubectl_uri}" - fi - echo "kubectl already downloaded" - chmod +x ./kubectl - - KUBECTL="${WORK_DIR}/kubectl" - KIND="${WORK_DIR}/kind" - - cd - -} - -# start up a kind cluster -function setup_env() { - docker pull "kindest/node:v${K8S_VERSION}" - ${KIND} create cluster --image="kindest/node:v${K8S_VERSION}" --config="${ROOT_DIR}/hack/e2e/kind.yaml" --name=devstream-e2e -} - -init -check_deps -fetch_tools -setup_env diff --git a/hack/e2e/kind.yaml b/hack/e2e/kind.yaml deleted file mode 100644 index 6c7e1e769..000000000 --- a/hack/e2e/kind.yaml +++ /dev/null @@ -1,9 +0,0 @@ -kind: Cluster -apiVersion: kind.x-k8s.io/v1alpha4 -nodes: -- role: control-plane - extraPortMappings: - - containerPort: 8080 - hostPort: 8080 - listenAddress: "0.0.0.0" - protocol: tcp diff --git a/hack/githooks/commit-msg b/hack/githooks/commit-msg deleted file mode 100755 index 0185dc3ce..000000000 --- a/hack/githooks/commit-msg +++ /dev/null @@ -1,51 +0,0 @@ -#!/bin/sh - -# ignore merge commits -MERGE_MSG=`cat $1 | egrep '^Merge branch*'` - -if [ "$MERGE_MSG" != "" ]; then - exit 0 -fi - - -# check for a valid commit message -COMMIT_MSG=`cat $1 | egrep "^(feat|fix|doc|docs|chore|build|ci|perf|style|revert|test|refactor)(\(\w+\))?!?:\s(\S|\w)+"` - -if [ "$COMMIT_MSG" = "" ]; then - echo -e " - \033[31mError!\033[0m The commit message should be structured as follows: - \033[31m - [optional scope]: - [optional body] - [optional footer(s)] - \033[0m - where \"type\" can be: \033[32m feat fix doc docs chore ci build perf style revert test refactor \033[0m - For details, please refer to \033[34m https://www.conventionalcommits.org/en/v1.0.0/#summary \033[0m - " - exit 1 -fi - -NAME=$(git config user.name) -EMAIL=$(git config user.email) - -if [ -z "$NAME" ]; then - echo "please config user.name" - exit 1 -fi - -if [ -z "$EMAIL" ]; then - echo "please config user.email" - exit 1 -fi - -# add sign off -git interpret-trailers --if-exists doNothing --trailer \ - "Signed-off-by: $NAME <$EMAIL>" \ - --in-place "$1" - -# check duplicate sign off -test "" = "$(grep '^Signed-off-by: ' "$1" | - sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { - echo >&2 Duplicate Signed-off-by lines. - exit 1 -} diff --git a/hack/image-pull-push.sh b/hack/image-pull-push.sh deleted file mode 100644 index 2c3ec2fba..000000000 --- a/hack/image-pull-push.sh +++ /dev/null @@ -1,122 +0,0 @@ -#!/usr/bin/env bash - -CurrentDIR=$(cd "$(dirname "$0")" || exit;pwd) - -func() { - echo "\"image-pull-push.sh\" is used for pull images from dockerhub and push images to private image registry." - echo "" - echo "Usage:" - echo "1. Download images from dockerhub and save them to a tarball:" - echo "./image-pull-push.sh -f images-list.txt -d dtm-images -r harbor.devstream.io -s" - echo "" - echo "2. Load images from tarball locally:" - echo "./image-pull-push.sh -f images-list.txt -d dtm-images -l" - echo "" - echo "3. Upload images to private image registry:" - echo "./image-pull-push.sh -f images-list.txt -d dtm-images -r harbor.devstream.io -u" - echo "" - echo "4. Load and upload at the same time:" - echo "./image-pull-push.sh -f images-list.txt -d dtm-images -r harbor.devstream.io -l -u" - echo "" - echo "5. Short command with default config: ./image-pull-push.sh -f images-list.txt -l -u" - echo "" - echo " $0 [-f IMAGES-LIST-FILE] [-d IMAGES-DIR] [-r PRIVATE-REGISTRY] [-s | -l | -u]" - echo "" - echo "Description:" - echo " -f IMAGES-LIST-FILE : text file with image list." - echo " -r PRIVATE-REGISTRY : target private registry addr. (default: harbor.devstream.io)" - echo " -d IMAGES-DIR : a directory where images.tar.gz is saved. (default: dtm-images)" - echo " -s : save images" - echo " -l : load images" - echo " -u : upload images" - echo "" - echo "Notice: pigz should be installed first. (yum install pigz)" - exit -} - -save='false' -load='false' -upload='false' - -while getopts 'f:r:d:sluh' OPT; do - case $OPT in - f) ImagesListFile="$OPTARG";; - r) RegistryUrl="$OPTARG";; - d) ImagesDir="$OPTARG";; - s) save='true';; - l) load='true';; - u) upload='true';; - h) func;; - ?) func;; - *) func;; - esac -done - -if [[ -z $RegistryUrl ]]; then - RegistryUrl='harbor.devstream.io' -fi - -if [[ -z $ImagesDir ]]; then - ImagesDir='dtm-images' -fi - -# save -if [[ $save == 'true' ]]; then - if [ ! -d ${ImagesDir} ]; then - mkdir -p ${ImagesDir} - fi - ImagesListLen=$(cat ${ImagesListFile} | grep -v ^$ | awk '{print}' | wc -l) - name="" - images="" - index=0 - for image in $(<${ImagesListFile}); do - if [[ ${image} =~ ^\#\#.* ]]; then - if [[ -n ${images} ]]; then - echo "" - echo "Save images: "${name}" to "${ImagesDir}"/"${name}".tar.gz <<<" - docker save ${images} | pigz > ${ImagesDir}"/"${name}.tar.gz - echo "" - fi - images="" - name=$(echo "${image}" | sed 's/#//g' | sed -e 's/[[:space:]]//g') - ((index++)) - continue - fi - - if [[ ${image} =~ ^----.* ]]; then - ((index++)) - continue - fi - - docker pull "${image}" - docker tag "${image}" "${RegistryUrl}/${image}" - images=${images}" "${RegistryUrl}/${image} - if [[ ${index} -eq ${ImagesListLen}-1 ]]; then - if [[ -n ${images} ]]; then - echo "Save images: "${name}" to "${ImagesDir}"/"${name}".tar.gz <<<" - echo "Images: ${images}" - docker save ${images} | pigz > ${ImagesDir}"/"${name}.tar.gz - fi - fi - ((index++)) - done -fi - -# load -if [[ $load == 'true' ]]; then - for image in $(<${ImagesListFile}); do - if [[ ${image} =~ ^\#\#.* ]]; then - docker load -i ${ImagesDir}/$(echo "${image}" | sed 's/#//g' | sed -e 's/[[:space:]]//g').tar.gz - fi - done -fi - -# push -if [[ $upload == 'true' ]]; then - for image in $(<${ImagesListFile}); do - if [[ ${image} =~ ^\#\#.* ]] || [[ ${image} =~ ^----.* ]]; then - continue - fi - docker push "${RegistryUrl}/${image}" - done -fi diff --git a/hack/install/download.sh b/hack/install/download.sh deleted file mode 100755 index af650c61e..000000000 --- a/hack/install/download.sh +++ /dev/null @@ -1,63 +0,0 @@ -#!/bin/bash - -function init() { - # todo customize dtm version, default is latest - if [ "$(uname)" == "Darwin" ];then - HOST_OS="darwin" - elif [ "$(uname)" == "Linux" ];then - HOST_OS="linux" - else - echo "Support Darwin/Linux OS only" - exit 1 - fi - - if [ "$(uname -m)" == "amd64" ] || [ "$(uname -m)" == "x86_64" ];then - HOST_ARCH="amd64" - elif [ "$(uname -m)" == "arm64" ];then - HOST_ARCH="arm64" - else - echo "Support amd64/arm64 CPU arch only" - exit 1 - fi - - echo "Got OS type: ${HOST_OS} and CPU arch: ${HOST_ARCH}" -} - -function getLatestReleaseVersion() { - # get latest release version from aws s3 - STORAGE_BASE_URL=https://download.devstream.io - LATEST_VERSION_FILE="latest_version" - - LATEST_VERSION=$(curl -fsSL ${STORAGE_BASE_URL}/${LATEST_VERSION_FILE}) - - if [ -z "${LATEST_VERSION}" ];then - echo "Failed to get latest release version" - exit 1 - fi - - echo "Got latest release version: ${LATEST_VERSION}" -} - -function downloadDtm() { - # 1. download the release and rename it to "dtm" - # 2. count the download count of the release - fullReleaseUrl="${STORAGE_BASE_URL}/${LATEST_VERSION}/dtm-${HOST_OS}-${HOST_ARCH}" - echo "Downloading dtm from: $fullReleaseUrl" - # use -L to follow redirects - curl -L -o dtm $fullReleaseUrl - echo "dtm downloaded completed\n" - - # grant execution rights - chmod +x dtm -} - -function showDtmHelp() { - echo "" - # show dtm help and double check the download is success - ./dtm help -} - -init -getLatestReleaseVersion -downloadDtm -showDtmHelp diff --git a/hack/release/auto-release-darwin-arm64.sh b/hack/release/auto-release-darwin-arm64.sh deleted file mode 100755 index 6342973bb..000000000 --- a/hack/release/auto-release-darwin-arm64.sh +++ /dev/null @@ -1,87 +0,0 @@ -#! /bin/bash -e -# usage: `sh auto-release-darwin-arm64.sh -t v0.6.0` -set -o nounset -# exit when any command fails -set -e - -tag="invalid" - -while getopts "t:" opt; do - case $opt in - t) - tag=$OPTARG - ;; - - ?) - echo "Options not used" - exit 1 - ;; - esac -done - -if [ "${tag}" == "invalid" ]; then - echo "Maybe you forgot to use -t flag. E.g. sh auto-release-darwin-arm64.sh -t v0.6.0" - exit 1 -fi -echo "tag: ${tag}" - -user=devstream-io -repo=devstream -github_token=$GITHUB_TOKEN -plugin_dir=~/.devstream/plugins - -GOOS=$(go env GOOS) -GOARCH=$(go env GOARCH) -DTM_CORE_BINARY=dtm-${GOOS}-${GOARCH} -STORAGE_BASE_URL=s3://download.devstream.io -STORAGE_URL_WITH_TAG=${STORAGE_BASE_URL}/${tag} - -if [ ! $tag ] || [ ! $user ] || [ ! $repo ] || [ ! $github_token ] || [ ! $plugin_dir ]; then - echo "The following variables cannot be empty!" - echo "tag="$tag - echo "user="$user - echo "repo="$repo - if [ ! $github_token ]; then - echo "github_token="$github_token - else - echo "github_token=***" - fi - echo "plugin_dir="$plugin_dir - exit -fi - -# call build core and plugins -cd ../.. -make clean -make build -j8 - -# install github-release for uploading -go install github.com/github-release/github-release@latest - -# upload dtm -echo "Uploading ${DTM_CORE_BINARY} ..." -# upload dtm to github release -github-release upload --security-token $github_token --user $user --repo $repo --tag $tag --file dtm --name ${DTM_CORE_BINARY} -# upload dtm to aws s3 -aws s3 cp dtm ${STORAGE_URL_WITH_TAG}/${DTM_CORE_BINARY} --acl public-read -echo "${DTM_CORE_BINARY} uploaded." - -# upload plugins and .md5 files -# In order to upload plug-ins to s3, you need to download aws cli. -# After downloading aws cli, you need to configure aws credentials. -pip3 install awscli -aws s3 cp $plugin_dir $STORAGE_URL_WITH_TAG --recursive --acl public-read - -# check if the number of plugins on s3 is correct -local_plugin_nums=$(./dtm list plugins |wc -l) -((local_plugin_file_nums=local_plugin_nums*6)) -s3_plugin_file_total_nums=$(aws s3 ls download.devstream.io/"$tag"/|awk '{print $NF}'|uniq|wc -l) -((s3_plugin_file_nums=s3_plugin_file_total_nums-3)) -echo "s3_plugin_file_nums:" "$s3_plugin_file_nums" -echo "local_plugin_file_nums:" "$local_plugin_file_nums" -if [ "$local_plugin_file_nums" -ne "$s3_plugin_file_nums" ] -then - echo "Attention,Maybe the plugin uploaded to s3 is not correct." -else - echo "The plugin uploaded to s3 is correct." -fi diff --git a/hack/release/update_download_sh.sh b/hack/release/update_download_sh.sh deleted file mode 100644 index f1867b853..000000000 --- a/hack/release/update_download_sh.sh +++ /dev/null @@ -1,9 +0,0 @@ -#! /bin/bash -e - -DOWNLOAD_SCRIPT_PATH="hack/install/download.sh" -STORAGE_BASE_URL=s3://download.devstream.io -DOWNLOAD_SCRIPT_S3_URL=${STORAGE_BASE_URL}/${DOWNLOAD_SCRIPT_PATH} - -# upload download.sh to aws s3 -echo "Uploading ${DOWNLOAD_SCRIPT_PATH} to ${DOWNLOAD_SCRIPT_S3_URL} ..." -aws s3 cp ${DOWNLOAD_SCRIPT_PATH} ${DOWNLOAD_SCRIPT_S3_URL} --acl public-read diff --git a/hack/release/update_dtm_version_on_s3.sh b/hack/release/update_dtm_version_on_s3.sh deleted file mode 100755 index 6b2d6b4b5..000000000 --- a/hack/release/update_dtm_version_on_s3.sh +++ /dev/null @@ -1,14 +0,0 @@ -#! /bin/bash -e - -version=$1 - -LATEST_VERSION_FILE="latest_version" -STORAGE_BASE_URL=s3://download.devstream.io - -# create latest_version file -echo "Saving latest version(${version}) to ${LATEST_VERSION_FILE} ..." -echo $version > ${LATEST_VERSION_FILE} - -# create or update latest_version.txt on s3 -aws s3 cp ${LATEST_VERSION_FILE} ${STORAGE_BASE_URL}/${LATEST_VERSION_FILE} --acl public-read -echo "${LATEST_VERSION_FILE} uploaded." diff --git a/hack/release/upload_dtm_core.sh b/hack/release/upload_dtm_core.sh deleted file mode 100755 index 633d34397..000000000 --- a/hack/release/upload_dtm_core.sh +++ /dev/null @@ -1,23 +0,0 @@ -#! /bin/bash -e - -github_token=$1 -tag=$2 -GOOS=$3 -GOARCH=$4 - -DTM_CORE_BINARY=dtm-${GOOS}-${GOARCH} -STORAGE_BASE_URL=s3://download.devstream.io -STORAGE_URL_WITH_TAG=${STORAGE_BASE_URL}/${tag} - -user=devstream-io -repo=devstream -plugin_dir=~/.devstream/plugins - -# upload dtm core -echo "Uploading ${DTM_CORE_BINARY} ..." -# upload dtm to github release -github-release upload --security-token $github_token --user $user --repo $repo --tag $tag --file dtm --name ${DTM_CORE_BINARY} -# upload dtm to aws s3 -aws s3 cp dtm ${STORAGE_URL_WITH_TAG}/${DTM_CORE_BINARY} --acl public-read -echo "${DTM_CORE_BINARY} uploaded." - diff --git a/hack/switch_k8s_dep_version.sh b/hack/switch_k8s_dep_version.sh deleted file mode 100644 index d73fecdf7..000000000 --- a/hack/switch_k8s_dep_version.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env bash - -# https://github.com/kubernetes/kubernetes/issues/79384#issuecomment-521493597 -# sh hack/switch_k8s_dep_version.sh v1.22.2 - -set -euo pipefail - -VERSION=${1#"v"} -if [ -z "$VERSION" ]; then - echo "Must specify version!" - exit 1 -fi -echo "Kubernetes version: v${VERSION}" - -MODS=($( - curl -sS https://raw.githubusercontent.com/kubernetes/kubernetes/v${VERSION}/go.mod | - sed -n 's|.*k8s.io/\(.*\) => ./staging/src/k8s.io/.*|k8s.io/\1|p' -)) -echo "Downloaded kubernetes/go.mod" - -for MOD in "${MODS[@]}"; do - V=$( - go mod download -json "${MOD}@kubernetes-${VERSION}" | - sed -n 's|.*"Version": "\(.*\)".*|\1|p' - ) - go mod edit "-replace=${MOD}=${MOD}@${V}" -done - -go get "k8s.io/kubernetes@v${VERSION}" diff --git a/hack/terraform/.terraform.lock.hcl b/hack/terraform/.terraform.lock.hcl deleted file mode 100644 index 0905d7f97..000000000 --- a/hack/terraform/.terraform.lock.hcl +++ /dev/null @@ -1,44 +0,0 @@ -# This file is maintained automatically by "terraform init". -# Manual edits may be lost in future updates. - -provider "registry.terraform.io/cloudflare/cloudflare" { - version = "3.14.0" - constraints = "3.14.0" - hashes = [ - "h1:MP2Br1A6Q+PLUVLR90o5RO98FSSkEHLinaXinxdqAvY=", - "zh:36b071d2e57c21a22c40dc7684eadaecf0309fc11e6f76f7eac5f8de3e97f604", - "zh:6ba1023ec4ea2645f27981d20fdabbf64c692abd750c4d8a9b40ed02c608403d", - "zh:6e810d8360e8e16075f838ec496b04f728450c284ca4045a95b7f76736fba9e5", - "zh:7ff8d4f1e23b7dbd115be98e221522f584d556040cd893da5d1f6bbdd43d8c06", - "zh:841cdf57d02ca40fc33580a5c5cd081abf816826875c329d9b43c8467c7843a2", - "zh:901e572e15eaec3db9ff41c4e29759a4208577dd30a948d41927799963f1d037", - "zh:9abfa2624f639471d7469cfd8c081f8206e09c878f71f1965b526760b4351772", - "zh:a0c107a96628d67a901b74e549132d877e041b967f73b3913a271116bc2261c8", - "zh:b28b8614dcd89a97763cff988fdd3eb9f6f70f6daa81fd5112153bd8caccb51b", - "zh:b980e4e4567a12650ddc6300d7a9da1c110b3ad20c9e0325119ac00b04de6c4d", - "zh:bfc01a48e0befbbb200821e2f2e041e9b349fa0a55e3fcd4ec0a3ab1a57cf297", - "zh:c101a35703cef8165e9fad1d3379801a40b86c8c4652bb374b2d0f02a31e0124", - "zh:d37fd62d51789e10bd9b415db57b8bcffa10129cb3634d5428986955470162de", - "zh:e980e42a97815255c992973a43ba37a2e4a4a8d519e8cff8e015296f8ee36bf9", - ] -} - -provider "registry.terraform.io/hashicorp/aws" { - version = "4.3.0" - constraints = "4.3.0" - hashes = [ - "h1:+/UcPbNhOmn+DIsiGugJd3B8ruKgXfC2JxIX9PCCFJI=", - "h1:OePPETAA8BIGTCgGQ54F3oAZnOFH5lxyDdP0cyLXdU4=", - "zh:087c67e5429f343a164221c05a83f152322f411e7394f8a39ed81a75982af1f2", - "zh:2e852a1b107e5324524874e1cd98bcf3a69284b4fe04750aa373054177c54214", - "zh:4b9a54b5895f945827832e6ddd16ff107301fedf47acbd83d17d4e18bbf10bb1", - "zh:64dfc02bc85f5df2f51ff942fc78d72fcd0db17b0f53e1fae380e58adbd239b3", - "zh:766f9aef619cfd23e924aee523791acccd30b6d8f1cc0ed1a7b5c953bf8c5392", - "zh:90048d87ff3071a4356cf91916b46a7ec69ba55bcba5765b598d3fe545d4c6ca", - "zh:c51f5b238af37c63e9033a12fd7fedc87c03eb966f5f5c7786eb6246e8bf3071", - "zh:d0df94d3112a25de609dfb55c5e3b0d119dea519a2bdd8099e64a8d63f22b683", - "zh:de166ecfeed70f570cea72ec094f00c2f997496b3226fa08518e7cd4a73884e1", - "zh:e31c31d00f42ea2dbaab1ad4c245da5cfff63e28399b5a5795b5e6a826c6c8af", - "zh:f93725afd8410194ede51d83505327aa1ae6a9b4280cf31db649c62c7dc203ae", - ] -} diff --git a/hack/terraform/bucket-policy.tf b/hack/terraform/bucket-policy.tf deleted file mode 100644 index b096c1e38..000000000 --- a/hack/terraform/bucket-policy.tf +++ /dev/null @@ -1,46 +0,0 @@ -resource "aws_s3_bucket_policy" "policy_iterable" { - bucket = "download.devstream.io" - policy = jsonencode({ - "Version" : "2012-10-17", - "Statement" : [ - { - "Sid" : "PublicReadGetObjectOnlyForCloudFlareIps111", - "Effect" : "Deny", - "Principal" : "*", - "Action" : "s3:GetObject", - "Resource" : [ - "arn:aws:s3:::download.devstream.io", - "arn:aws:s3:::download.devstream.io/*" - ], - "Condition" : { - "NotIpAddress" : { - "aws:SourceIp" : [ - "2400:cb00::/32", - "2606:4700::/32", - "2803:f800::/32", - "2405:b500::/32", - "2405:8100::/32", - "2a06:98c0::/29", - "2c0f:f248::/32", - "173.245.48.0/20", - "103.21.244.0/22", - "103.22.200.0/22", - "103.31.4.0/22", - "141.101.64.0/18", - "108.162.192.0/18", - "190.93.240.0/20", - "188.114.96.0/20", - "197.234.240.0/22", - "198.41.128.0/17", - "162.158.0.0/15", - "104.16.0.0/13", - "104.24.0.0/14", - "172.64.0.0/13", - "131.0.72.0/22" - ] - } - } - } - ] - }) -} diff --git a/hack/terraform/config.tf b/hack/terraform/config.tf deleted file mode 100644 index e180228eb..000000000 --- a/hack/terraform/config.tf +++ /dev/null @@ -1,23 +0,0 @@ -terraform { - required_version = ">= 1.1.7" - - required_providers { - aws = { - source = "hashicorp/aws" - version = "4.3.0" - } - cloudflare = { - source = "cloudflare/cloudflare" - version = "3.14.0" - } - } - - backend "s3" { - bucket = "devstream-terraform-state" - key = "test.tfstate" - region = "ap-southeast-1" - } -} -provider "aws" { - region = "ap-southeast-1" -} diff --git a/hack/terraform/devstream-iam.tf b/hack/terraform/devstream-iam.tf deleted file mode 100644 index dd06e809e..000000000 --- a/hack/terraform/devstream-iam.tf +++ /dev/null @@ -1,194 +0,0 @@ -resource "aws_iam_group" "devstream" { - name = "DevStream" - path = "/" -} - -resource "aws_iam_group_policy" "devstream-enforce-mfa" { - name = "devstream_enforce_mfa" - group = aws_iam_group.devstream.name - - policy = jsonencode({ - "Version" : "2012-10-17", - "Statement" : [ - { - "Sid" : "AllowViewAccountInfo", - "Effect" : "Allow", - "Action" : [ - "iam:GetAccountPasswordPolicy", - "iam:GetAccountSummary", - "iam:ListVirtualMFADevices" - ], - "Resource" : "*" - }, - { - "Sid" : "AllowManageOwnPasswords", - "Effect" : "Allow", - "Action" : [ - "iam:ChangePassword", - "iam:GetUser" - ], - "Resource" : "arn:aws:iam::*:user/$${aws:username}" - }, - { - "Sid" : "AllowManageOwnAccessKeys", - "Effect" : "Allow", - "Action" : [ - "iam:CreateAccessKey", - "iam:DeleteAccessKey", - "iam:ListAccessKeys", - "iam:UpdateAccessKey" - ], - "Resource" : "arn:aws:iam::*:user/$${aws:username}" - }, - { - "Sid" : "AllowManageOwnSigningCertificates", - "Effect" : "Allow", - "Action" : [ - "iam:DeleteSigningCertificate", - "iam:ListSigningCertificates", - "iam:UpdateSigningCertificate", - "iam:UploadSigningCertificate" - ], - "Resource" : "arn:aws:iam::*:user/$${aws:username}" - }, - { - "Sid" : "AllowManageOwnSSHPublicKeys", - "Effect" : "Allow", - "Action" : [ - "iam:DeleteSSHPublicKey", - "iam:GetSSHPublicKey", - "iam:ListSSHPublicKeys", - "iam:UpdateSSHPublicKey", - "iam:UploadSSHPublicKey" - ], - "Resource" : "arn:aws:iam::*:user/$${aws:username}" - }, - { - "Sid" : "AllowManageOwnGitCredentials", - "Effect" : "Allow", - "Action" : [ - "iam:CreateServiceSpecificCredential", - "iam:DeleteServiceSpecificCredential", - "iam:ListServiceSpecificCredentials", - "iam:ResetServiceSpecificCredential", - "iam:UpdateServiceSpecificCredential" - ], - "Resource" : "arn:aws:iam::*:user/$${aws:username}" - }, - { - "Sid" : "AllowManageOwnVirtualMFADevice", - "Effect" : "Allow", - "Action" : [ - "iam:CreateVirtualMFADevice", - "iam:DeleteVirtualMFADevice" - ], - "Resource" : "arn:aws:iam::*:mfa/$${aws:username}" - }, - { - "Sid" : "AllowManageOwnUserMFA", - "Effect" : "Allow", - "Action" : [ - "iam:DeactivateMFADevice", - "iam:EnableMFADevice", - "iam:ListMFADevices", - "iam:ResyncMFADevice" - ], - "Resource" : "arn:aws:iam::*:user/$${aws:username}" - } - ] - }) -} - -resource "aws_iam_group_policy_attachment" "devstream-iam" { - group = aws_iam_group.devstream.name - policy_arn = "arn:aws:iam::aws:policy/IAMReadOnlyAccess" -} - -resource "aws_iam_group_policy" "devstream-eks" { - name = "devstream-eks" - group = aws_iam_group.devstream.name - - policy = jsonencode({ - "Version" : "2012-10-17", - "Statement" : [ - { - "Effect" : "Allow", - "Action" : [ - "eks:DescribeNodegroup", - "eks:ListNodegroups", - "eks:DescribeCluster", - "eks:ListClusters", - "eks:AccessKubernetesApi", - "ssm:GetParameter", - "eks:ListUpdates", - "eks:ListFargateProfiles" - ], - "Resource" : "*" - } - ] - }) -} - -resource "aws_iam_group_policy" "DevStream-Download-Bucket-RW-Policy" { - name = "DevStream-Download-Bucket-RW-Policy" - group = aws_iam_group.devstream.name - - policy = jsonencode({ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "PermissionForDownloadUploadForTheBucket", - "Effect": "Allow", - "Action": [ - "s3:GetObject", - "s3:GetObjectAcl", - "s3:PutObject", - "s3:PutObjectAcl", - "s3:DeleteObject", - "s3:RestoreObject", - "s3:ListBucket", - "s3:GetBucketPolicy", - "s3:ReplicateObject", - "s3:GetBucketWebsite", - "s3:PutBucketWebsite", - "s3:GetBucketCORS", - ], - "Resource": [ - "arn:aws:s3:::download.devstream.io", - "arn:aws:s3:::download.devstream.io/*" - ] - } - ] - }) -} - -locals { - users = ["fangbao", "hutao"] -} - -resource "aws_iam_user" "devstream" { - for_each = toset(local.users) - - name = each.key - force_destroy = true -} - -resource "aws_iam_user_login_profile" "devstream" { - for_each = toset(local.users) - - user = aws_iam_user.devstream[each.key].name - # password_reset_required = true -} - -output "users" { - value = { - for k, v in aws_iam_user_login_profile.devstream : k => v.password - } -} - -resource "aws_iam_user_group_membership" "devstream" { - for_each = toset(local.users) - - user = aws_iam_user.devstream[each.key].name - groups = [aws_iam_group.devstream.name] -} diff --git a/hack/terraform/github-actions-iam.tf b/hack/terraform/github-actions-iam.tf deleted file mode 100644 index cdd6eb9d3..000000000 --- a/hack/terraform/github-actions-iam.tf +++ /dev/null @@ -1,67 +0,0 @@ -resource "aws_iam_user" "githubactions" { - name = "devstream-github-actions" - force_destroy = true -} - -data "aws_iam_policy_document" "githubactions" { - statement { - actions = [ - "eks:DescribeCluster", - "eks:AccessKubernetesApi", - ] - resources = [module.cluster.cluster_arn] - } -} - -resource "aws_iam_user_policy" "githubactions" { - name = "devstream-githubactions-eks-policy" - user = aws_iam_user.githubactions.name - policy = data.aws_iam_policy_document.githubactions.json -} - -resource "aws_iam_user_policy" "GitHubActions-Download-Bucket-RW-Policy" { - name = "GitHubActions-Download-Bucket-RW-Policy" - user = aws_iam_user.githubactions.name - - policy = jsonencode({ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "PermissionForDownloadUploadForTheBucket", - "Effect": "Allow", - "Action": [ - "s3:GetObject", - "s3:GetObjectAcl", - "s3:PutObject", - "s3:PutObjectAcl", - "s3:DeleteObject", - "s3:RestoreObject", - "s3:ListBucket", - "s3:GetBucketPolicy", - "s3:ReplicateObject", - "s3:GetBucketWebsite", - "s3:PutBucketWebsite", - "s3:GetBucketCORS", - ], - "Resource": [ - "arn:aws:s3:::download.devstream.io", - "arn:aws:s3:::download.devstream.io/*" - ] - } - ] - }) -} - -resource "aws_iam_access_key" "githubactions" { - user = aws_iam_user.githubactions.name -} - -output "githubactions_iam_id" { - sensitive = true - value = aws_iam_access_key.githubactions.id -} - -output "githubactions_iam_secret" { - sensitive = true - value = aws_iam_access_key.githubactions.secret -} diff --git a/hack/terraform/main.tf b/hack/terraform/main.tf deleted file mode 100644 index 15dfb71f9..000000000 --- a/hack/terraform/main.tf +++ /dev/null @@ -1,33 +0,0 @@ -module "network" { - source = "./modules/networking" - - vpc_name = "DevStream" - vpc_cidr_block = "10.0.0.0/16" - - public_subnets = { - "ap-southeast-1a" = "10.0.1.0/24" - "ap-southeast-1b" = "10.0.2.0/24" - } - - private_subnets = { - "ap-southeast-1a" = "10.0.11.0/24" - "ap-southeast-1b" = "10.0.12.0/24" - } - - team = "DevStream" -} - -module "cluster" { - source = "./modules/eks" - - cluster_name = "dtm-test" - nodegroup_name = "dtm-test-1" - vpc_id = module.network.vpc_id - worker_subnet_ids = module.network.private_subnet_ids - worker_instance_type = "t2.medium" - min_worker_node_number = 1 - desired_worker_node_number = 1 - max_worker_node_number = 1 - - team = "DevStream" -} diff --git a/hack/terraform/modules/eks/README.md b/hack/terraform/modules/eks/README.md deleted file mode 100644 index 4ae734d17..000000000 --- a/hack/terraform/modules/eks/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# AWS EKS Module - -This module creates an EKS cluster with managed node group. diff --git a/hack/terraform/modules/eks/cluster.tf b/hack/terraform/modules/eks/cluster.tf deleted file mode 100644 index 583270b1f..000000000 --- a/hack/terraform/modules/eks/cluster.tf +++ /dev/null @@ -1,49 +0,0 @@ -resource "aws_security_group" "cluster" { - name = "${var.cluster_name}_eks_cluster_sg" - description = "EKS cluster security group." - vpc_id = var.vpc_id - - tags = { - Name = "${var.cluster_name}_eks_cluster_sg" - Team = var.team - } -} - -resource "aws_security_group_rule" "cluster_egress_internet" { - description = "Allow cluster egress access to the Internet." - protocol = "-1" - security_group_id = aws_security_group.cluster.id - cidr_blocks = ["0.0.0.0/0"] - from_port = 0 - to_port = 0 - type = "egress" -} - -resource "aws_security_group_rule" "cluster_https_worker_ingress" { - description = "Allow pods to communicate with the EKS cluster API." - protocol = "tcp" - security_group_id = aws_security_group.cluster.id - source_security_group_id = aws_security_group.worker.id - from_port = 443 - to_port = 443 - type = "ingress" -} - -resource "aws_eks_cluster" "cluster" { - depends_on = [ - aws_iam_role_policy_attachment.AmazonEKSClusterPolicy, - aws_iam_role_policy_attachment.AmazonEKSServicePolicy, - ] - version = var.k8s_version - name = var.cluster_name - role_arn = aws_iam_role.eks_role.arn - vpc_config { - subnet_ids = var.worker_subnet_ids - security_group_ids = [aws_security_group.cluster.id] - } - enabled_cluster_log_types = ["api", "audit", "authenticator", "controllerManager", "scheduler"] - - tags = { - Team = var.team - } -} diff --git a/hack/terraform/modules/eks/cluster_roles.tf b/hack/terraform/modules/eks/cluster_roles.tf deleted file mode 100644 index b3741ff9e..000000000 --- a/hack/terraform/modules/eks/cluster_roles.tf +++ /dev/null @@ -1,33 +0,0 @@ -data "aws_iam_policy_document" "eks-assume-role-policy" { - statement { - actions = [ - "sts:AssumeRole" - ] - - principals { - type = "Service" - identifiers = ["eks.amazonaws.com"] - } - } -} - -resource "aws_iam_role" "eks_role" { - name = "eks-cluster-${var.cluster_name}-role" - assume_role_policy = data.aws_iam_policy_document.eks-assume-role-policy.json - - max_session_duration = 43200 - - tags = { - Team = var.team - } -} - -resource "aws_iam_role_policy_attachment" "AmazonEKSClusterPolicy" { - policy_arn = "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy" - role = aws_iam_role.eks_role.name -} - -resource "aws_iam_role_policy_attachment" "AmazonEKSServicePolicy" { - policy_arn = "arn:aws:iam::aws:policy/AmazonEKSServicePolicy" - role = aws_iam_role.eks_role.name -} diff --git a/hack/terraform/modules/eks/control_plane_sg.tf b/hack/terraform/modules/eks/control_plane_sg.tf deleted file mode 100644 index 796a0e7ad..000000000 --- a/hack/terraform/modules/eks/control_plane_sg.tf +++ /dev/null @@ -1,12 +0,0 @@ -resource "aws_security_group" "control_plane" { - name = "eks_cluster_${var.cluster_name}_control_plane_sg" - description = "EKS cluster ${var.cluster_name} control plane security group." - - vpc_id = var.vpc_id - - tags = { - Name = "eks_cluster_${var.cluster_name}_control_plane_sg" - Team = var.team - - } -} diff --git a/hack/terraform/modules/eks/nodegroup.tf b/hack/terraform/modules/eks/nodegroup.tf deleted file mode 100644 index 24669946f..000000000 --- a/hack/terraform/modules/eks/nodegroup.tf +++ /dev/null @@ -1,24 +0,0 @@ -resource "aws_eks_node_group" "default" { - depends_on = [ - aws_iam_role_policy_attachment.eks_worker_AmazonEKSWorkerNodePolicy, - aws_iam_role_policy_attachment.eks_worker_AmazonEKS_CNI_Policy, - aws_iam_role_policy_attachment.eks_worker_AmazonEC2ContainerRegistryReadOnly, - aws_iam_role_policy_attachment.eks_worker_autoscaling - ] - - cluster_name = aws_eks_cluster.cluster.name - node_group_name = "${aws_eks_cluster.cluster.name}-managed-${var.nodegroup_name}" - node_role_arn = aws_iam_role.worker_role.arn - subnet_ids = var.worker_subnet_ids - instance_types = [var.worker_instance_type] - - scaling_config { - min_size = var.min_worker_node_number - desired_size = var.desired_worker_node_number - max_size = var.max_worker_node_number - } - - tags = { - Team = var.team - } -} diff --git a/hack/terraform/modules/eks/output.tf b/hack/terraform/modules/eks/output.tf deleted file mode 100644 index ff1b951db..000000000 --- a/hack/terraform/modules/eks/output.tf +++ /dev/null @@ -1,23 +0,0 @@ -output "cluster_name" { - value = aws_eks_cluster.cluster.id -} - -output "cluster_arn" { - value = aws_eks_cluster.cluster.arn -} - -output "endpoint" { - value = aws_eks_cluster.cluster.endpoint -} - -output "kubeconfig_certificate_authority_data" { - value = aws_eks_cluster.cluster.certificate_authority[0].data -} - -output "security_group_id" { - value = aws_eks_cluster.cluster.vpc_config[0].cluster_security_group_id -} - -output "worker_node_role_name" { - value = aws_iam_role.worker_role.name -} diff --git a/hack/terraform/modules/eks/variables.tf b/hack/terraform/modules/eks/variables.tf deleted file mode 100644 index baa13a5ea..000000000 --- a/hack/terraform/modules/eks/variables.tf +++ /dev/null @@ -1,52 +0,0 @@ -# mandatory variables - -# cluster -variable "cluster_name" { - type = string -} - -variable "nodegroup_name" { - type = string -} - -# networking -variable "vpc_id" { - description = "The existing VPC" -} - -variable "worker_subnet_ids" { - type = list(any) - description = "the subnets where to deploy EKS" -} - -# optional variables -variable "k8s_version" { - type = string - default = "1.21" -} - -variable "worker_instance_type" { - description = "worker type, like t2.medium" - type = string - default = "t2.medium" -} - -variable "min_worker_node_number" { - type = number - default = 2 -} - -variable "desired_worker_node_number" { - type = number - default = 2 -} - -variable "max_worker_node_number" { - type = number - default = 2 -} - -variable "team" { - type = string - description = "used for tagging, to which team the resource belongs" -} diff --git a/hack/terraform/modules/eks/worker_node_roles.tf b/hack/terraform/modules/eks/worker_node_roles.tf deleted file mode 100644 index 0f1635835..000000000 --- a/hack/terraform/modules/eks/worker_node_roles.tf +++ /dev/null @@ -1,87 +0,0 @@ -resource "aws_iam_role" "worker_role" { - name = "eks-cluster-${var.cluster_name}-worker-node-role" - path = "/" - - assume_role_policy = < is illegal", state.Backend) - } -} diff --git a/internal/pkg/backend/backend_suit_test.go b/internal/pkg/backend/backend_suit_test.go deleted file mode 100644 index 7029a76d0..000000000 --- a/internal/pkg/backend/backend_suit_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package backend_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestPlanmanager(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Pkg Backend Suite") -} diff --git a/internal/pkg/backend/backend_test.go b/internal/pkg/backend/backend_test.go deleted file mode 100644 index 88343d2b9..000000000 --- a/internal/pkg/backend/backend_test.go +++ /dev/null @@ -1,44 +0,0 @@ -package backend_test - -import ( - "os" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/internal/pkg/backend" - "github.com/devstream-io/devstream/internal/pkg/backend/local" - "github.com/devstream-io/devstream/internal/pkg/configmanager" -) - -var _ = Describe("GetBackend", func() { - When("use local backend", func() { - It("should return local backend struct", func() { - state := configmanager.State{Backend: "local"} - _, err := backend.GetBackend(state) - Expect(err).Error().ShouldNot(HaveOccurred()) - }) - - AfterEach(func() { - err := os.RemoveAll(local.DefaultStateFile) - Expect(err).NotTo(HaveOccurred()) - }) - }) - - // TODO: add mock s3 test - When("use unknown backend", func() { - It("should return err", func() { - state := configmanager.State{Backend: "not_exist_plug"} - _, err := backend.GetBackend(state) - Expect(err).Error().Should(HaveOccurred()) - }) - }) - - When("s3 config is empty", func() { - It("should return err of backendOptionErr", func() { - state := configmanager.State{Backend: "s3"} - _, err := backend.GetBackend(state) - Expect(err).Error().Should(HaveOccurred()) - }) - }) -}) diff --git a/internal/pkg/backend/k8s/backend.go b/internal/pkg/backend/k8s/backend.go deleted file mode 100644 index 2e5c9e993..000000000 --- a/internal/pkg/backend/k8s/backend.go +++ /dev/null @@ -1,70 +0,0 @@ -package k8s - -import ( - "sync" - - "github.com/devstream-io/devstream/pkg/util/k8s" - "github.com/devstream-io/devstream/pkg/util/log" -) - -const ( - defaultNamespace = "devstream" - defaultConfigMapName = "state" - stateKey = "state" -) - -type Backend struct { - mu sync.Mutex - - namespace string - configMapName string - - client k8s.K8sAPI -} - -// NewBackend returns a backend which uses ConfigMap to store data -func NewBackend(namespace, configMapName string) (*Backend, error) { - // default value - if namespace == "" { - namespace = defaultNamespace - } - if configMapName == "" { - configMapName = defaultConfigMapName - } - - log.Infof("Using configmap backend. Namespace: %s, ConfigMap name: %s.", namespace, configMapName) - - // create client and return - c, err := k8s.NewClient() - if err != nil { - return nil, err - } - - b := &Backend{ - namespace: namespace, - configMapName: configMapName, - client: c, - } - - return b, nil -} - -func (b *Backend) Read() ([]byte, error) { - b.mu.Lock() - defer b.mu.Unlock() - - configMap, err := b.getOrCreateConfigMap() - if err != nil { - return nil, err - } - - return []byte(configMap.Data[stateKey]), nil -} - -func (b *Backend) Write(data []byte) error { - b.mu.Lock() - defer b.mu.Unlock() - - _, err := b.applyConfigMap(string(data)) - return err -} diff --git a/internal/pkg/backend/k8s/configmap.go b/internal/pkg/backend/k8s/configmap.go deleted file mode 100644 index 162f14c38..000000000 --- a/internal/pkg/backend/k8s/configmap.go +++ /dev/null @@ -1,60 +0,0 @@ -package k8s - -import ( - v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" - - "github.com/devstream-io/devstream/pkg/util/log" -) - -func (b *Backend) applyConfigMap(content string) (*v1.ConfigMap, error) { - if err := b.client.UpsertNameSpace(b.namespace); err != nil { - return nil, err - } - - // build configmap object - labels := map[string]string{ - "app.kubernetes.io/name": b.configMapName, - "app.kubernetes.io/managed-by": "DevStream", - "created_by": "DevStream", - } - data := map[string]string{ - stateKey: content, - } - // apply configmap - configMapRes, err := b.client.ApplyConfigMap(b.configMapName, b.namespace, data, labels) - if err != nil { - return nil, err - } - log.Debugf("configmap %s created, detail: %s", configMapRes.Name, configMapRes.String()) - - return configMapRes, nil -} - -func (b *Backend) getOrCreateConfigMap() (*v1.ConfigMap, error) { - configMap, exist, err := b.getConfigMap() - if err != nil { - return nil, err - } - if exist { - return configMap, nil - } - - // if configmap not exist, create it - log.Infof("configmap %s in namespace %s not exist, will create it", b.configMapName, b.namespace) - return b.applyConfigMap("") -} - -func (b *Backend) getConfigMap() (cm *v1.ConfigMap, exist bool, err error) { - configMap, err := b.client.GetConfigMap(b.configMapName, b.namespace) - // if configmap not exist, return nil - if errors.IsNotFound(err) { - return nil, false, nil - } - if err != nil { - return nil, false, err - } - - log.Debugf("configmap %s in namespace %s found, detail: %v", configMap.Name, configMap.Namespace, configMap) - return configMap, true, nil -} diff --git a/internal/pkg/backend/local/local.go b/internal/pkg/backend/local/local.go deleted file mode 100644 index ff8738ea1..000000000 --- a/internal/pkg/backend/local/local.go +++ /dev/null @@ -1,75 +0,0 @@ -package local - -import ( - "errors" - "os" - "path/filepath" - "sync" - - "github.com/devstream-io/devstream/pkg/util/log" -) - -const DefaultStateFile = "devstream.state" - -// Local is a default implement for backend.Backend -type Local struct { - mu sync.Mutex - filename string -} - -// NewLocal will use DefaultStateFile as statemanager file if filename is not given. -func NewLocal(baseDir, filename string) (*Local, error) { - var lFile = filename - if filename == "" { - log.Debugf("The stateFile has not been set, default value %s will be used.", DefaultStateFile) - lFile = DefaultStateFile - } - - // if state file is not absolute path, use baseDir as prefix. - if !filepath.IsAbs(lFile) { - lFile = filepath.Join(baseDir, lFile) - } - - log.Infof("Using local backend. State file: %s.", filename) - - if _, err := os.Stat(lFile); errors.Is(err, os.ErrNotExist) { - file, err := os.Create(lFile) - if err != nil { - log.Fatalf("Creating state file %s failed.", lFile) - } - log.Debugf("The state file %s have been created.", lFile) - defer file.Close() - } - - return &Local{ - filename: lFile, - }, nil -} - -// Read is used to retrieve the data from local file. -func (l *Local) Read() ([]byte, error) { - l.mu.Lock() - defer l.mu.Unlock() - - data, err := os.ReadFile(l.filename) - if err != nil { - return nil, err - } - - return data, nil -} - -// Write is used to write the data to local file. -func (l *Local) Write(data []byte) error { - l.mu.Lock() - defer l.mu.Unlock() - - if err := os.MkdirAll(filepath.Dir(l.filename), 0755); err != nil { - return err - } - - if err := os.WriteFile(l.filename, data, 0644); err != nil { - return err - } - return nil -} diff --git a/internal/pkg/backend/local/local_suit_test.go b/internal/pkg/backend/local/local_suit_test.go deleted file mode 100644 index 90cb33904..000000000 --- a/internal/pkg/backend/local/local_suit_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package local_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestPlanmanager(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Pkg Backend Suite") -} diff --git a/internal/pkg/backend/local/local_test.go b/internal/pkg/backend/local/local_test.go deleted file mode 100644 index 68ffe374b..000000000 --- a/internal/pkg/backend/local/local_test.go +++ /dev/null @@ -1,105 +0,0 @@ -package local_test - -import ( - "os" - "path/filepath" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/internal/pkg/backend/local" -) - -var _ = Describe("NewLocal func", func() { - var tFile, tempDir string - - BeforeEach(func() { - tempDir = GinkgoT().TempDir() - tFile = "test_state_file" - }) - - When("specify dir and relative filename", func() { - It("should create state file", func() { - _, err := local.NewLocal(tempDir, tFile) - Expect(err).Should(Succeed()) - _, err = os.Stat(filepath.Join(tempDir, tFile)) - Expect(err).Error().ShouldNot(HaveOccurred()) - }) - }) - - When("current dir and multilevel file", func() { - It("should create state file", func() { - fileLoc := filepath.Join(tempDir, tFile) - _, err := local.NewLocal(".", fileLoc) - Expect(err).Should(Succeed()) - _, err = os.Stat(fileLoc) - Expect(err).Error().ShouldNot(HaveOccurred()) - }) - }) - - When("specify absolute file", func() { - It("should create state file", func() { - fileLoc := filepath.Join(tempDir, tFile) - fileLoc, err := filepath.Abs(fileLoc) - Expect(err).Should(Succeed()) - _, err = local.NewLocal("/not/active", fileLoc) - Expect(err).Should(Succeed()) - _, err = os.Stat(fileLoc) - Expect(err).Error().ShouldNot(HaveOccurred()) - }) - }) -}) - -var _ = Describe("Local struct", func() { - var ( - tFile, tFileLoc, tempDir string - tLocal *local.Local - err error - ) - - BeforeEach(func() { - tempDir = GinkgoT().TempDir() - tFile = "test_state_file" - tFileLoc = filepath.Join(tempDir, tFile) - tLocal, err = local.NewLocal(".", tFileLoc) - Expect(err).ShouldNot(HaveOccurred()) - }) - - Describe("Read method", func() { - var testData []byte - - BeforeEach(func() { - testData = []byte("this is test data") - err := os.WriteFile(tFileLoc, testData, 0644) - Expect(err).Error().ShouldNot(HaveOccurred()) - }) - - It("should return file content", func() { - fileData, err := tLocal.Read() - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(fileData).Should(Equal(testData)) - }) - }) - - Describe("Write method", func() { - var writeData []byte - - BeforeEach(func() { - writeData = []byte("this is write test") - }) - - It("should write data to file", func() { - err := tLocal.Write(writeData) - Expect(err).Error().ShouldNot(HaveOccurred()) - fileData, err := os.ReadFile(tFileLoc) - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(fileData).Should(Equal(writeData)) - }) - }) - - // After each test, clean file content - AfterEach(func() { - err := os.Truncate(tFileLoc, 0) - Expect(err).Error().ShouldNot(HaveOccurred()) - }) -}) diff --git a/internal/pkg/backend/s3/s3.go b/internal/pkg/backend/s3/s3.go deleted file mode 100644 index 3a905b84b..000000000 --- a/internal/pkg/backend/s3/s3.go +++ /dev/null @@ -1,43 +0,0 @@ -package s3 - -import ( - "context" - - "github.com/devstream-io/devstream/pkg/util/cloud/aws/s3" - "github.com/devstream-io/devstream/pkg/util/log" -) - -type S3Backend struct { - file *s3.S3File -} - -func NewS3Backend(bucket, region, key string) (*S3Backend, error) { - if err := validate(bucket, region, key); err != nil { - return nil, err - } - - log.Infof("Using s3 backend. Bucket: %s, region: %s, key: %s.", bucket, region, key) - - ctx := context.Background() - client, err := s3.NewClient(ctx, region) - if err != nil { - log.Fatalf("Creating s3 client failed: %s.", err) - } - - file, err := s3.NewS3File(ctx, client, bucket, region, key) - if err != nil { - log.Fatalf("Creating remote state file %s failed.", key) - } - - return &S3Backend{ - file: file, - }, nil -} - -func (b *S3Backend) Read() ([]byte, error) { - return b.file.Get() -} - -func (b *S3Backend) Write(data []byte) error { - return b.file.Put(data) -} diff --git a/internal/pkg/backend/s3/s3_suite_test.go b/internal/pkg/backend/s3/s3_suite_test.go deleted file mode 100644 index bd0c6b8a6..000000000 --- a/internal/pkg/backend/s3/s3_suite_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package s3_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestS3(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "S3 Suite") -} diff --git a/internal/pkg/backend/s3/validate.go b/internal/pkg/backend/s3/validate.go deleted file mode 100644 index 5b8bfdd00..000000000 --- a/internal/pkg/backend/s3/validate.go +++ /dev/null @@ -1,29 +0,0 @@ -package s3 - -import ( - "fmt" - - "github.com/devstream-io/devstream/pkg/util/log" -) - -func validate(bucket, region, key string) error { - var errs []error - if bucket == "" { - errs = append(errs, fmt.Errorf("state s3 Bucket is empty")) - } - if region == "" { - errs = append(errs, fmt.Errorf("state s3 Region is empty")) - } - if key == "" { - errs = append(errs, fmt.Errorf("state s3 Key is empty")) - } - - if len(errs) > 0 { - for _, err := range errs { - log.Error(err) - } - return fmt.Errorf("s3 Backend config error") - } - - return nil -} diff --git a/internal/pkg/backend/s3/validate_test.go b/internal/pkg/backend/s3/validate_test.go deleted file mode 100644 index accedb9f6..000000000 --- a/internal/pkg/backend/s3/validate_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package s3 - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -var _ = Describe("validate", func() { - It("should return error s3 option not config", func() { - err := validate("", "", "") - Expect(err).Error().Should(HaveOccurred()) - }) - - It("should return true if config s3 valid", func() { - bucket := "test_bucket" - region := "test_region" - key := "test_key" - - err := validate(bucket, region, key) - Expect(err).Error().ShouldNot(HaveOccurred()) - }) -}) diff --git a/internal/pkg/commit/commit.go b/internal/pkg/commit/commit.go new file mode 100644 index 000000000..5462ae20e --- /dev/null +++ b/internal/pkg/commit/commit.go @@ -0,0 +1,29 @@ +package commit + +import ( + "fmt" + "os/exec" + "strings" + + "github.com/devstream-io/devstream/internal/log" +) + +// Commit is used to execute git commit operations +func Commit(message string) error { + // Check if the git command exists + gitPath, err := exec.LookPath("git") + if err != nil { + return fmt.Errorf("git command not found: %w", err) + } + + cmd := exec.Command(gitPath, "commit", "-m", message) + output, err := cmd.CombinedOutput() + outputStr := strings.TrimSpace(string(output)) + + if err != nil { + return fmt.Errorf("git commit failed: %w\nOutput: %s", err, outputStr) + } + + log.Infof("Successfully committed the file") + return nil +} diff --git a/internal/pkg/commit/commit_test.go b/internal/pkg/commit/commit_test.go new file mode 100644 index 000000000..9419be114 --- /dev/null +++ b/internal/pkg/commit/commit_test.go @@ -0,0 +1,77 @@ +package commit_test + +import ( + "os" + "os/exec" + "path/filepath" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + . "github.com/devstream-io/devstream/internal/pkg/commit" +) + +var _ = Describe("Commit", func() { + var testRepoDir string + + BeforeEach(func() { + // 1. Create a temporary directory + var err error + testRepoDir, err = os.MkdirTemp("", "test-repo-*") + Expect(err).NotTo(HaveOccurred()) + + // 2. Change the working directory to the temporary directory + err = os.Chdir(testRepoDir) + Expect(err).NotTo(HaveOccurred()) + + // 3. Initialize a git repository + cmd := exec.Command("git", "init") + err = cmd.Run() + Expect(err).NotTo(HaveOccurred()) + + // 4. Create a file and write some content to it + file, err := os.Create(filepath.Join(testRepoDir, "test.txt")) + Expect(err).NotTo(HaveOccurred()) + + _, err = file.WriteString("Test content") + Expect(err).NotTo(HaveOccurred()) + file.Close() + + // 5. Add the file to the git index + cmd = exec.Command("git", "add", "test.txt") + err = cmd.Run() + Expect(err).NotTo(HaveOccurred()) + }) + + AfterEach(func() { + err := os.RemoveAll(testRepoDir) + Expect(err).NotTo(HaveOccurred()) + }) + + It("should create a new commit with the given message", func() { + message := "Test commit" + err := Commit(message) + Expect(err).NotTo(HaveOccurred()) + + cmd := exec.Command("git", "log", "--oneline") + output, err := cmd.CombinedOutput() + Expect(err).NotTo(HaveOccurred()) + + Expect(string(output)).To(ContainSubstring(message)) + }) + + It("should return an error when git is not installed", func() { + origGitPath, err := exec.LookPath("git") + Expect(err).NotTo(HaveOccurred()) + + err = os.Setenv("PATH", "") + Expect(err).NotTo(HaveOccurred()) + defer func() { + err = os.Setenv("PATH", origGitPath) + Expect(err).NotTo(HaveOccurred()) + }() + + err = Commit("Test commit") + Expect(err).To(HaveOccurred()) + }) +}) diff --git a/internal/pkg/completion/completion.go b/internal/pkg/completion/completion.go deleted file mode 100644 index 06aa32005..000000000 --- a/internal/pkg/completion/completion.go +++ /dev/null @@ -1,108 +0,0 @@ -package completion - -import ( - "fmt" - "io" - "os" - "path/filepath" - - "github.com/spf13/cobra" - - "github.com/devstream-io/devstream/cmd/devstream/list" - "github.com/devstream-io/devstream/pkg/util/log" -) - -func FlagPluginsCompletion(cmd *cobra.Command, flag string) { - if err := cmd.RegisterFlagCompletionFunc(flag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - return list.PluginsNameSlice(), cobra.ShellCompDirectiveDefault - }); err != nil { - log.Warn(err) - } -} - -func FlagFilenameCompletion(cmd *cobra.Command, flagName string) { - - // Ref: https://github.com/spf13/cobra/blob/master/shell_completions.md#specify-valid-filename-extensions-for-flags-that-take-a-filename - if err := cmd.MarkFlagFilename(flagName, "yaml", "yml"); err != nil { - log.Warn(err) - } -} - -func FlagDirnameCompletion(cmd *cobra.Command, flagName string) { - - // Ref: https://github.com/spf13/cobra/blob/master/shell_completions.md#limit-flag-completions-to-directory-names - if err := cmd.MarkFlagDirname(flagName); err != nil { - log.Warn(err) - } -} - -func CompletionBash(out io.Writer, cmd *cobra.Command) error { - err := cmd.Root().GenBashCompletion(out) - - // The default binary name downloaded from the Releases page is dtm-{os}-amd64 - // solve the problem that autocompletion fails when the name of the binary is not dtm - if binary := filepath.Base(os.Args[0]); binary != "dtm" { - renamedBinary := ` -# the user renamed the dtm binary -if [[ $(type -t compopt) = "builtin" ]]; then - complete -o default -F __start_dtm %[1]s -else - complete -o default -o nospace -F __start_dtm %[1]s -fi -` - fmt.Fprintf(out, renamedBinary, binary) - } - - return err -} - -func CompletionZsh(out io.Writer, cmd *cobra.Command) error { - err := cmd.Root().GenZshCompletionNoDesc(out) - - // The default binary name downloaded from the Releases page is dtm-{os}-amd64 - // solve the problem that autocompletion fails when the name of the binary is not dtm - if binary := filepath.Base(os.Args[0]); binary != "dtm" { - renamedBinary := ` -# the user renamed the dtm binary -compdef _dtm %[1]s -` - fmt.Fprintf(out, renamedBinary, binary) - } - - fmt.Fprintf(out, "compdef _dtm dtm") - - return err -} - -func BashExample(binary string) string { - return fmt.Sprintf(`Load is completions in the current shell session: -# source <(%s completion bash) - -Load completions for every new session: -(in Linux)# %s completion bash > /etc/bash_completion.d/dtm -(in MacOS)# %s completion bash > $(brew --prefix)/etc/bash_completion.d/dtm`, binary, binary, binary) -} - -func ZshExample(binary string) string { - return fmt.Sprintf(`Load is completions in the current shell session: -# source <(%s completion zsh) - -Load completions for every new session: -# %s completion zsh > "${fpath[1]}/_dtm"`, binary, binary) -} - -func FishExample(binary string) string { - return fmt.Sprintf(`Load is completions in the current shell session: -# %s completion fish | source - -Load completions for every new session: -# %s completion fish > ~/.config/fish/completions/dtm.fish`, binary, binary) -} - -func PowershellExample(binary string) string { - return fmt.Sprintf(`Load is completions in the current shell session: -C:\> %s completion powershell | Out-String | Invoke-Expression - -Load completions for every new session: -add the output of the above command to powershell profile.`, binary) -} diff --git a/internal/pkg/configmanager/app.go b/internal/pkg/configmanager/app.go deleted file mode 100644 index 28f6e3897..000000000 --- a/internal/pkg/configmanager/app.go +++ /dev/null @@ -1,124 +0,0 @@ -package configmanager - -import ( - "fmt" - - "github.com/devstream-io/devstream/pkg/util/log" - "github.com/devstream-io/devstream/pkg/util/scm/git" - "github.com/devstream-io/devstream/pkg/util/validator" -) - -const ( - repoScaffoldingPluginName = "repo-scaffolding" -) - -type repoTemplate struct { - *git.RepoInfo `yaml:",inline"` - Vars RawOptions `yaml:"vars"` -} - -type app struct { - Name string `yaml:"name" mapstructure:"name" validate:"required"` - Spec *appSpec `yaml:"spec" mapstructure:"spec" validate:"required"` - Repo *git.RepoInfo `yaml:"repo" mapstructure:"repo" validate:"required"` - RepoTemplate *repoTemplate `yaml:"repoTemplate" mapstructure:"repoTemplate"` - CIRawConfigs []pipelineRaw `yaml:"ci" mapstructure:"ci"` - CDRawConfigs []pipelineRaw `yaml:"cd" mapstructure:"cd"` -} - -func (a *app) getTools(vars map[string]any, templateMap map[string]string) (Tools, error) { - // 1. check app config data is valid - if err := validator.CheckStructError(a).Combine(); err != nil { - return nil, err - } - // 2. set app default field repoInfo and repoTemplateInfo - if err := a.setDefault(); err != nil { - return nil, err - } - - // 3. get ci/cd pipelineTemplates - appVars := a.Spec.merge(vars) - tools, err := a.generateCICDTools(templateMap, appVars) - if err != nil { - return nil, fmt.Errorf("app[%s] get pipeline tools failed: %w", a.Name, err) - } - - // 4. generate app repo and template repo from scmInfo - repoScaffoldingTool := a.generateRepoTemplateTool() - if repoScaffoldingTool != nil { - tools = append(tools, repoScaffoldingTool) - } - log.Debugf("Have got %d tools from app %s.", len(tools), a.Name) - return tools, nil -} - -// generateCICDTools generate ci/cd tools from app config -func (a *app) generateCICDTools(templateMap map[string]string, appVars map[string]any) (Tools, error) { - allPipelineRaw := append(a.CIRawConfigs, a.CDRawConfigs...) - var tools Tools - // pipelineGlobalVars is used to pass variable from ci/cd pipelines - pipelineGlobalVars := a.newPipelineGlobalOptionFromApp() - for _, p := range allPipelineRaw { - t, err := p.getPipelineTemplate(templateMap, appVars) - if err != nil { - return nil, err - } - if err := validator.CheckStructError(t).Combine(); err != nil { - return nil, err - } - t.updatePipelineVars(pipelineGlobalVars) - pipelineTool, err := t.generatePipelineTool(pipelineGlobalVars) - if err != nil { - return nil, err - } - pipelineTool.DependsOn = a.getRepoTemplateDependants() - tools = append(tools, pipelineTool) - } - return tools, nil -} - -// generateRepoTemplateTool will use repo-scaffolding plugin for app -func (a *app) generateRepoTemplateTool() *Tool { - if a.RepoTemplate != nil { - templateVars := make(RawOptions) - // templateRepo doesn't need auth info - if a.RepoTemplate.Vars == nil { - templateVars = make(RawOptions) - } - return newTool( - repoScaffoldingPluginName, a.Name, RawOptions{ - "destinationRepo": RawOptions(a.Repo.Encode()), - "sourceRepo": RawOptions(a.RepoTemplate.Encode()), - "vars": templateVars, - }, - ) - } - return nil -} - -// setDefault will set repoName to appName if repo.name field is empty -func (a *app) setDefault() error { - if a.Repo.Repo == "" { - a.Repo.Repo = a.Name - } - return nil -} - -// since all plugin depends on code is deployed, get dependsOn for repoTemplate -func (a *app) getRepoTemplateDependants() []string { - var dependsOn []string - // if a.RepoTemplate is configured, pipeline need to wait reposcaffolding finished - if a.RepoTemplate != nil { - dependsOn = []string{fmt.Sprintf("%s.%s", repoScaffoldingPluginName, a.Name)} - } - return dependsOn -} - -// newPipelineGlobalOptionFromApp generate pipeline options used for pipeline option configuration -func (a *app) newPipelineGlobalOptionFromApp() *pipelineGlobalOption { - return &pipelineGlobalOption{ - Repo: a.Repo, - AppSpec: a.Spec, - AppName: a.Name, - } -} diff --git a/internal/pkg/configmanager/app_test.go b/internal/pkg/configmanager/app_test.go deleted file mode 100644 index 5ed6f16b1..000000000 --- a/internal/pkg/configmanager/app_test.go +++ /dev/null @@ -1,98 +0,0 @@ -package configmanager - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/pkg/util/scm/git" -) - -var _ = Describe("app struct", func() { - var ( - a *app - appName string - vars map[string]any - templateMap map[string]string - ) - BeforeEach(func() { - appName = "test_app" - vars = map[string]any{} - templateMap = map[string]string{} - }) - Context("getTools method", func() { - When("repo is not valid", func() { - BeforeEach(func() { - a = &app{Name: appName} - }) - It("should return error", func() { - _, err := a.getTools(vars, templateMap) - Expect(err).Should(HaveOccurred()) - Expect(err.Error()).Should(ContainSubstring("field app.repo is required")) - }) - }) - When("ci/cd template is not valid", func() { - BeforeEach(func() { - a = &app{ - Repo: &git.RepoInfo{ - CloneURL: "http://test.com/test/test_app", - }, - CIRawConfigs: []pipelineRaw{ - { - Type: "template", - TemplateName: "not_exist", - }, - }, - } - }) - It("should return error", func() { - _, err := a.getTools(vars, templateMap) - Expect(err).Should(HaveOccurred()) - Expect(err.Error()).Should(ContainSubstring("field app.name is required")) - Expect(err.Error()).Should(ContainSubstring("field app.spec is required")) - }) - }) - When("app repo template is empty", func() { - BeforeEach(func() { - a = &app{ - Name: appName, - Repo: &git.RepoInfo{ - CloneURL: "http://test.com/test/test_app", - }, - Spec: &appSpec{Language: "go", FrameWork: "gin"}, - } - }) - It("should return empty tools", func() { - tools, err := a.getTools(vars, templateMap) - Expect(err).ShouldNot(HaveOccurred()) - Expect(len(tools)).Should(Equal(0)) - }) - }) - }) - - Context("generateCICDTools method", func() { - When("template type not exist", func() { - BeforeEach(func() { - a = &app{ - Repo: &git.RepoInfo{ - CloneURL: "http://test.com/test/test_app", - }, - CIRawConfigs: []pipelineRaw{ - { - Type: "template", - TemplateName: "not_valid", - }, - }, - } - }) - It("should return error", func() { - templateMap = map[string]string{ - "not_valid": ` -name: not_valid, -type: not_valid`} - _, err := a.generateCICDTools(templateMap, vars) - Expect(err).Should(HaveOccurred()) - Expect(err.Error()).Should(ContainSubstring("field pipelineTemplate.type must be one of")) - }) - }) - }) -}) diff --git a/internal/pkg/configmanager/appspec.go b/internal/pkg/configmanager/appspec.go deleted file mode 100644 index 8873eba00..000000000 --- a/internal/pkg/configmanager/appspec.go +++ /dev/null @@ -1,39 +0,0 @@ -package configmanager - -import ( - "github.com/imdario/mergo" - - "github.com/devstream-io/devstream/pkg/util/log" - "github.com/devstream-io/devstream/pkg/util/mapz" -) - -// appSpec is app special options -type appSpec struct { - // language config - Language string `yaml:"language" mapstructure:"language" validate:"required"` - FrameWork string `yaml:"framework" mapstructure:"framework" validate:"required"` -} - -// merge will merge vars and appSpec -func (s *appSpec) merge(vars map[string]any) map[string]any { - specMap, err := mapz.DecodeStructToMap(s) - if err != nil { - log.Warnf("appspec %+v decode failed: %+v", s, err) - return map[string]any{} - } - _ = mergo.Merge(&specMap, vars) - return specMap -} - -func (s *appSpec) updatePiplineOption(options RawOptions) { - if _, exist := options["language"]; !exist && s.hasLanguageConfig() { - options["language"] = RawOptions{ - "name": s.Language, - "framework": s.FrameWork, - } - } -} - -func (s *appSpec) hasLanguageConfig() bool { - return s.Language != "" || s.FrameWork != "" -} diff --git a/internal/pkg/configmanager/config.go b/internal/pkg/configmanager/config.go deleted file mode 100644 index d280d914a..000000000 --- a/internal/pkg/configmanager/config.go +++ /dev/null @@ -1,23 +0,0 @@ -package configmanager - -import ( - "fmt" -) - -// Config is a general config in DevStream. -type Config struct { - Config CoreConfig `yaml:"config"` - Vars map[string]any `yaml:"vars"` - Tools Tools `yaml:"tools"` -} - -func (c *Config) validate() error { - if c.Config.State == nil { - return fmt.Errorf("config.state is not defined") - } - - if err := c.Tools.validateAll(); err != nil { - return err - } - return nil -} diff --git a/internal/pkg/configmanager/config_test.go b/internal/pkg/configmanager/config_test.go deleted file mode 100644 index 8f0371cad..000000000 --- a/internal/pkg/configmanager/config_test.go +++ /dev/null @@ -1,26 +0,0 @@ -package configmanager - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -var _ = Describe("Config struct", func() { - var c *Config - - BeforeEach(func() { - c = &Config{} - }) - Context("validate method", func() { - When("config state is null", func() { - BeforeEach(func() { - c.Config.State = nil - }) - It("should return err", func() { - err := c.validate() - Expect(err).Should(HaveOccurred()) - Expect(err.Error()).Should(Equal("config.state is not defined")) - }) - }) - }) -}) diff --git a/internal/pkg/configmanager/configmanager.go b/internal/pkg/configmanager/configmanager.go deleted file mode 100644 index 60b4c8782..000000000 --- a/internal/pkg/configmanager/configmanager.go +++ /dev/null @@ -1,111 +0,0 @@ -package configmanager - -import ( - "fmt" - "strings" - - "github.com/devstream-io/devstream/pkg/util/file" - "github.com/devstream-io/devstream/pkg/util/log" -) - -// Manager is used to load the config file from the ConfigFilePath and finally get the Config object. -type Manager struct { - ConfigFilePath string -} - -// NewManager takes configFilePath(file or directory), then return a *Manager object. -func NewManager(configPath string) *Manager { - return &Manager{ - ConfigFilePath: configPath, - } -} - -// LoadConfig is the only method that the caller of Manager needs to be concerned with, and this method returns a *Config finally. -// The main workflow of this method is: -// 1. Get the original config from the config file specified by ConfigFilePath; -// 2. Validation. -func (m *Manager) LoadConfig() (*Config, error) { - // step 1: get config - c, err := m.getConfigFromFileWithGlobalVars() - if err != nil { - return nil, err - } - // set instanceID in options - c.Tools.renderInstanceIDtoOptions() - - // step 2: check config is valid - if err = c.validate(); err != nil { - return nil, err - } - - return c, nil -} - -// getConfigFromFileWithGlobalVars gets Config from the config file specified by Manager.ConfigFilePath, then: -// 1. render the global variables to Config.Tools and Config.Apps -// 2. transfer the PipelineTemplates to Config.pipelineTemplateMap, it's map[string]string type. -// We can't render the original config file to Config.PipelineTemplates directly for the: -// 1. variables rendered must be before the yaml.Unmarshal() called for the [[ foo ]] will be treated as a two-dimensional array by the yaml parser; -// 2. the variables used([[ foo ]]) in the Config.PipelineTemplates can be defined in the Config.Apps or Config.Vars; -func (m *Manager) getConfigFromFileWithGlobalVars() (*Config, error) { - configBytes, err := file.ReadYamls(m.ConfigFilePath) - if err != nil { - return nil, err - } - - // extract top raw config struct from config text - r, err := newRawConfigFromConfigBytes(configBytes) - if err != nil { - return nil, err - } - // 1. get global variables - vars, err := r.getVars() - if err != nil { - return nil, fmt.Errorf("failed to get variables from config file. Error: %w", err) - } - - missingVariableErrorMsg := "map has no entry for key " - - // 2. tools with global variables rendered - tools, err := r.getToolsWithVars(vars) - if err != nil { - keyNotFoundIndex := strings.Index(err.Error(), missingVariableErrorMsg) - if keyNotFoundIndex != -1 { - return nil, fmt.Errorf("failed to process variables in the tools section. Missing variable definition: %s", err.Error()[keyNotFoundIndex+len(missingVariableErrorMsg):]) - } - return nil, fmt.Errorf("failed to get tools from config file. Error: %w", err) - - } - - // 3. apps tools with global variables rendered - appTools, err := r.getAppToolsWithVars(vars) - if err != nil { - keyNotFoundIndex := strings.Index(err.Error(), "map has no entry for key ") - if keyNotFoundIndex != -1 { - return nil, fmt.Errorf("failed to process variables in the apps section. Missing variable definition: %s", err.Error()[keyNotFoundIndex+len(missingVariableErrorMsg):]) - } - return nil, fmt.Errorf("failed to get apps from config file. Error: %w", err) - } - // all tools from apps should depend on the original tools, - // because dtm will execute all original tools first, then execute all tools from apps - appTools.updateToolDepends(tools) - tools = append(tools, appTools...) - - // 4. coreConfig without any changes - coreConfig, err := r.getConfig() - if err != nil { - return nil, fmt.Errorf("failed to get coreConfig from config file. Error: %w", err) - } - - coreConfig.State.BaseDir, err = file.GetFileAbsDirPathOrDirItself(m.ConfigFilePath) - if err != nil { - return nil, fmt.Errorf("failed to get base dir of config. Error: %w", err) - } - log.Debugf("baseDir of config and state is %s", coreConfig.State.BaseDir) - - return &Config{ - Config: *coreConfig, - Vars: vars, - Tools: tools, - }, nil -} diff --git a/internal/pkg/configmanager/configmanager_suite_test.go b/internal/pkg/configmanager/configmanager_suite_test.go deleted file mode 100644 index d963c3499..000000000 --- a/internal/pkg/configmanager/configmanager_suite_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package configmanager - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestPlanmanager(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "configmanager Suite") -} diff --git a/internal/pkg/configmanager/configmanager_test.go b/internal/pkg/configmanager/configmanager_test.go deleted file mode 100644 index c3f829ed7..000000000 --- a/internal/pkg/configmanager/configmanager_test.go +++ /dev/null @@ -1,285 +0,0 @@ -package configmanager - -import ( - "os" - "path/filepath" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/pkg/util/scm/git" -) - -var _ = Describe("Manager struct", func() { - var tmpWorkDir string - const configFileStr = `--- -config: - state: - backend: local - options: - stateFile: devstream.state - -vars: - foo1: bar1 - foo2: 123 - foo3: foo1+foo2=[[foo1]][[foo2 ]]! - appName: service-a - registryType: dockerhub - argocdNamespace: argocd - -apps: -- name: service-a - spec: - language: python - framework: django - repo: - scmType: github - owner: devstream-io - url: github.com/devstream-io/service-a # optional,if url is specified,we can infer scm/owner/org/name from url - apiURL: gitlab.com/some/path/to/your/api # optional, if you want to create a repo from repo template - # if repoTemplate is not empty,we could help user to create repo from scaffoldingRepo - repoTemplate: # optional - scmType: github - owner: devstream-io - org: devstream-io # choose between owner and org - name: dtm-repo-scaffolding-golang-gin - url: github.com/devstream-io/dtm-repo-scaffolding-golang-gin # optional,if url is specified,we can infer scm/owner/org/name from url - ci: - - type: template - templateName: ci-pipeline-for-github-actions - options: # overwrite options in pipelineTemplates - docker: - registry: - type: [[ registryType ]] # while overridden, use global variables - vars: # optional, use to render vars in template(valid only if the ci.type is template) - dockerUser: dockerUser1 - app: service-a - cd: - - type: template - templateName: cd-pipeline-for-argocdapp - options: # overwrite options in pipelineTemplates - destination: - namespace: devstream-io - vars: # optional, use to render vars in template(valid only if the cd.type is template) - app: [[ appName ]] - -tools: -- name: plugin1 - instanceID: default - dependsOn: [] - options: - foo1: [[ foo1 ]] - foo3: [[ foo3 ]] -- name: plugin2 - instanceID: tluafed - dependsOn: [] - options: - foo: bar - foo2: [[ foo2 ]] - -pipelineTemplates: -- name: ci-pipeline-for-github-actions - type: github-actions # corresponding to a plugin - options: - branch: main # optional, default is main - docker: - registry: - type: dockerhub - username: [[ dockerUser ]] - repository: [[ app ]] -- name: cd-pipeline-for-argocdapp - type: argocdapp - options: - app: - namespace: [[ argocdNamespace ]] # you can use global vars in templates - destination: - server: https://kubernetes.default.svc - namespace: devstream-io - source: - valuefile: values.yaml - path: helm/[[ app ]] - repoURL: ${{repo-scaffolding.myapp.outputs.repoURL}} -` - - Context("LoadConfig method", func() { - tool1 := &Tool{ - Name: "plugin1", - InstanceID: "default", - DependsOn: []string{}, - Options: RawOptions{ - "foo1": "bar1", - "foo3": "foo1+foo2=bar1123!", - "instanceID": "default", - }, - } - - tool2 := &Tool{ - Name: "plugin2", - InstanceID: "tluafed", - DependsOn: []string{}, - Options: RawOptions{ - "instanceID": "tluafed", - "foo": "bar", - "foo2": 123, - }, - } - - tool3 := &Tool{ - Name: "github-actions", - InstanceID: "service-a", - DependsOn: []string{ - "repo-scaffolding.service-a", - "plugin1.default", - "plugin2.tluafed", - }, - Options: RawOptions{ - "instanceID": "service-a", - "pipeline": RawOptions{ - "language": RawOptions{ - "name": "python", - "framework": "django", - }, - "docker": RawOptions{ - "registry": RawOptions{ - "repository": "service-a", - "type": "dockerhub", - "username": "dockerUser1", - }, - }, - "branch": "main", - "configLocation": "https://raw.githubusercontent.com/devstream-io/dtm-pipeline-templates/main/github-actions/workflows/main.yml", - }, - "scm": RawOptions{ - "apiURL": "gitlab.com/some/path/to/your/api", - "owner": "devstream-io", - "name": "service-a", - "scmType": "github", - "url": git.ScmURL("github.com/devstream-io/service-a"), - }, - }, - } - - tool4 := &Tool{ - Name: "argocdapp", - InstanceID: "service-a", - DependsOn: []string{ - "repo-scaffolding.service-a", - "plugin1.default", - "plugin2.tluafed", - }, - Options: RawOptions{ - "instanceID": "service-a", - "destination": RawOptions{ - "namespace": "devstream-io", - "server": "https://kubernetes.default.svc", - }, - "app": RawOptions{ - "namespace": "argocd", - }, - "source": RawOptions{ - "valuefile": "values.yaml", - "path": "helm/service-a", - "repoURL": "${{repo-scaffolding.myapp.outputs.repoURL}}", - }, - }, - } - - tool5 := &Tool{ - Name: "repo-scaffolding", - InstanceID: "service-a", - DependsOn: []string{ - "plugin1.default", - "plugin2.tluafed", - }, - Options: RawOptions{ - "instanceID": "service-a", - "destinationRepo": RawOptions{ - "owner": "devstream-io", - "name": "service-a", - "scmType": "github", - "apiURL": "gitlab.com/some/path/to/your/api", - "url": git.ScmURL("github.com/devstream-io/service-a"), - }, - "sourceRepo": RawOptions{ - "scmType": "github", - "url": git.ScmURL("github.com/devstream-io/dtm-repo-scaffolding-golang-gin"), - "owner": "devstream-io", - "name": "dtm-repo-scaffolding-golang-gin", - "org": "devstream-io", - }, - "vars": RawOptions{}, - }, - } - - BeforeEach(func() { - tmpWorkDir = GinkgoT().TempDir() - err := os.WriteFile(filepath.Join(tmpWorkDir, "config.yaml"), []byte(configFileStr), 0644) - Expect(err).NotTo(HaveOccurred()) - }) - - When("load a config file", func() { - It("should return tools", func() { - mgr := NewManager(filepath.Join(tmpWorkDir, "config.yaml")) - cfg, err := mgr.LoadConfig() - Expect(err).NotTo(HaveOccurred()) - Expect(cfg).NotTo(BeNil()) - - GinkgoWriter.Printf("Config: %v", cfg) - - // config/state - Expect(*cfg.Config.State).To(Equal(State{ - BaseDir: tmpWorkDir, - Backend: "local", - Options: StateConfigOptions{ - StateFile: "devstream.state", - }, - })) - - // vars - Expect(len(cfg.Vars)).To(Equal(6)) - Expect(cfg.Vars["foo1"]).To(Equal("bar1")) - Expect(cfg.Vars["foo2"]).To(Equal(123)) - Expect(cfg.Vars["foo3"]).To(Equal("foo1+foo2=bar1123!")) - - // tools - Expect(len(cfg.Tools)).To(Equal(5)) - for _, t := range cfg.Tools { - switch t.Name { - case "plugin1": - Expect(t).Should(Equal(tool1)) - case "plugin2": - Expect(t).Should(Equal(tool2)) - case "github-actions": - Expect(t).Should(Equal(tool3)) - case "argocdapp": - Expect(t).Should(Equal(tool4)) - case "repo-scaffolding": - Expect(t).Should(Equal(tool5)) - default: - Fail("Unexpected plugin name.") - } - } - }) - }) - }) - - Context("getConfigFromFileWithGlobalVars method", func() { - BeforeEach(func() { - tmpWorkDir = GinkgoT().TempDir() - err := os.WriteFile(filepath.Join(tmpWorkDir, "config.yaml"), []byte(configFileStr), 0644) - Expect(err).NotTo(HaveOccurred()) - }) - - When("get config from file", func() { - It("should return a config", func() { - mgr := NewManager(filepath.Join(tmpWorkDir, "config.yaml")) - cfg, err := mgr.getConfigFromFileWithGlobalVars() - Expect(err).NotTo(HaveOccurred()) - Expect(cfg.Config.State.Backend).To(Equal("local")) - Expect(cfg.Vars["foo1"]).To(Equal("bar1")) - Expect(len(cfg.Tools)).To(Equal(5)) - Expect(cfg.Tools[1].Name).To(Equal("plugin2")) - }) - }) - }) -}) diff --git a/internal/pkg/configmanager/coreconfig.go b/internal/pkg/configmanager/coreconfig.go deleted file mode 100644 index 6d05762a7..000000000 --- a/internal/pkg/configmanager/coreconfig.go +++ /dev/null @@ -1,26 +0,0 @@ -package configmanager - -type CoreConfig struct { - State *State `yaml:"state"` -} - -// State is the struct for reading the state configuration in the config file. -// It defines how the state is stored, specifies the type of backend and related options. -type State struct { - Backend string `yaml:"backend"` - Options StateConfigOptions `yaml:"options"` - BaseDir string `yaml:"-"` // baseDir is the base directory of the config file -} - -// StateConfigOptions is the struct for reading the options of the state backend. -type StateConfigOptions struct { - // for s3 backend - Bucket string `yaml:"bucket"` - Region string `yaml:"region"` - Key string `yaml:"key"` - // for local backend - StateFile string `yaml:"stateFile"` - // for k8s backend - Namespace string `yaml:"namespace"` - ConfigMap string `yaml:"configmap"` -} diff --git a/internal/pkg/configmanager/pipelineDefault.go b/internal/pkg/configmanager/pipelineDefault.go deleted file mode 100644 index 6384895dc..000000000 --- a/internal/pkg/configmanager/pipelineDefault.go +++ /dev/null @@ -1,122 +0,0 @@ -package configmanager - -import ( - "fmt" -) - -var optionConfiguratorMap = map[string]pipelineOption{ - "github-actions": githubGeneral, - "gitlab-ci": gitlabGeneral, - "jenkins-pipeline": jenkinsGeneral, - "argocdapp": argocdApp, -} - -type pipelineOptionGenerator func(originOption RawOptions, vars *pipelineGlobalOption) RawOptions - -// pipelineOption is used for config different pipeline type's default config -type pipelineOption struct { - defaultConfigLocation string - optionGeneratorFunc pipelineOptionGenerator -} - -// TODO(steinliber) unify all ci/cd config to same config options -var ( - // github actions pipeline options - githubGeneral = pipelineOption{ - defaultConfigLocation: "https://raw.githubusercontent.com/devstream-io/dtm-pipeline-templates/main/github-actions/workflows/main.yml", - optionGeneratorFunc: pipelineGeneralGenerator, - } - gitlabGeneral = pipelineOption{ - defaultConfigLocation: "https://raw.githubusercontent.com/devstream-io/dtm-pipeline-templates/main/gitlab-ci/.gitlab-ci.yml", - optionGeneratorFunc: gitlabCIGenerator, - } - jenkinsGeneral = pipelineOption{ - defaultConfigLocation: "https://raw.githubusercontent.com/devstream-io/dtm-pipeline-templates/main/jenkins-pipeline/general/Jenkinsfile", - optionGeneratorFunc: jenkinsGenerator, - } - argocdApp = pipelineOption{ - optionGeneratorFunc: pipelineArgocdAppGenerator, - } -) - -// jenkinsGenerator generate jenkins pipeline config -func jenkinsGenerator(options RawOptions, globalVars *pipelineGlobalOption) RawOptions { - newOptions := pipelineGeneralGenerator(options, globalVars) - // extract jenkins config from options - jenkinsOptions, exist := options["jenkins"] - if exist { - newOptions["jenkins"] = jenkinsOptions - } - return newOptions -} - -// gitlabGenerator generate gitlab ci config -func gitlabCIGenerator(options RawOptions, globalVars *pipelineGlobalOption) RawOptions { - newOptions := pipelineGeneralGenerator(options, globalVars) - // extract jenkins config from options - gitlabRunnerOption, exist := options["runner"] - if exist { - newOptions["runner"] = gitlabRunnerOption - } - return newOptions -} - -// pipelineGeneralGenerator generate pipeline general options from RawOptions -func pipelineGeneralGenerator(options RawOptions, globalVars *pipelineGlobalOption) RawOptions { - if globalVars.AppSpec != nil { - globalVars.AppSpec.updatePiplineOption(options) - } - // update image related config - newOption := make(RawOptions) - newOption["pipeline"] = options - newOption["scm"] = RawOptions(globalVars.Repo.Encode()) - return newOption -} - -// pipelineArgocdAppGenerator generate argocdApp options from RawOptions -func pipelineArgocdAppGenerator(options RawOptions, globalVars *pipelineGlobalOption) RawOptions { - // config app default options - if _, exist := options["app"]; !exist { - options["app"] = RawOptions{ - "name": globalVars.AppName, - "namespace": "argocd", - } - } - // config destination options - if _, exist := options["destination"]; !exist { - options["destination"] = RawOptions{ - "server": "https://kubernetes.default.svc", - "namespace": "default", - } - } - // config source default options - if source, sourceExist := options["source"]; sourceExist { - sourceMap := source.(RawOptions) - if _, repoURLExist := sourceMap["repoURL"]; !repoURLExist { - sourceMap["repoURL"] = globalVars.Repo.GetCloneURL() - sourceMap["repoBranch"] = globalVars.Repo.Branch - sourceMap["token"] = globalVars.Repo.Token - } - options["source"] = sourceMap - } else { - options["source"] = RawOptions{ - "valuefile": "values.yaml", - "path": fmt.Sprintf("helm/%s", globalVars.AppName), - "repoURL": string(globalVars.Repo.GetCloneURL()), - "repoBranch": globalVars.Repo.Branch, - "token": globalVars.Repo.Token, - } - } - // config imageRepo default options - if _, imageRepoExist := options["imageRepo"]; !imageRepoExist { - if len(globalVars.ImageRepo) > 0 { - options["imageRepo"] = globalVars.ImageRepo - } - } - return options -} - -// hasDefaultConfig check whether -func (o *pipelineOption) hasDefaultConfig() bool { - return o.defaultConfigLocation != "" -} diff --git a/internal/pkg/configmanager/pipelineDefault_test.go b/internal/pkg/configmanager/pipelineDefault_test.go deleted file mode 100644 index 246ce0df8..000000000 --- a/internal/pkg/configmanager/pipelineDefault_test.go +++ /dev/null @@ -1,100 +0,0 @@ -package configmanager - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/pkg/util/scm/git" -) - -var _ = Describe("jenkinsGenerator func", func() { - var ( - jenkinsOptions RawOptions - pipelineOptions *pipelineGlobalOption - ) - BeforeEach(func() { - jenkinsOptions = RawOptions{ - "jenkins": RawOptions{ - "url": "test.jenkins.com", - }, - "imageRepo": RawOptions{ - "user": "test_user", - }, - } - pipelineOptions = &pipelineGlobalOption{ - AppSpec: &appSpec{ - Language: "test_language", - FrameWork: "test_framework", - }, - Repo: &git.RepoInfo{ - CloneURL: "https://test.scm.com", - }, - } - }) - It("should config default options", func() { - opt := jenkinsGenerator(jenkinsOptions, pipelineOptions) - Expect(opt).Should(Equal(RawOptions{ - "pipeline": RawOptions{ - "language": RawOptions{ - "name": "test_language", - "framework": "test_framework", - }, - "jenkins": RawOptions{ - "url": "test.jenkins.com", - }, - "imageRepo": RawOptions{ - "user": "test_user", - }, - }, - "scm": RawOptions{ - "url": git.ScmURL("https://test.scm.com"), - }, - "jenkins": RawOptions{ - "url": "test.jenkins.com", - }, - })) - }) -}) - -var _ = Describe("pipelineArgocdAppGenerator func", func() { - var ( - options RawOptions - pipelineVars *pipelineGlobalOption - ) - BeforeEach(func() { - options = RawOptions{} - pipelineVars = &pipelineGlobalOption{ - ImageRepo: map[string]any{ - "owner": "test_user", - }, - Repo: &git.RepoInfo{ - CloneURL: "scm.test.com", - Branch: "testBranch", - }, - AppName: "test_app", - } - }) - It("should return options", func() { - opt := pipelineArgocdAppGenerator(options, pipelineVars) - Expect(opt).Should(Equal(RawOptions{ - "app": RawOptions{ - "name": "test_app", - "namespace": "argocd", - }, - "destination": RawOptions{ - "server": "https://kubernetes.default.svc", - "namespace": "default", - }, - "source": RawOptions{ - "valuefile": "values.yaml", - "path": "helm/test_app", - "repoURL": "https://scm.test.com", - "repoBranch": "testBranch", - "token": "", - }, - "imageRepo": RawOptions{ - "owner": "test_user", - }, - })) - }) -}) diff --git a/internal/pkg/configmanager/pipelinetemplate.go b/internal/pkg/configmanager/pipelinetemplate.go deleted file mode 100644 index 0acaf7813..000000000 --- a/internal/pkg/configmanager/pipelinetemplate.go +++ /dev/null @@ -1,121 +0,0 @@ -package configmanager - -import ( - "fmt" - - "github.com/imdario/mergo" - "gopkg.in/yaml.v3" - - "github.com/devstream-io/devstream/pkg/util/mapz" - "github.com/devstream-io/devstream/pkg/util/scm/git" -) - -type ( - // pipelineRaw is the raw format of app.ci/app.cd config - pipelineRaw struct { - Type string `yaml:"type" mapstructure:"type" validate:"required,oneof=template jenkins-pipeline github-actions gitlab-ci argocdapp"` - TemplateName string `yaml:"templateName" mapstructure:"templateName" validate:"required"` - Options RawOptions `yaml:"options" mapstructure:"options"` - Vars RawOptions `yaml:"vars" mapstructure:"vars"` - } - // pipelineTemplate is valid pipeline format - pipelineTemplate struct { - Name string `yaml:"name" validate:"required"` - Type string `yaml:"type" validate:"required,oneof=jenkins-pipeline github-actions gitlab-ci argocdapp"` - Options RawOptions `yaml:"options"` - } - // pipelineGlobalOption is used to pass variable between ci/cd pipeline - pipelineGlobalOption struct { - ImageRepo RawOptions - Repo *git.RepoInfo - AppSpec *appSpec - AppName string - } -) - -// getPipelineTemplate will generate pipleinTemplate from pipelineRaw -func (p *pipelineRaw) getPipelineTemplate(templateMap map[string]string, globalVars map[string]any) (*pipelineTemplate, error) { - var ( - t *pipelineTemplate - err error - ) - if p.Options == nil { - p.Options = make(RawOptions) - } - switch p.Type { - case "template": - t, err = p.newPipelineFromTemplate(templateMap, globalVars) - if err != nil { - return nil, err - } - default: - t = &pipelineTemplate{ - Type: p.Type, - Name: p.Type, - Options: p.Options, - } - } - return t, nil -} - -func (p *pipelineRaw) newPipelineFromTemplate(templateMap map[string]string, globalVars map[string]any) (*pipelineTemplate, error) { - var t pipelineTemplate - if p.TemplateName == "" { - return nil, fmt.Errorf("templateName is required") - } - templateStr, ok := templateMap[p.TemplateName] - if !ok { - return nil, fmt.Errorf("%s not found in pipelineTemplates", p.TemplateName) - } - - allVars := mapz.Merge(globalVars, p.Vars) - templateRenderdStr, err := renderConfigWithVariables(templateStr, allVars) - if err != nil { - return nil, fmt.Errorf("%s render pipelineTemplate failed: %+w", p.TemplateName, err) - } - - if err := yaml.Unmarshal([]byte(templateRenderdStr), &t); err != nil { - return nil, fmt.Errorf("%s parse pipelineTemplate yaml failed: %+w", p.TemplateName, err) - } - - if t.Options == nil { - t.Options = make(RawOptions) - } - - if err := mergo.Merge(&t.Options, p.Options, mergo.WithOverride); err != nil { - return nil, fmt.Errorf("%s merge template options faield: %+v", p.TemplateName, err) - } - return &t, nil -} - -func (t *pipelineTemplate) generatePipelineTool(pipelineOption *pipelineGlobalOption) (*Tool, error) { - const configLocationKey = "configLocation" - // 1.set default options - if t.Options == nil { - t.Options = RawOptions{} - } - // 2. get configurator by template type - pipelineConfigurator, exist := optionConfiguratorMap[t.Type] - if !exist { - return nil, fmt.Errorf("pipeline type [%s] not supported for now", t.Type) - } - // 3. set default configLocation - if _, configLocationExist := t.Options[configLocationKey]; !configLocationExist { - if pipelineConfigurator.hasDefaultConfig() { - t.Options[configLocationKey] = pipelineConfigurator.defaultConfigLocation - } - } - // 4. generate tool options - pipelineFinalOptions := pipelineConfigurator.optionGeneratorFunc(t.Options, pipelineOption) - return newTool(t.Type, pipelineOption.AppName, pipelineFinalOptions), nil -} - -func (t *pipelineTemplate) updatePipelineVars(vars *pipelineGlobalOption) { - imageRepo, exist := t.Options["imageRepo"] - if exist { - imageRepoMap, ok := imageRepo.(RawOptions) - if ok { - vars.ImageRepo = imageRepoMap - } - } -} diff --git a/internal/pkg/configmanager/pipelinetemplate_test.go b/internal/pkg/configmanager/pipelinetemplate_test.go deleted file mode 100644 index 311e5b397..000000000 --- a/internal/pkg/configmanager/pipelinetemplate_test.go +++ /dev/null @@ -1,264 +0,0 @@ -package configmanager - -import ( - "fmt" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/pkg/util/scm/git" -) - -var _ = Describe("pipelineRaw struct", func() { - var ( - r *pipelineRaw - templateMap map[string]string - opt, globalVars map[string]any - typeInfo, templateName string - ) - BeforeEach(func() { - typeInfo = "github-actions" - templateName = "testTemplate" - templateMap = map[string]string{} - globalVars = map[string]any{} - opt = map[string]any{} - }) - Context("getPipelineTemplate method", func() { - When("type is not template", func() { - BeforeEach(func() { - opt = RawOptions{ - "toolconfig": "here", - } - r = &pipelineRaw{ - Type: typeInfo, - Options: opt, - } - }) - It("should return template", func() { - expectedInfo := RawOptions{ - "toolconfig": "here", - } - t, err := r.getPipelineTemplate(templateMap, globalVars) - Expect(err).ShouldNot(HaveOccurred()) - Expect(t.Type).Should(Equal(typeInfo)) - Expect(t.Name).Should(Equal(typeInfo)) - Expect(t.Options).Should(Equal(expectedInfo)) - }) - }) - When("templateName is empty", func() { - BeforeEach(func() { - r = &pipelineRaw{ - Type: "template", - TemplateName: "", - } - }) - It("should return err", func() { - _, err := r.getPipelineTemplate(templateMap, globalVars) - Expect(err).Error().Should(HaveOccurred()) - Expect(err.Error()).Should(ContainSubstring("templateName is required")) - }) - }) - When("template not exist in templateMap", func() { - BeforeEach(func() { - r = &pipelineRaw{ - Type: "template", - TemplateName: "not_exit", - } - templateMap = map[string]string{} - }) - It("should return err", func() { - _, err := r.getPipelineTemplate(templateMap, globalVars) - Expect(err).Error().Should(HaveOccurred()) - Expect(err.Error()).Should(ContainSubstring("not found in pipelineTemplates")) - }) - }) - When("render template failed", func() { - BeforeEach(func() { - r = &pipelineRaw{ - Type: "template", - TemplateName: templateName, - } - templateMap = map[string]string{ - templateName: "[[ not_exist ]]", - } - }) - It("should return err", func() { - _, err := r.getPipelineTemplate(templateMap, globalVars) - Expect(err).Error().Should(HaveOccurred()) - }) - }) - When("template is not valid yaml format", func() { - BeforeEach(func() { - r = &pipelineRaw{ - Type: "template", - TemplateName: templateName, - } - templateMap = map[string]string{ - templateName: "test{}{}", - } - }) - It("should return err", func() { - _, err := r.getPipelineTemplate(templateMap, globalVars) - Expect(err).Error().Should(HaveOccurred()) - Expect(err.Error()).Should(ContainSubstring("parse pipelineTemplate yaml failed")) - }) - }) - When("app and global has same value", func() { - BeforeEach(func() { - r = &pipelineRaw{ - Type: "template", - TemplateName: templateName, - Vars: map[string]any{ - "var1": "cover", - }, - } - templateMap = map[string]string{ - templateName: fmt.Sprintf(` -name: %s -type: github-actions -options: - branch: main - docker: - registry: - username: [[ var1 ]]`, templateName), - } - globalVars = map[string]any{ - "var1": "global", - } - }) - It("should render with app vars", func() { - t, err := r.getPipelineTemplate(templateMap, globalVars) - Expect(err).ShouldNot(HaveOccurred()) - Expect(t.Options).Should(Equal(RawOptions{ - "branch": "main", - "docker": nil, - "registry": RawOptions{ - "username": "cover", - }, - })) - }) - }) - When("app and template has options", func() { - BeforeEach(func() { - r = &pipelineRaw{ - Type: "template", - TemplateName: templateName, - Options: RawOptions{ - "app": "test", - "option": "app", - }, - Vars: map[string]any{ - "var1": "cover", - }, - } - templateMap = map[string]string{ - templateName: fmt.Sprintf(` -name: %s -type: github-actions -options: - branch: main - app: template - registry: - username: [[ var1 ]]`, templateName), - } - globalVars = map[string]any{ - "var1": "global", - } - }) - It("should render with app vars", func() { - t, err := r.getPipelineTemplate(templateMap, globalVars) - Expect(err).ShouldNot(HaveOccurred()) - Expect(t.Options).Should(Equal(RawOptions{ - "branch": "main", - "registry": RawOptions{ - "username": "cover", - }, - "app": "test", - "option": "app", - })) - }) - }) - }) -}) - -var _ = Describe("PipelineTemplate struct", func() { - var ( - t *pipelineTemplate - s *git.RepoInfo - opts map[string]any - appName, cloneURL string - globalOption *pipelineGlobalOption - ) - BeforeEach(func() { - appName = "test_app" - cloneURL = "http://test.com" - t = &pipelineTemplate{} - s = &git.RepoInfo{ - CloneURL: git.ScmURL(cloneURL), - } - globalOption = &pipelineGlobalOption{ - Repo: s, - AppName: appName, - } - }) - Context("generatePipelineTool method", func() { - When("pipeline type is not valid", func() { - BeforeEach(func() { - t.Type = "not_exist" - }) - It("should return err", func() { - _, err := t.generatePipelineTool(globalOption) - Expect(err).Error().Should(HaveOccurred()) - Expect(err.Error()).Should(ContainSubstring("pipeline type [not_exist] not supported for now")) - }) - }) - When("pipeline type is valid", func() { - BeforeEach(func() { - appName = "test_app" - opts = map[string]any{ - "test": "testV", - } - t.Type = "github-actions" - t.Options = opts - }) - It("should return tool", func() { - tool, err := t.generatePipelineTool(globalOption) - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(tool).Should(Equal(&Tool{ - Name: t.Type, - InstanceID: appName, - DependsOn: []string{}, - Options: RawOptions{ - "pipeline": RawOptions{ - "test": "testV", - "configLocation": "https://raw.githubusercontent.com/devstream-io/dtm-pipeline-templates/main/github-actions/workflows/main.yml", - }, - "scm": RawOptions{ - "url": git.ScmURL(cloneURL), - }, - }, - })) - }) - }) - }) - - Context("updatePipelineVars method", func() { - var pipelineOpt *pipelineGlobalOption - - BeforeEach(func() { - t.Options = RawOptions{ - "imageRepo": RawOptions{ - "user": "test_user", - }, - } - pipelineOpt = &pipelineGlobalOption{} - }) - It("should update global option", func() { - t.updatePipelineVars(pipelineOpt) - Expect(pipelineOpt.ImageRepo).ShouldNot(BeNil()) - Expect(pipelineOpt.ImageRepo).Should(Equal(RawOptions{ - "user": "test_user", - })) - }) - }) -}) diff --git a/internal/pkg/configmanager/rawconfig.go b/internal/pkg/configmanager/rawconfig.go deleted file mode 100644 index a0c4b03f1..000000000 --- a/internal/pkg/configmanager/rawconfig.go +++ /dev/null @@ -1,192 +0,0 @@ -package configmanager - -import ( - "bytes" - "fmt" - "regexp" - "strings" - "unicode" - - "gopkg.in/yaml.v3" -) - -// rawConfig represent every valid config block for devstream -type rawConfig struct { - apps []byte - pipelineTemplates []byte - tools []byte - vars []byte - config []byte -} - -// newRawConfigFromConfigBytes will extract rawConfig from file content -func newRawConfigFromConfigBytes(fileText []byte) (*rawConfig, error) { - topConfigLevelRegex := regexp.MustCompile(`(?m)^\w+\s*:`) - allMatchIndex := topConfigLevelRegex.FindAllIndex(fileText, -1) - totalMatchLength := len(allMatchIndex) - cleanSpaceAndColonFunc := func(r rune) bool { - return unicode.IsSpace(r) || string(r) == ":" - } - rawConfigData := &rawConfig{} - // get map key and construct rawConfig options - for i, currentIndex := range allMatchIndex { - var matchStr []byte - // if is last item, just get rest string - if i == totalMatchLength-1 { - matchStr = fileText[currentIndex[1]:] - } else { - nextIndex := allMatchIndex[i+1] - matchStr = fileText[currentIndex[1]:nextIndex[0]] - } - configKey := bytes.TrimFunc(fileText[currentIndex[0]:currentIndex[1]], cleanSpaceAndColonFunc) - switch string(configKey) { - case "apps": - rawConfigData.apps = append(rawConfigData.apps, matchStr...) - case "tools": - rawConfigData.tools = append(rawConfigData.tools, matchStr...) - case "pipelineTemplates": - rawConfigData.pipelineTemplates = append(rawConfigData.pipelineTemplates, matchStr...) - case "config": - if len(rawConfigData.config) != 0 { - return nil, fmt.Errorf(" key can only be defined once") - } - rawConfigData.config = append(rawConfigData.config, matchStr...) - case "vars": - rawConfigData.vars = append(rawConfigData.vars, matchStr...) - default: - const errHint = "you may have filled in the wrong key or imported a yaml file that is not related to dtm" - return nil, fmt.Errorf("invalid config key <%s>, %s", string(configKey), errHint) - } - } - if err := rawConfigData.validate(); err != nil { - return nil, err - } - return rawConfigData, nil -} - -// validate will check config data is valid -func (c *rawConfig) validate() error { - errorFmt := "config not valid; check the [%s] section of your config file" - if (len(c.config)) == 0 { - return fmt.Errorf(errorFmt, "config") - } - if len(c.apps) == 0 && len(c.tools) == 0 { - return fmt.Errorf(errorFmt, "tools and apps") - } - return nil -} - -// getVars will generate variables from vars config -func (c *rawConfig) getVars() (map[string]any, error) { - var ( - globalVarsOrigin, globalVarsParsedNest map[string]any - err error - ) - - // [[]] is array in yaml, replace them, or yaml.Unmarshal will return err - const ( - leftOrigin, leftReplaced = "[[", "【$【" - rightOrigin, rightReplaced = "]]", "】$】" - ) - converter := strings.NewReplacer(leftOrigin, leftReplaced, rightOrigin, rightReplaced) - varsReplacedSquareBrackets := converter.Replace(string(c.vars)) - - // get origin vars from config - if err = yaml.Unmarshal([]byte(varsReplacedSquareBrackets), &globalVarsOrigin); err != nil { - return nil, err - } - - // restore vars - restorer := strings.NewReplacer(leftReplaced, leftOrigin, rightReplaced, rightOrigin) - for k, v := range globalVarsOrigin { - switch value := v.(type) { - case string: - globalVarsOrigin[k] = restorer.Replace(value) - case []byte: - globalVarsOrigin[k] = restorer.Replace(string(value)) - } - } - - // parse nested vars - if globalVarsParsedNest, err = parseNestedVars(globalVarsOrigin); err != nil { - return nil, err - } - return globalVarsParsedNest, nil -} - -// getToolsWithVars will generate Tools from tools config -func (c *rawConfig) getToolsWithVars(vars map[string]any) (Tools, error) { - renderedTools, err := renderConfigWithVariables(string(c.tools), vars) - if err != nil { - return nil, err - } - var tools = make(Tools, 0) - err = yaml.Unmarshal(renderedTools, &tools) - return tools, err -} - -// getAppToolsWithVars will generate tools from apps and pipelineTemplates -func (c *rawConfig) getAppToolsWithVars(vars map[string]any) (Tools, error) { - // render and unmarshal app config - apps, err := c.getAppsWithVars(vars) - if err != nil { - return nil, err - } - - // get pipelineTemplateMap for app ci/cd config - pipelineTemplateMap, err := c.getTemplatePipelineMap() - if err != nil { - return nil, err - } - - var tools = make(Tools, 0) - for _, a := range apps { - appTools, err := a.getTools(vars, pipelineTemplateMap) - if err != nil { - return nil, err - } - tools = append(tools, appTools...) - } - return tools, err -} - -// getTemplatePipelineMap will get map of templateName => templateStr -// TODO(steinliber) this func will transfer int to string, change it later -func (c *rawConfig) getTemplatePipelineMap() (map[string]string, error) { - yamlRegex := regexp.MustCompile(`([^:]+:)(\s*)\'?((\[\[[^\]]+\]\][^\s\[]*)+)\'?[^\s#\n]*`) - pipelineTemplateStr := yamlRegex.ReplaceAll(c.pipelineTemplates, []byte("$1$2\"$3\"")) - var pipelineTemplates = make([]*pipelineTemplate, 0) - if err := yaml.Unmarshal(pipelineTemplateStr, &pipelineTemplates); err != nil { - return nil, err - } - pipelineTemplateMap := map[string]string{} - for _, t := range pipelineTemplates { - rawPipeline, err := yaml.Marshal(t) - if err != nil { - return nil, err - } - if _, ok := pipelineTemplateMap[t.Name]; ok { - return nil, fmt.Errorf("pipelineTemplate <%s> is duplicated", t.Name) - } - pipelineTemplateMap[t.Name] = string(rawPipeline) - } - return pipelineTemplateMap, nil -} - -// getConfig will get config options -func (c *rawConfig) getConfig() (*CoreConfig, error) { - var config *CoreConfig - err := yaml.Unmarshal(c.config, &config) - return config, err -} - -// getAppsWithVars will get apps struct array -func (c *rawConfig) getAppsWithVars(vars map[string]any) ([]*app, error) { - renderedApps, err := renderConfigWithVariables(string(c.apps), vars) - if err != nil { - return nil, err - } - var apps = make([]*app, 0) - err = yaml.Unmarshal(renderedApps, &apps) - return apps, err -} diff --git a/internal/pkg/configmanager/rawconfig_test.go b/internal/pkg/configmanager/rawconfig_test.go deleted file mode 100644 index ea976dfd9..000000000 --- a/internal/pkg/configmanager/rawconfig_test.go +++ /dev/null @@ -1,238 +0,0 @@ -package configmanager - -import ( - "fmt" - "strings" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -var _ = Describe("newRawConfigFromConfigText func", func() { - var testText []byte - const ( - validConfigTextWithoutKey = ` - state: - backend: local - options: - stateFile: devstream.state` - validVarsTextWithoutKey = ` - tests: argocd` - validAppsTextWithoutKey = ` -- name: service-a - spec: - language: python - framework: [[ var1 ]]/[[ var2 ]]_gg` - ) - When("text is valid", func() { - BeforeEach(func() { - testText = []byte(fmt.Sprintf(`--- -config:%s -vars:%s -apps:%s`, validConfigTextWithoutKey, validVarsTextWithoutKey, validAppsTextWithoutKey)) - }) - It("should return config map", func() { - rawMap, err := newRawConfigFromConfigBytes(testText) - Expect(err).ShouldNot(HaveOccurred()) - removeLineFeed := func(s string) string { - return strings.ReplaceAll(s, "\n", "") - } - Expect(removeLineFeed(string(rawMap.apps))).Should(Equal(removeLineFeed(validAppsTextWithoutKey))) - Expect(removeLineFeed(string(rawMap.config))).Should(Equal(removeLineFeed(validConfigTextWithoutKey))) - Expect(removeLineFeed(string(rawMap.vars))).Should(Equal(removeLineFeed(validVarsTextWithoutKey))) - }) - }) - When("text is invalid", func() { - When("invalid keys are used", func() { - BeforeEach(func() { - testText = []byte(fmt.Sprintf(`--- -config: -%s -vars: -%s -app: -%s`, validConfigTextWithoutKey, validVarsTextWithoutKey, validAppsTextWithoutKey)) - }) - It("should return error", func() { - _, err := newRawConfigFromConfigBytes(testText) - Expect(err).Should(HaveOccurred()) - }) - }) - - When("there are no enough keys", func() { - BeforeEach(func() { - testText = []byte(fmt.Sprintf(` -config: -%s -`, validConfigTextWithoutKey)) - }) - It("should return error", func() { - _, err := newRawConfigFromConfigBytes(testText) - Expect(err).Error().Should(HaveOccurred()) - }) - }) - }) - -}) - -var _ = Describe("rawConfig struct", func() { - var r *rawConfig - Context("checkValid method", func() { - When("config is not exist", func() { - BeforeEach(func() { - r = &rawConfig{ - apps: []byte("apps"), - } - }) - It("should return error", func() { - e := r.validate() - Expect(e).Error().Should(HaveOccurred()) - Expect(e.Error()).Should(Equal("config not valid; check the [config] section of your config file")) - }) - }) - When("apps and tools is not exist", func() { - BeforeEach(func() { - r = &rawConfig{ - config: []byte("config"), - } - }) - It("should return error", func() { - e := r.validate() - Expect(e).Error().Should(HaveOccurred()) - Expect(e.Error()).Should(Equal("config not valid; check the [tools and apps] section of your config file")) - }) - }) - }) - - Context("getTemplateMap method", func() { - BeforeEach(func() { - r = &rawConfig{ - pipelineTemplates: []byte(`--- -- name: ci-pipeline-for-gh-actions - type: github-actions # corresponding to a plugin - options: - docker: - registry: - username: [[ dockerUser ]] - repository: [[ app ]] -- name: cd-pipeline-for-argocdapp - type: argocdapp - options: - app: - namespace: [[ argocdNamespace ]]/[[ test ]] # you can use global vars in templates -`)} - }) - It("should get template", func() { - m, err := r.getTemplatePipelineMap() - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(m).Should(Equal(map[string]string{ - "ci-pipeline-for-gh-actions": "name: ci-pipeline-for-gh-actions\ntype: github-actions\noptions:\n docker:\n registry:\n repository: '[[ app ]]'\n username: '[[ dockerUser ]]'\n", - "cd-pipeline-for-argocdapp": "name: cd-pipeline-for-argocdapp\ntype: argocdapp\noptions:\n app:\n namespace: '[[ argocdNamespace ]]/[[ test ]]'\n", - })) - }) - }) - - Context("getApps method", func() { - When("app file is not valid", func() { - BeforeEach(func() { - r = &rawConfig{ - apps: []byte(` ---- -- name: app-1 - cd: - - type: template - vars: - app: [[ appName ]]`)} - }) - It("should get apps", func() { - vars := map[string]any{} - _, err := r.getAppsWithVars(vars) - Expect(err).Should(HaveOccurred()) - Expect(err.Error()).Should(ContainSubstring("has no entry")) - }) - }) - }) - Context("getConfig method", func() { - BeforeEach(func() { - r = &rawConfig{ - config: []byte(` - state: - backend: local - options: - stateFile: devstream.state`)} - }) - - When("get core config from config file", func() { - It("should works fine", func() { - cc, err := r.getConfig() - Expect(err).NotTo(HaveOccurred()) - Expect(cc).NotTo(BeNil()) - Expect(cc.State.Backend).To(Equal("local")) - Expect(cc.State.Options.StateFile).To(Equal("devstream.state")) - }) - }) - }) - - Context("getTools method", func() { - BeforeEach(func() { - r = &rawConfig{ - tools: []byte(` -- name: name1 - instanceID: ins-1 - dependsOn: [] - options: - foo: [[ foo ]]`)} - }) - - When("get tools from config file", func() { - It("should return config with vars", func() { - tools, err := r.getToolsWithVars(map[string]any{"foo": interface{}("bar")}) - Expect(err).NotTo(HaveOccurred()) - Expect(tools).NotTo(BeNil()) - Expect(len(tools)).To(Equal(1)) - Expect(len(tools[0].Options)).To(Equal(1)) - Expect(tools[0].Options["foo"]).To(Equal(interface{}("bar"))) - }) - }) - }) - Context("getPipelineTemplateMap method", func() { - BeforeEach(func() { - r = &rawConfig{ - pipelineTemplates: []byte(` -- name: tpl1 - type: github-actions - options: - foo: bar`)} - }) - When("get tools from config file", func() { - It("should return config with vars", func() { - pipelineTemplatesMap, err := r.getTemplatePipelineMap() - Expect(err).NotTo(HaveOccurred()) - Expect(pipelineTemplatesMap).NotTo(BeNil()) - Expect(len(pipelineTemplatesMap)).To(Equal(1)) - Expect(pipelineTemplatesMap["tpl1"]).To(Equal("name: tpl1\ntype: github-actions\noptions:\n foo: bar\n")) - }) - }) - }) - - Context("getVars method", func() { - BeforeEach(func() { - r = &rawConfig{ - vars: []byte(`--- -foo1: bar1 -foo2: 123 -foo3: [[foo1]]+[[ foo2]]! -foo4: [[foo1]]+[[ foo2]]+sep+[[ foo3 ]]!`)} - }) - It("should works fine", func() { - varMap, err := r.getVars() - Expect(err).NotTo(HaveOccurred()) - Expect(varMap).NotTo(BeNil()) - Expect(len(varMap)).To(Equal(4)) - Expect(varMap["foo1"]).To(Equal(interface{}("bar1"))) - Expect(varMap["foo2"]).To(Equal(interface{}(123))) - Expect(varMap["foo3"]).To(Equal("bar1+123!")) - Expect(varMap["foo4"]).To(Equal("bar1+123+sep+bar1+123!!")) - }) - }) -}) diff --git a/internal/pkg/configmanager/tool.go b/internal/pkg/configmanager/tool.go deleted file mode 100644 index af136ef47..000000000 --- a/internal/pkg/configmanager/tool.go +++ /dev/null @@ -1,171 +0,0 @@ -package configmanager - -import ( - "fmt" - "runtime" - "strings" - - "gopkg.in/yaml.v3" - - "github.com/devstream-io/devstream/internal/pkg/version" - "github.com/devstream-io/devstream/pkg/util/validator" -) - -var ( - GOOS string = runtime.GOOS - GOARCH string = runtime.GOARCH -) - -type RawOptions map[string]any - -// Tool is the struct for one section of the DevStream tool file (part of the config.) -type Tool struct { - Name string `yaml:"name" validate:"required"` - // RFC 1123 - DNS Subdomain Names style - // contain no more than 253 characters - // contain only lowercase alphanumeric characters, '-' or '.' - // start with an alphanumeric character - // end with an alphanumeric character - InstanceID string `yaml:"instanceID" validate:"required,dns1123subdomain"` - DependsOn []string `yaml:"dependsOn"` - Options RawOptions `yaml:"options"` -} - -func newTool(name, instanceID string, options RawOptions) *Tool { - if options == nil { - options = make(RawOptions) - } - - return &Tool{ - Name: name, - InstanceID: instanceID, - DependsOn: []string{}, - Options: options, - } -} - -func (t *Tool) String() string { - bs, err := yaml.Marshal(t) - if err != nil { - return err.Error() - } - return string(bs) -} - -type Tools []*Tool - -func (tools Tools) validateAll() error { - var errs validator.StructFieldErrors - for _, tool := range tools { - errs = append(errs, validator.CheckStructError(tool)...) - } - errs = append(errs, tools.validateDependsOnConfig()...) - errs = append(errs, tools.duplicatedCheck()...) - return errs.Combine() -} - -func (t *Tool) DeepCopy() *Tool { - var retTool = Tool{ - Name: t.Name, - InstanceID: t.InstanceID, - DependsOn: t.DependsOn, - Options: RawOptions{}, - } - for k, v := range t.Options { - retTool.Options[k] = v - } - return &retTool -} - -func (t *Tool) KeyWithNameAndInstanceID() string { - return fmt.Sprintf("%s.%s", t.Name, t.InstanceID) -} - -// GetPluginName return plugin name without file extensions -func (t *Tool) GetPluginName() string { - return fmt.Sprintf("%s-%s-%s_%s", t.Name, GOOS, GOARCH, version.Version) -} - -func (t *Tool) GetPluginNameWithOSAndArch(os, arch string) string { - return fmt.Sprintf("%s-%s-%s_%s", t.Name, os, arch, version.Version) -} - -// GetPluginFileName creates the file name based on the tool's name and version -// If the plugin {github-actions 0.0.1}, the generated name will be "github-actions_0.0.1.so" -func (t *Tool) GetPluginFileName() string { - return t.GetPluginName() + ".so" -} - -func (t *Tool) GetPluginFileNameWithOSAndArch(os, arch string) string { - return t.GetPluginNameWithOSAndArch(os, arch) + ".so" -} - -func (t *Tool) GetPluginMD5FileName() string { - return t.GetPluginName() + ".md5" -} -func (t *Tool) GetPluginMD5FileNameWithOSAndArch(os, arch string) string { - return t.GetPluginNameWithOSAndArch(os, arch) + ".md5" -} - -func (tools Tools) duplicatedCheck() (errs []error) { - list := make(map[string]struct{}) - for _, t := range tools { - key := t.KeyWithNameAndInstanceID() - if _, ok := list[key]; ok { - errs = append(errs, fmt.Errorf("tool or app <%s> is duplicated", key)) - } - list[key] = struct{}{} - } - return errs -} - -// validateDependsOnConfig is used to validate all tools' DependsOn config -func (tools Tools) validateDependsOnConfig() (retErrs []error) { - retErrs = make([]error, 0) - toolKeySet := make(map[string]struct{}) - - // record all tools' key with name.instanceID - for _, tool := range tools { - toolKeySet[tool.KeyWithNameAndInstanceID()] = struct{}{} - } - - validateOneTool := func(tool *Tool) (errs []error) { - errs = make([]error, 0) - if len(tool.DependsOn) == 0 { - return - } - for _, d := range tool.DependsOn { - if strings.TrimSpace(d) == "" { - continue - } - - if _, ok := toolKeySet[d]; !ok { - errs = append(errs, fmt.Errorf("tool %s's DependsOn %s doesn't exist", tool.InstanceID, d)) - } - } - return - } - - for _, t := range tools { - retErrs = append(retErrs, validateOneTool(t)...) - } - - return -} - -func (tools Tools) updateToolDepends(dependTools Tools) { - for _, tool := range tools { - for _, dependTool := range dependTools { - tool.DependsOn = append(tool.DependsOn, dependTool.KeyWithNameAndInstanceID()) - } - } -} - -func (tools Tools) renderInstanceIDtoOptions() { - for _, t := range tools { - if t.Options == nil { - t.Options = make(RawOptions) - } - t.Options["instanceID"] = t.InstanceID - } -} diff --git a/internal/pkg/configmanager/tool_test.go b/internal/pkg/configmanager/tool_test.go deleted file mode 100644 index ecf083f8a..000000000 --- a/internal/pkg/configmanager/tool_test.go +++ /dev/null @@ -1,207 +0,0 @@ -package configmanager - -import ( - "fmt" - "runtime" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -var tools Tools - -var _ = Describe("validateDependsOnConfig", func() { - When("empty dep", func() { - BeforeEach(func() { - tools = Tools{ - {InstanceID: "ins-1", Name: "plugin1"}, - {InstanceID: "ins-2", Name: "plugin2"}, - } - }) - It("should not have errors", func() { - errs := tools.validateDependsOnConfig() - Expect(len(errs)).To(Equal(0)) - }) - }) - - When("singe dep", func() { - BeforeEach(func() { - tools = Tools{ - {InstanceID: "ins-1", Name: "plugin1"}, - {InstanceID: "ins-2", Name: "plugin2"}, - } - }) - It("should not have errors", func() { - tools[1].DependsOn = []string{"plugin1.ins-1"} - errs := tools.validateDependsOnConfig() - Expect(len(errs)).To(Equal(0)) - }) - It("should has some errors", func() { - tools[1].DependsOn = []string{"plugin1.ins-2"} - errs := tools.validateDependsOnConfig() - Expect(len(errs)).To(Equal(1)) - }) - }) - - When("multi-dep", func() { - BeforeEach(func() { - tools = Tools{ - {InstanceID: "ins-1", Name: "plugin1"}, - {InstanceID: "ins-2", Name: "plugin2"}, - {InstanceID: "ins-3", Name: "plugin3"}, - } - }) - It("should not have errors", func() { - tools[2].DependsOn = []string{"plugin1.ins-1"} - tools[2].DependsOn = []string{"plugin2.ins-2"} - tools[1].DependsOn = []string{"plugin1.ins-1"} - errs := tools.validateDependsOnConfig() - Expect(len(errs)).To(Equal(0)) - }) - It("should has some errors", func() { - tools[1].DependsOn = []string{"plugin1.ins-3"} - tools[2].DependsOn = []string{"plugin1.ins-2", "plugin2.ins-1"} - errs := tools.validateDependsOnConfig() - Expect(len(errs)).To(Equal(3)) - }) - }) -}) - -var _ = Describe("Tool Validation all", func() { - It("should return empty error array if tools all valid", func() { - tools := Tools{ - {Name: "test_tool", InstanceID: "0", DependsOn: []string{}}, - } - err := tools.validateAll() - Expect(err).ShouldNot(HaveOccurred()) - }) - It("should return error if tool not valid", func() { - tools := Tools{ - {Name: "", InstanceID: "", DependsOn: []string{}}, - } - err := tools.validateAll() - Expect(err).Should(HaveOccurred()) - }) - -}) - -var _ = Describe("Tool struct", func() { - var ( - t *Tool - name, instanceID string - opts RawOptions - ) - BeforeEach(func() { - name = "test" - instanceID = "test_instance" - opts = RawOptions{"test": "gg"} - t = &Tool{ - Name: name, - InstanceID: instanceID, - Options: opts, - } - }) - Context("DeepCopy method", func() { - It("should copy struct", func() { - x := t.DeepCopy() - Expect(x).Should(Equal(t)) - }) - }) - Context("GetPluginName method", func() { - It("should return valid", func() { - Expect(t.GetPluginName()).Should(Equal( - fmt.Sprintf("%s-%s-%s_%s", t.Name, runtime.GOOS, runtime.GOARCH, ""), - )) - }) - }) - Context("GetPluginFileName method", func() { - It("should return valid", func() { - Expect(t.GetPluginFileName()).Should( - Equal(fmt.Sprintf("%s-%s-%s_%s.so", t.Name, runtime.GOOS, runtime.GOARCH, "")), - ) - }) - }) - Context("GetPluginFileNameWithOSAndArch method", func() { - It("should return valid", func() { - Expect(t.GetPluginFileNameWithOSAndArch("test", "plug")).Should(Equal("test-test-plug_.so")) - }) - }) - Context("GetPluginMD5FileName method", func() { - It("should return valid", func() { - Expect(t.GetPluginMD5FileName()).Should( - Equal(fmt.Sprintf("%s-%s-%s_%s.md5", t.Name, runtime.GOOS, runtime.GOARCH, "")), - ) - }) - }) - Context("GetPluginMD5FileNameWithOSAndArch method", func() { - It("should return valid", func() { - Expect(t.GetPluginMD5FileNameWithOSAndArch("test", "plug")).Should(Equal("test-test-plug_.md5")) - }) - }) -}) - -var _ = Describe("Tool struct", func() { - var tools Tools - - const ( - toolName = "test_tool" - instanceID = "test_instance" - ) - Context("renderInstanceIDtoOptions method", func() { - BeforeEach(func() { - tools = Tools{ - {Name: toolName, InstanceID: instanceID}, - } - }) - When("tool option is null", func() { - It("should set nil to RawOptions", func() { - tools.renderInstanceIDtoOptions() - Expect(len(tools)).Should(Equal(1)) - tool := tools[0] - Expect(tool.Options).Should(Equal(RawOptions{ - "instanceID": instanceID, - })) - }) - }) - }) -}) - -var _ = Describe("duplicatedCheck", func() { - var ( - errs []error - tools Tools - toolsWithoutDuplicated = Tools{ - {Name: "test_tool", InstanceID: "0"}, - {Name: "test_tool", InstanceID: "1"}, - {Name: "test_tool", InstanceID: "2"}, - } - - toolsWithDuplicated = Tools{ - {Name: "test_tool", InstanceID: "0"}, - {Name: "test_tool", InstanceID: "1"}, - {Name: "test_tool", InstanceID: "0"}, - } - ) - - JustBeforeEach(func() { - errs = tools.duplicatedCheck() - }) - - When("tools has duplicated name and instanceID", func() { - BeforeEach(func() { - tools = toolsWithDuplicated - }) - It("should return error", func() { - Expect(errs).Should(HaveLen(1)) - }) - }) - - When("tools don't have duplicated name and instanceID", func() { - BeforeEach(func() { - tools = toolsWithoutDuplicated - }) - It("should return nil", func() { - Expect(errs).Should(BeEmpty()) - }) - }) -}) diff --git a/internal/pkg/configmanager/var.go b/internal/pkg/configmanager/var.go deleted file mode 100644 index f1c3555db..000000000 --- a/internal/pkg/configmanager/var.go +++ /dev/null @@ -1,76 +0,0 @@ -package configmanager - -import ( - "fmt" - "strings" - - "github.com/devstream-io/devstream/pkg/util/template" -) - -func parseNestedVars(origin map[string]any) (map[string]any, error) { - unparsed := make(map[string]any, len(origin)) - for k, v := range origin { - unparsed[k] = v - } - - parsed := make(map[string]any, len(origin)) - updated := true // if any vars were updated in one loop - - // loop until: - // 1. all vars have been parsed - // 2. or can not render more vars by existing parsed vars - for len(unparsed) > 0 && updated { - updated = false - // use "parsed map" to parse each in "unparsed map". - // once one value doesn't contain other keys, put it into "parsed map". - - // a util func which implements the "put" steps - putParsedValue := func(k string, v any) { - parsed[k] = v - delete(unparsed, k) - updated = true - } - - for k, v := range unparsed { - // if value is not string or doesn't contain var, just put - vString, ok := v.(string) - if !ok || !ifContainVar(vString) { - putParsedValue(k, v) - continue - } - - // parse one value with "parsed map" - valueParsed, err := template.NewRenderClient(&template.TemplateOption{}, - template.ContentGetter, template.AddDotForVariablesInConfigProcessor). - Render(fmt.Sprintf("%v", v), parsed) - // if no error(means this value doesn't import vars in "unparsed map"), put - if err == nil { - putParsedValue(k, valueParsed) - } - } - } - - // check if vars map is correct - if len(unparsed) > 0 { - errString := "failed to parse var " - var errKeyValues []string - for k := range unparsed { - errKeyValues = append(errKeyValues, fmt.Sprintf(`<"%s": "%s">`, k, origin[k])) - } - errString += strings.Join(errKeyValues, ", ") - return nil, fmt.Errorf(errString) - } - - return parsed, nil - -} - -// check if value contains other vars -func ifContainVar(value string) bool { - // this function is humble, some case could not be checked probably - // e.g. "[[123]]" "[[]]" will be regard as a var - if strings.Contains(value, "[[") && strings.Contains(value, "]]") { - return true - } - return false -} diff --git a/internal/pkg/configmanager/var_test.go b/internal/pkg/configmanager/var_test.go deleted file mode 100644 index 7028ab6fa..000000000 --- a/internal/pkg/configmanager/var_test.go +++ /dev/null @@ -1,138 +0,0 @@ -package configmanager - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -var _ = Describe("parseNestedVars", func() { - var ( - origin, expected, parsed map[string]any - err error - ) - - JustBeforeEach(func() { - parsed, err = parseNestedVars(origin) - }) - - When("vars map is correct", func() { - When("case is simple(no nested)", func() { - BeforeEach(func() { - origin = map[string]any{ - "a": "a", - "b": 123, - } - expected = origin - }) - It("should parse succeed", func() { - Expect(err).Should(Succeed()) - Expect(parsed).Should(Equal(expected)) - }) - }) - When("case is complex(nested once)", func() { - BeforeEach(func() { - origin = map[string]any{ - "a": "[[ b]]a", - "b": "123", - } - expected = map[string]any{ - "a": "123a", - "b": "123", - } - }) - It("should parse succeed", func() { - Expect(err).Should(Succeed()) - Expect(parsed).Should(Equal(expected)) - }) - }) - When("case is complex(nested many times)", func() { - BeforeEach(func() { - origin = map[string]any{ - "a": "[[ b]]a", - "b": 123, - "c": "[[a]]c", - "d": "[[a]]/[[ c ]]/[[b]]", - } - expected = map[string]any{ - "a": "123a", - "b": 123, - "c": "123ac", - "d": "123a/123ac/123", - } - }) - - It("should parse succeed", func() { - Expect(err).Should(Succeed()) - Expect(parsed).Should(Equal(expected)) - }) - }) - }) - - When("vars map is incorrect", func() { - BeforeEach(func() { - origin = map[string]any{ - "a": "[[ b]]a", - "b": "123", - "c": "[[a]]c[[c]]", - "d": "[[a]]/[[ c ]]/[[b]]", - } - expected = map[string]any{ - "a": "123a", - "b": "123", - "c": "123ac", - "d": "123a/123ac/123", - } - }) - - It("should return error", func() { - Expect(err).Should(HaveOccurred()) - }) - }) -}) - -var _ = Describe("ifContainVar", func() { - var ( - value string - checkResult bool - ) - - JustBeforeEach(func() { - checkResult = ifContainVar(value) - }) - - When("contains var", func() { - AfterEach(func() { - Expect(checkResult).To(Equal(true)) - }) - Context("case 1", func() { - BeforeEach(func() { - value = " [[a]]" - }) - It("should check correctly", func() {}) - }) - Context("case 2", func() { - BeforeEach(func() { - value = "[[ b ]]" - }) - It("should check correctly", func() {}) - }) - }) - - When("doesn't contain var", func() { - AfterEach(func() { - Expect(checkResult).To(Equal(false)) - }) - Context("case 1", func() { - BeforeEach(func() { - value = " [[a] ]" - }) - It("should check correctly", func() {}) - }) - Context("case 2", func() { - BeforeEach(func() { - value = " [ b ]]" - }) - It("should check correctly", func() {}) - }) - }) -}) diff --git a/internal/pkg/configmanager/variables.go b/internal/pkg/configmanager/variables.go deleted file mode 100644 index 9fe634f88..000000000 --- a/internal/pkg/configmanager/variables.go +++ /dev/null @@ -1,30 +0,0 @@ -package configmanager - -import ( - "github.com/devstream-io/devstream/pkg/util/log" - "github.com/devstream-io/devstream/pkg/util/template" -) - -func renderConfigWithVariables(fileContent string, variables map[string]interface{}) ([]byte, error) { - logFunc(fileContent, variables) - - str, err := template.NewRenderClient(&template.TemplateOption{ - Name: "configmanager", - }, template.ContentGetter, template.AddDotForVariablesInConfigProcessor, template.AddQuoteForVariablesInConfigProcessor, - ).Render(fileContent, variables) - - if err != nil { - return nil, err - } - - return []byte(str), nil -} - -func logFunc(fileContent string, variables map[string]interface{}) { - log.Debugf("renderConfigWithVariables got str: %s", fileContent) - log.Debug("Vars: ---") - for k, v := range variables { - log.Debugf("%s: %s", k, v) - } - log.Debug("---") -} diff --git a/internal/pkg/configmanager/variables_test.go b/internal/pkg/configmanager/variables_test.go deleted file mode 100644 index 3e75eee4a..000000000 --- a/internal/pkg/configmanager/variables_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package configmanager - -import ( - "fmt" - "os" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -var _ = Describe("renderConfigWithVariables", func() { - When("render a config with variables", func() { - It("should works fine", func() { - var vars = map[string]interface{}{ - "foo1": "bar1", - "foo2": "bar2", - } - result1, err1 := renderConfigWithVariables("[[ foo1 ]]/[[ foo2]]", vars) - result2, err2 := renderConfigWithVariables("a[[ foo1 ]]/[[ foo2]]b", vars) - result3, err3 := renderConfigWithVariables(" [[ foo1 ]] [[ foo2]] ", vars) - Expect(err1).NotTo(HaveOccurred()) - Expect(err2).NotTo(HaveOccurred()) - Expect(err3).NotTo(HaveOccurred()) - Expect(result1).To(Equal([]byte("bar1/bar2"))) - Expect(result2).To(Equal([]byte("abar1/bar2b"))) - Expect(result3).To(Equal([]byte(" bar1 bar2 "))) - }) - }) - - When("there are env variables", func() { - const ( - envKey1, envKey2 = "GITHUB_TOKEN", "GITLAB_TOKEN" - envVal1, envVal2 = "123456", "abcdef" - ) - BeforeEach(func() { - DeferCleanup(os.Setenv, envKey1, os.Getenv(envKey1)) - DeferCleanup(os.Setenv, envKey2, os.Getenv(envKey2)) - }) - - It("should works fine", func() { - err := os.Setenv(envKey1, envVal1) - Expect(err).NotTo(HaveOccurred()) - err = os.Setenv(envKey2, envVal2) - Expect(err).NotTo(HaveOccurred()) - - content := fmt.Sprintf(` -githubToken: [[env %s]] -gitlabToken: [[ env "%s"]] -`, envKey1, envKey2) - expected := fmt.Sprintf(` -githubToken: %s -gitlabToken: %s -`, envVal1, envVal2) - result, err := renderConfigWithVariables(content, nil) - Expect(err).NotTo(HaveOccurred()) - Expect(result).To(Equal([]byte(expected))) - }) - }) -}) diff --git a/internal/pkg/create/create.go b/internal/pkg/create/create.go deleted file mode 100644 index f00adf448..000000000 --- a/internal/pkg/create/create.go +++ /dev/null @@ -1,138 +0,0 @@ -package create - -import ( - "fmt" - "time" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/create/param" - "github.com/devstream-io/devstream/internal/pkg/plugin/argocdapp" - general "github.com/devstream-io/devstream/internal/pkg/plugin/githubactions" - "github.com/devstream-io/devstream/internal/pkg/plugin/reposcaffolding" - "github.com/devstream-io/devstream/pkg/util/cli" -) - -func Create() error { - params, err := param.GetParams() - if err != nil { - return err - } - fmt.Printf("Start create app %s's stream...\n", params.GitHubRepo) - return create(params) -} - -// create will do following three things: -// 1. create repo by repoScaffolding -// 2. config GitHub actions for this repo -// 3. create Argo CD application for this repo -func create(params *param.Param) error { - if err := createRepo(params); err != nil { - return err - } - - if err := createApp(params); err != nil { - return err - } - - // finalMessage is used to help user to vist this app - finalMessage := `You can now connect to you app with: - - kubectl port-forward service/%s 8080:8080 -n default - -Then you can visit this app by http://127.0.0.1:8080 in your browser. - -Happy Hacking! 😊 -` - fmt.Printf(finalMessage, params.GitHubRepo) - return nil -} - -func createRepo(params *param.Param) error { - repoOptions := configmanager.RawOptions{ - "owner": params.GithubUsername, - "name": params.GitHubRepo, - "scmType": "github", - "token": params.GithubToken, - } - - // 1.create repo - status := cli.StatusForPlugin() - repoScaffoldingOptions := configmanager.RawOptions{ - "destinationRepo": repoOptions, - "sourceRepo": configmanager.RawOptions{ - "url": params.RepoScaffoldingURL, - }, - } - status.Start("Creating repo from scaffolding 🖼") - _, err := reposcaffolding.Create(repoScaffoldingOptions) - status.End(err) - if err != nil { - return err - } - - // 2.config ci - ciOptions := configmanager.RawOptions{ - "scm": repoOptions, - "pipeline": configmanager.RawOptions{ - "language": configmanager.RawOptions{ - "name": params.Language, - "framework": params.Framework, - }, - "imageRepo": configmanager.RawOptions{ - "user": params.DockerhubUsername, - "password": params.DockerhubToken, - }, - }, - } - status.Start("Writing github action configuration ✍️ ") - _, err = general.Create(ciOptions) - status.End(err) - status.Start("Waiting for github action finished 🐎") - - // 3.wait repo ci finished - waitCIFinished() - status.End(err) - return err -} - -func createApp(params *param.Param) error { - status := cli.StatusForPlugin() - argocdAppOption := configmanager.RawOptions{ - "app": configmanager.RawOptions{ - "name": params.GitHubRepo, - "namespace": "argocd", - }, - "destination": configmanager.RawOptions{ - "server": "https://kubernetes.default.svc", - "namespace": "default", - }, - "source": configmanager.RawOptions{ - "valuefile": "values.yaml", - "path": fmt.Sprintf("helm/%s", params.GitHubRepo), - "repoURL": fmt.Sprintf("https://github.com/%s/%s", params.GithubUsername, params.GitHubRepo), - "repoBranch": "main", - "token": params.GithubToken, - }, - "imageRepo": configmanager.RawOptions{ - "user": params.DockerhubUsername, - }, - } - status.Start("Creating argocd app 🕹️") - _, err := argocdapp.Create(argocdAppOption) - status.End(err) - status.Start("Waiting for app to running 🚀") - // wait argocd app status to running - waitAppUp() - status.End(nil) - return err -} - -// TODO(steinliber): add logic to wait for ci finished -func waitCIFinished() { - time.Sleep(70 * time.Second) // current github actions takes 62 seconds for finished -} - -// TODO(steinliber): add logic to wait for pod start running -func waitAppUp() { - time.Sleep(30 * time.Second) -} diff --git a/internal/pkg/create/param/dockerhub.go b/internal/pkg/create/param/dockerhub.go deleted file mode 100644 index 3946fa435..000000000 --- a/internal/pkg/create/param/dockerhub.go +++ /dev/null @@ -1,40 +0,0 @@ -package param - -import ( - "fmt" - - "github.com/manifoldco/promptui" -) - -func getDockerHubUsername() (string, error) { - prompt := promptui.Prompt{ - Label: "What is your DockerHub username", - Validate: validate, - } - - result, err := prompt.Run() - - if err != nil { - fmt.Printf("Failed to get DockerHub username %v\n", err) - return "", err - } - - return result, nil -} - -func getDockerHubToken() (string, error) { - prompt := promptui.Prompt{ - Label: "Please input your DockerHub Personal Access Token", - Mask: '*', - Validate: validate, - } - - result, err := prompt.Run() - - if err != nil { - fmt.Printf("Failed to get DockerHub token %v\n", err) - return "", err - } - - return result, nil -} diff --git a/internal/pkg/create/param/github.go b/internal/pkg/create/param/github.go deleted file mode 100644 index 3d86ad9ea..000000000 --- a/internal/pkg/create/param/github.go +++ /dev/null @@ -1,57 +0,0 @@ -package param - -import ( - "fmt" - - "github.com/manifoldco/promptui" -) - -func getGitHubUsername() (string, error) { - prompt := promptui.Prompt{ - Label: "What is your GitHub username", - Validate: validate, - } - - result, err := prompt.Run() - - if err != nil { - fmt.Printf("Failed to get GitHub username %v\n", err) - return "", err - } - - return result, nil -} - -func getGitHubRepo() (string, error) { - prompt := promptui.Prompt{ - Label: "What GitHub Repo You Want to Create", - Validate: validate, - Default: "firstapp", - } - - result, err := prompt.Run() - - if err != nil { - fmt.Printf("Failed to get GitHub repo %v\n", err) - return "", err - } - - return result, nil -} - -func getGitHubToken() (string, error) { - prompt := promptui.Prompt{ - Label: "Please input your GitHub Personal Access Token", - Mask: '*', - Validate: validate, - } - - result, err := prompt.Run() - - if err != nil { - fmt.Printf("Failed to get GitHub token %v\n", err) - return "", err - } - - return result, nil -} diff --git a/internal/pkg/create/param/param.go b/internal/pkg/create/param/param.go deleted file mode 100644 index 7ec1d097f..000000000 --- a/internal/pkg/create/param/param.go +++ /dev/null @@ -1,56 +0,0 @@ -package param - -type Param struct { - GithubUsername string - GitHubRepo string - GithubToken string - DockerhubUsername string - DockerhubToken string - Language string - Framework string - RepoScaffoldingURL string -} - -func GetParams() (*Param, error) { - lang, frame, url, err := selectRepoScaffolding() - if err != nil { - return nil, err - } - - githubUsername, err := getGitHubUsername() - if err != nil { - return nil, err - } - - githubRepo, err := getGitHubRepo() - if err != nil { - return nil, err - } - - githubToken, err := getGitHubToken() - if err != nil { - return nil, err - } - - dockerhubUsername, err := getDockerHubUsername() - if err != nil { - return nil, err - } - - dockerhubToken, err := getDockerHubToken() - if err != nil { - return nil, err - } - - param := &Param{ - Language: lang, - Framework: frame, - RepoScaffoldingURL: url, - GithubUsername: githubUsername, - GitHubRepo: githubRepo, - GithubToken: githubToken, - DockerhubUsername: dockerhubUsername, - DockerhubToken: dockerhubToken, - } - return param, nil -} diff --git a/internal/pkg/create/param/repoScaffolding.go b/internal/pkg/create/param/repoScaffolding.go deleted file mode 100644 index 9bfb11403..000000000 --- a/internal/pkg/create/param/repoScaffolding.go +++ /dev/null @@ -1,147 +0,0 @@ -package param - -import ( - "fmt" - "strings" - - "github.com/manifoldco/promptui" -) - -const ( - arrowUp = "↑" - fingerUp = "👆" - backToLanguageSelection = arrowUp + " Back to language selection" -) - -func selectRepoScaffolding() (language, framework, url string, err error) { - languagesRepoMap := make(map[string][]RepoScaffolding) - for _, repo := range ListRepoScaffolding() { - languagesRepoMap[repo.Language] = append(languagesRepoMap[repo.Language], repo) - } - - // use a loop to allow user to go back to language selection - for { - language, err = selectLanguage(languagesRepoMap) - if err != nil { - return - } - - fmt.Println("\nPlease choose a framework next.") - - framework, url, err = selectFrameworks(languagesRepoMap[language]) - if err != nil { - return - } - if framework != backToLanguageSelection { - break - } - } - language = strings.ToLower(language) - framework = strings.ToLower(framework) - - return language, framework, url, nil - -} - -func selectLanguage(languagesRepoMap map[string][]RepoScaffolding) (language string, err error) { - var langFrameworkList []struct { - Language string - Frameworks []string - } - - for lang, repos := range languagesRepoMap { - var frameworks []string - for _, repo := range repos { - frameworks = append(frameworks, repo.Framework) - } - langFrameworkList = append(langFrameworkList, struct { - Language string - Frameworks []string - }{lang, frameworks}) - } - - combinedFuncMap := promptui.FuncMap - combinedFuncMap["join"] = func(list []string, sep string) string { - return strings.Join(list, sep) - } - - templates := &promptui.SelectTemplates{ - Label: "{{ . }}?", - Active: "🐰 {{ .Language | blue }}", - Inactive: " {{ .Language | cyan }}", - Selected: "🐰 Language: {{ .Language | blue | cyan }}", - Details: ` ---------- Supported Frameworks For {{ .Language}} ---------- -{{ join .Frameworks ", " }}`, - FuncMap: combinedFuncMap, - } - - searcher := func(input string, index int) bool { - lang := langFrameworkList[index] - name := strings.Replace(strings.ToLower(lang.Language), " ", "", -1) - input = strings.Replace(strings.ToLower(input), " ", "", -1) - - return strings.Contains(name, input) - } - - prompt := promptui.Select{ - Label: "Choose your language", - Items: langFrameworkList, - Templates: templates, - Size: 4, - Searcher: searcher, - } - - i, _, err := prompt.Run() - - if err != nil { - fmt.Printf("Prompt failed %v\n", err) - return - } - - return langFrameworkList[i].Language, nil -} - -func selectFrameworks(repos []RepoScaffolding) (framework, url string, err error) { - repos = append(repos, RepoScaffolding{ - Name: backToLanguageSelection, - Framework: backToLanguageSelection, - URL: fingerUp, Description: fingerUp, Language: fingerUp}) - templates := &promptui.SelectTemplates{ - Label: "{{ . }}?", - Active: "🐰 {{ .Framework | blue }}", - Inactive: " {{ .Framework | cyan }}", - Selected: "🐰 Framework: {{ .Framework | blue | cyan }}", - Details: ` ---------- Language/Framework Details ---------- -{{ "Repo Template Name:" | faint }} {{ .Name }} -{{ "Language:" | faint }} {{ .Language }} -{{ "Framework:" | faint }} {{ .Framework }} -{{ "Description:" | faint }} {{ .Description }}`, - } - - searcher := func(input string, index int) bool { - repo := repos[index] - name := strings.Replace(strings.ToLower(repo.Name), " ", "", -1) - input = strings.Replace(strings.ToLower(input), " ", "", -1) - - return strings.Contains(name, input) - } - - prompt := promptui.Select{ - Label: "Choose your framework", - Items: repos, - Templates: templates, - Size: 4, - Searcher: searcher, - } - - i, _, err := prompt.Run() - - if err != nil { - fmt.Printf("Prompt failed %v\n", err) - return - } - - return repos[i].Framework, repos[i].URL, nil -} diff --git a/internal/pkg/create/param/repos.go b/internal/pkg/create/param/repos.go deleted file mode 100644 index d038bcca2..000000000 --- a/internal/pkg/create/param/repos.go +++ /dev/null @@ -1,32 +0,0 @@ -package param - -func ListRepoScaffolding() []RepoScaffolding { - return []RepoScaffolding{ - {Name: "dtm-repo-scaffolding-golang-gin", - URL: "https://github.com/devstream-io/dtm-repo-scaffolding-golang-gin", - Language: "Golang", - Framework: "Gin", - Description: "This is a scaffolding for Golang web app based on Gin framework", - }, - {Name: "dtm-repo-scaffolding-python-flask", - URL: "https://github.com/devstream-io/dtm-repo-scaffolding-python-flask", - Language: "Python", - Framework: "Flask", - Description: "This is a scaffolding for Python web app based on Flask framework", - }, - {Name: "dtm-repo-scaffolding-java-springboot", - URL: "https://github.com/devstream-io/dtm-repo-scaffolding-java-springboot", - Language: "Java", - Framework: "SpringBoot", - Description: "This is a scaffolding for Java web app based on SpringBoot framework", - }, - } -} - -type RepoScaffolding struct { - Name string - URL string - Language string - Framework string - Description string -} diff --git a/internal/pkg/create/param/validate.go b/internal/pkg/create/param/validate.go deleted file mode 100644 index f97ff25e0..000000000 --- a/internal/pkg/create/param/validate.go +++ /dev/null @@ -1,13 +0,0 @@ -package param - -import ( - "errors" - "strings" -) - -var validate = func(input string) error { - if strings.TrimSpace(input) == "" { - return errors.New("input can not be empty") - } - return nil -} diff --git a/internal/pkg/develop/develop.go b/internal/pkg/develop/develop.go deleted file mode 100644 index 6dc88c85c..000000000 --- a/internal/pkg/develop/develop.go +++ /dev/null @@ -1,33 +0,0 @@ -package develop - -import ( - "github.com/devstream-io/devstream/internal/pkg/develop/plugin" - "github.com/devstream-io/devstream/pkg/util/log" -) - -type Action string - -const ( - ActionCreatePlugin Action = "create-plugin" - ActionValidatePlugin Action = "validate-plugin" -) - -var ActionSet = map[Action]struct{}{ - ActionCreatePlugin: {}, - ActionValidatePlugin: {}, -} - -func IsValideAction(action Action) bool { - _, ok := ActionSet[action] - return ok -} - -func CreatePlugin() error { - log.Debugf("Start create plugin") - return plugin.Create() -} - -func ValidatePlugin() error { - log.Debugf("Start validate plugin") - return plugin.Validate() -} diff --git a/internal/pkg/develop/plugin/create.go b/internal/pkg/develop/plugin/create.go deleted file mode 100644 index ec469fd4e..000000000 --- a/internal/pkg/develop/plugin/create.go +++ /dev/null @@ -1,56 +0,0 @@ -package plugin - -import ( - "fmt" - - "github.com/spf13/viper" - - "github.com/devstream-io/devstream/cmd/devstream/list" - "github.com/devstream-io/devstream/pkg/util/log" -) - -// Create creates a new plugin. -// 1. Render template files -// 2. Persist all files -// 3. Print help information -func Create() error { - name := viper.GetString("name") - if name == "" { - return fmt.Errorf(`the name must be not "", you can specify it by --name flag`) - } - log.Debugf("Got the name: %s.", name) - - if pluginExists(name) { - return fmt.Errorf("plugin %s already exists", name) - } - log.Debugf("Got the name: %s.", name) - - p := NewPlugin(name) - - // 1. Render template files - files, err := p.RenderTplFiles() - if err != nil { - log.Debugf("Failed to render template files: %s.", err) - return err - } - log.Info("Render template files finished.") - - // 2. Persist all files - if err := p.PersistFiles(files); err != nil { - log.Debugf("Failed to persist files: %s.", err) - } - log.Info("Persist all files finished.") - - // 3. Print help information - p.PrintHelpInfo() - - return nil -} - -// Check whether the new name exists. -func pluginExists(name string) bool { - pluginMp := list.PluginNamesMap() - _, ok := (pluginMp)[name] - - return ok -} diff --git a/internal/pkg/develop/plugin/plugin.go b/internal/pkg/develop/plugin/plugin.go deleted file mode 100644 index 73775c46d..000000000 --- a/internal/pkg/develop/plugin/plugin.go +++ /dev/null @@ -1,202 +0,0 @@ -package plugin - -import ( - "fmt" - "os" - "path" - "strings" - "text/template" - - pluginTpl "github.com/devstream-io/devstream/internal/pkg/develop/plugin/template" - "github.com/devstream-io/devstream/pkg/util/log" - templateUtil "github.com/devstream-io/devstream/pkg/util/template" -) - -type Plugin struct { - Name string -} - -func NewPlugin(name string) *Plugin { - return &Plugin{ - Name: name, - } -} - -// RenderTplFiles takes specified data that the templates needed, -// then render TplFiles to "Files" and return it as []File. -func (p *Plugin) RenderTplFiles() ([]pluginTpl.File, error) { - retFiles := make([]pluginTpl.File, 0) - count := len(pluginTpl.TplFiles) - log.Debugf("Template files count: %d.", count) - - for i, tplFile := range pluginTpl.TplFiles { - log.Debugf("Render process: %d/%d.", i+1, count) - file, err := p.renderTplFile(&tplFile) - if err != nil { - return nil, err - } - log.Debugf("File: %v.", *file) - retFiles = append(retFiles, *file) - } - - return retFiles, nil -} - -// RenderTplFile takes specified data that the template needed, -// then render one TplFile to File and return it. -func (p *Plugin) renderTplFile(tplFile *pluginTpl.TplFile) (*pluginTpl.File, error) { - name, err := p.renderTplString(tplFile.NameTpl) - if err != nil { - return nil, err - } - dir, err := p.renderTplString(tplFile.DirTpl) - if err != nil { - return nil, err - } - content, err := p.renderTplString(tplFile.ContentTpl) - if err != nil { - return nil, err - } - mustExistFlag := tplFile.MustExistFlag - - return &pluginTpl.File{ - Name: name, - Dir: dir, - Content: content, - MustExistFlag: mustExistFlag, - }, nil -} - -// renderTplString get the template string and the data object, -// then render it and return the rendered string. -func (p *Plugin) renderTplString(tplStr string) (string, error) { - if tplStr == "" { - return "", nil - } - - var funcMap = template.FuncMap{ - "format": pluginTpl.FormatPackageName, - "dirFormat": pluginTpl.FormatPackageDirName, - } - - res, err := templateUtil.NewRenderClient( - &templateUtil.TemplateOption{ - Name: "plugins", - FuncMap: funcMap, - }, templateUtil.ContentGetter, - ).Render(tplStr, *p) - if err != nil { - log.Debugf("Template execute failed: %s.", err) - log.Debugf("Template content: %s.", tplStr) - log.Debugf("Data object: %v.", *p) - return "", err - } - - return res, nil -} - -// PersistFiles gets the []pluginTpl.File, for each File: -// call the persistFile() method to deal with. -func (p *Plugin) PersistFiles(files []pluginTpl.File) error { - fileCount := len(files) - log.Debugf("There are %d files wait to persist.", fileCount) - for i, file := range files { - log.Debugf("Persist process: %d/%d.", i+1, fileCount) - if err := p.persistFile(&file); err != nil { - log.Errorf("Failed to persist: %s/%s.", file.Dir, file.Name) - return err - } - } - - return nil -} - -// persistFile gets the *pluginTpl.File, then do the following: -// 1. mkdir the File.Dir -// 2. create the File.Name file -// 3. write the File.Content into the File.Name file -func (p *Plugin) persistFile(file *pluginTpl.File) error { - // mkdir the File.Dir - if err := os.MkdirAll(file.Dir, 0755); err != nil { - log.Debugf("Failed to create directory: %s.", file.Dir) - } - log.Debugf("Directory created: %s.", file.Dir) - - // create the File.Name file and write the File.Content into the File.Name file - filePath := path.Join(file.Dir, file.Name) - if err := os.WriteFile(filePath, []byte(file.Content), 0644); err != nil { - log.Debugf("Failed to write content to the file: %s.", err) - } - log.Debugf("File %s has been created.", filePath) - - return nil -} - -func (p *Plugin) PrintHelpInfo() { - help := ` -The DevStream PMC (project management committee) sincerely thank you for your devotion and enthusiasm in creating new plugins! - -To make the process easy as a breeze, DevStream(dtm) has generated some templated source code files for you to flatten the learning curve and reduce manual copy-paste. -In the generated templates, dtm has left some special marks in the format of "TODO(dtm)". -Please look for these TODOs by global search. Once you find them, you will know what to do with them. Also, please remember to check our documentation on creating a new plugin: - -**README_when_create_plugin.md** - -Source code files created. - -Happy hacking, buddy! -Please give us feedback through GitHub issues if you encounter any difficulties. We guarantee that you will receive unrivaled help from our passionate community! -` - fmt.Print(help) -} - -// ValidateFiles Validates the []pluginTpl.File, for each File if File in needValidateFiles: -// call the validateFile() method to deal with. -func (p *Plugin) ValidateFiles(files []pluginTpl.File) error { - fileCount := len(files) - var errs []string - log.Debugf("There are %d files wait to validate.", fileCount) - for i, file := range files { - log.Debugf("Validate process: %d/%d.", i+1, fileCount) - if err := p.validateFile(&file); err != nil { - log.Errorf("Failed to validate: %s%s.", file.Dir, file.Name) - errs = append(errs, err.Error()) - } - } - - if len(errs) != 0 { - log.Debugf("Total number of validation failures: %d.", len(errs)) - log.Errorf(strings.Join(errs, "\n")) - log.Errorf("Plugin <%s> does NOT passed validation.", p.Name) - return nil - } - - log.Successf("Plugin <%s> passed validation.", p.Name) - return nil -} - -// validateFile gets the *pluginTpl.File, then do the following: -// 1. if !MustExistFlag, continue -// 2. verify the existence of file.Dir -// 3. verify the existence of File.Name file -func (p *Plugin) validateFile(file *pluginTpl.File) error { - if !file.MustExistFlag { - log.Debugf("MustExistFlag is not true, no validation: %s%s.", file.Dir, file.Name) - return nil - } - // verify the existence of file.Dir - if _, err := os.Stat(file.Dir); err != nil { - log.Debugf("Directory does not exist: %s.", file.Dir) - return err - } - log.Debugf("Directory existed: %s.", file.Dir) - - // verify the existence of File.Name file - filePath := path.Join(file.Dir, file.Name) - if _, err := os.Stat(filePath); err != nil { - log.Debugf("File does not exist: %s.", filePath) - return err - } - log.Debugf("File existed: %s.", filePath) - return nil -} diff --git a/internal/pkg/develop/plugin/template/NAME.go b/internal/pkg/develop/plugin/template/NAME.go deleted file mode 100644 index 527ce485f..000000000 --- a/internal/pkg/develop/plugin/template/NAME.go +++ /dev/null @@ -1,18 +0,0 @@ -package template - -var NameGoNameTpl = "[[ .Name | format ]].go" -var NameGoDirTpl = "internal/pkg/plugin/[[ .Name | dirFormat ]]/" -var NameGoMustExistFlag = true -var NameGoContentTpl = `package [[ .Name | format ]] - -// TODO(dtm): Add your logic here. -` - -func init() { - TplFiles = append(TplFiles, TplFile{ - NameTpl: NameGoNameTpl, - DirTpl: NameGoDirTpl, - ContentTpl: NameGoContentTpl, - MustExistFlag: NameGoMustExistFlag, - }) -} diff --git a/internal/pkg/develop/plugin/template/NAME.md.tpl b/internal/pkg/develop/plugin/template/NAME.md.tpl deleted file mode 100644 index 140b2bb63..000000000 --- a/internal/pkg/develop/plugin/template/NAME.md.tpl +++ /dev/null @@ -1,9 +0,0 @@ -# [[ .Name ]] Plugin - -TODO(dtm): Add your document here. - -## Usage - -``` yaml ---8<-- "[[ .Name ]].yaml" -``` diff --git a/internal/pkg/develop/plugin/template/NAME.zh.md.tpl b/internal/pkg/develop/plugin/template/NAME.zh.md.tpl deleted file mode 100644 index 8f75d8cc7..000000000 --- a/internal/pkg/develop/plugin/template/NAME.zh.md.tpl +++ /dev/null @@ -1,9 +0,0 @@ -# [[ .Name ]] 插件 - -TODO(dtm): 在这里添加文档. - -## 用例 - -``` yaml ---8<-- "[[ .Name ]].yaml" -``` diff --git a/internal/pkg/develop/plugin/template/NAME_md.go b/internal/pkg/develop/plugin/template/NAME_md.go deleted file mode 100644 index 60ca9e3b5..000000000 --- a/internal/pkg/develop/plugin/template/NAME_md.go +++ /dev/null @@ -1,18 +0,0 @@ -package template - -import _ "embed" - -var NamePluginMdNameTpl = "[[ .Name ]].md" -var NamePluginMdDirTpl = "docs/plugins/" - -//go:embed NAME.md.tpl -var NamePluginMdContentTpl string - -func init() { - - TplFiles = append(TplFiles, TplFile{ - NameTpl: NamePluginMdNameTpl, - DirTpl: NamePluginMdDirTpl, - ContentTpl: NamePluginMdContentTpl, - }) -} diff --git a/internal/pkg/develop/plugin/template/NAME_zh_md.go b/internal/pkg/develop/plugin/template/NAME_zh_md.go deleted file mode 100644 index 6b10cbec9..000000000 --- a/internal/pkg/develop/plugin/template/NAME_zh_md.go +++ /dev/null @@ -1,18 +0,0 @@ -package template - -import _ "embed" - -var NamePluginZhMdNameTpl = "[[ .Name ]].zh.md" -var NamePluginZhMdDirTpl = "docs/plugins/" - -//go:embed NAME.zh.md.tpl -var NamePluginZhMdContentTpl string - -func init() { - - TplFiles = append(TplFiles, TplFile{ - NameTpl: NamePluginZhMdNameTpl, - DirTpl: NamePluginZhMdDirTpl, - ContentTpl: NamePluginZhMdContentTpl, - }) -} diff --git a/internal/pkg/develop/plugin/template/README_when_create_plugin.md.go b/internal/pkg/develop/plugin/template/README_when_create_plugin.md.go deleted file mode 100644 index d85087f7c..000000000 --- a/internal/pkg/develop/plugin/template/README_when_create_plugin.md.go +++ /dev/null @@ -1,18 +0,0 @@ -package template - -var ReadmeWhenCreatePluginMdNameTpl = "README_when_create_plugin.md" -var ReadmeWhenCreatePluginMdDirTpl = "./" -var ReadmeWhenCreatePluginMdContentTpl = `# Note with **dtm develop create-plugin** - -## Done-Done Check - -- [ ] I've finished all the TODO items and deleted all the *TODO(dtm)* comments. -` - -func init() { - TplFiles = append(TplFiles, TplFile{ - NameTpl: ReadmeWhenCreatePluginMdNameTpl, - DirTpl: ReadmeWhenCreatePluginMdDirTpl, - ContentTpl: ReadmeWhenCreatePluginMdContentTpl, - }) -} diff --git a/internal/pkg/develop/plugin/template/config.go b/internal/pkg/develop/plugin/template/config.go deleted file mode 100644 index ecb242650..000000000 --- a/internal/pkg/develop/plugin/template/config.go +++ /dev/null @@ -1,23 +0,0 @@ -package template - -var configGoNameTpl = "[[ .Name ]].yaml" -var configGoDirTpl = "internal/pkg/show/config/plugins/" -var configGoContentTpl = `tools: -# name of the tool -- name: [[ .Name ]] - # id of the tool instance - instanceID: default - # format: name.instanceID; If specified, dtm will make sure the dependency is applied first before handling this tool. - dependsOn: [ ] - # options for the plugin - options: - # TODO(dtm): Add your default config here. -` - -func init() { - TplFiles = append(TplFiles, TplFile{ - NameTpl: configGoNameTpl, - DirTpl: configGoDirTpl, - ContentTpl: configGoContentTpl, - }) -} diff --git a/internal/pkg/develop/plugin/template/create.go b/internal/pkg/develop/plugin/template/create.go deleted file mode 100644 index fedad1e0d..000000000 --- a/internal/pkg/develop/plugin/template/create.go +++ /dev/null @@ -1,50 +0,0 @@ -package template - -var createGoNameTpl = "create.go" -var createGoDirTpl = "internal/pkg/plugin/[[ .Name | dirFormat ]]/" -var createGoContentTpl = `package [[ .Name | format ]] - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" -) - -func Create(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - // Initialize Operator with Operations - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - // TODO(dtm): Add your PreExecuteOperations here. - }, - ExecuteOperations: installer.ExecuteOperations{ - // TODO(dtm): Add your ExecuteOperations here. - }, - TerminateOperations: installer.TerminateOperations{ - // TODO(dtm): Add your TerminateOperations here. - }, - GetStatusOperation: func(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - // TODO(dtm): Add your GetStatusOperation here. - return nil, nil - }, - } - - // Execute all Operations in Operator - status, err := operator.Execute(options) - if err != nil { - return nil, err - } - log.Debugf("Return map: %v", status) - return status, nil -} -` -var create_go_mustExistFlag = true - -func init() { - TplFiles = append(TplFiles, TplFile{ - NameTpl: createGoNameTpl, - DirTpl: createGoDirTpl, - ContentTpl: createGoContentTpl, - MustExistFlag: create_go_mustExistFlag, - }) -} diff --git a/internal/pkg/develop/plugin/template/delete.go b/internal/pkg/develop/plugin/template/delete.go deleted file mode 100644 index b0069968a..000000000 --- a/internal/pkg/develop/plugin/template/delete.go +++ /dev/null @@ -1,42 +0,0 @@ -package template - -var deleteGoNameTpl = "delete.go" -var deleteGoDirTpl = "internal/pkg/plugin/[[ .Name | dirFormat ]]/" -var deleteGoContentTpl = `package [[ .Name | format ]] - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" -) - -func Delete(options configmanager.RawOptions) (bool, error) { - // Initialize Operator with Operations - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - // TODO(dtm): Add your PreExecuteOperations here. - }, - ExecuteOperations: installer.ExecuteOperations{ - // TODO(dtm): Add your ExecuteOperations here. - }, - } - - // Execute all Operations in Operator - _, err := operator.Execute(options) - if err != nil { - return false, err - } - - return true, nil -} -` - -var delete_go_mustExistFlag = true - -func init() { - TplFiles = append(TplFiles, TplFile{ - NameTpl: deleteGoNameTpl, - DirTpl: deleteGoDirTpl, - ContentTpl: deleteGoContentTpl, - MustExistFlag: delete_go_mustExistFlag, - }) -} diff --git a/internal/pkg/develop/plugin/template/funcmaps.go b/internal/pkg/develop/plugin/template/funcmaps.go deleted file mode 100644 index f3b04a5df..000000000 --- a/internal/pkg/develop/plugin/template/funcmaps.go +++ /dev/null @@ -1,23 +0,0 @@ -package template - -import "strings" - -func FormatPackageName(name string) string { - if specialPluginName, isOk := SpecialPluginNameMap[name]; isOk { - return specialPluginName.PackageName - } else { - // Remove suffixes like "-integ" - name = strings.TrimSuffix(name, "-integ") - return strings.ReplaceAll(name, "-", "") - } -} - -func FormatPackageDirName(name string) string { - if specialPluginName, isOk := SpecialPluginNameMap[name]; isOk { - return specialPluginName.DirName - } else { - // Remove suffixes like "-integ" - name = strings.TrimSuffix(name, "-integ") - return strings.ReplaceAll(name, "-", "") - } -} diff --git a/internal/pkg/develop/plugin/template/main.go b/internal/pkg/develop/plugin/template/main.go deleted file mode 100644 index 864f88967..000000000 --- a/internal/pkg/develop/plugin/template/main.go +++ /dev/null @@ -1,57 +0,0 @@ -package template - -var mainGoNameTpl = "main.go" -var mainGoDirTpl = "cmd/plugin/[[ .Name ]]/" -var mainGoContentTpl = `package main - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/[[ .Name | format ]]" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" -) - -// NAME is the name of this DevStream plugin. -const NAME = "[[ .Name ]]" - -// Plugin is the type used by DevStream core. It's a string. -type Plugin string - -// Create implements the create of [[ .Name ]]. -func (p Plugin) Create(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return [[ .Name | format ]].Create(options) -} - -// Update implements the update of [[ .Name ]]. -func (p Plugin) Update(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return [[ .Name | format ]].Update(options) -} - -// Delete implements the delete of [[ .Name ]]. -func (p Plugin) Delete(options configmanager.RawOptions) (bool, error) { - return [[ .Name | format ]].Delete(options) -} - -// Read implements the read of [[ .Name ]]. -func (p Plugin) Read(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return [[ .Name | format ]].Read(options) -} - -// DevStreamPlugin is the exported variable used by the DevStream core. -var DevStreamPlugin Plugin - -func main() { - log.Infof("%T: %s is a plugin for DevStream. Use it with DevStream.\n", DevStreamPlugin, NAME) -} -` - -var mainGoMustExistFlag = true - -func init() { - TplFiles = append(TplFiles, TplFile{ - NameTpl: mainGoNameTpl, - DirTpl: mainGoDirTpl, - ContentTpl: mainGoContentTpl, - MustExistFlag: mainGoMustExistFlag, - }) -} diff --git a/internal/pkg/develop/plugin/template/options.go b/internal/pkg/develop/plugin/template/options.go deleted file mode 100644 index 1ec9d2a6b..000000000 --- a/internal/pkg/develop/plugin/template/options.go +++ /dev/null @@ -1,35 +0,0 @@ -package template - -var optionsGoNameTpl = "options.go" -var optionsGoDirTpl = "internal/pkg/plugin/[[ .Name | dirFormat ]]/" -var optionsGoContentTpl = `package [[ .Name | format ]] - -import ( - "github.com/mitchellh/mapstructure" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" -) - -// Options is the struct for configurations of the [[ .Name ]] plugin. -type Options struct { - // TODO(dtm): Add your params here. - Foo string -} - -// NewOptions create options by raw options -func NewOptions(options configmanager.RawOptions) (Options, error) { - var opts Options - if err := mapstructure.Decode(options, &opts); err != nil { - return opts, err - } - return opts, nil -} -` - -func init() { - TplFiles = append(TplFiles, TplFile{ - NameTpl: optionsGoNameTpl, - DirTpl: optionsGoDirTpl, - ContentTpl: optionsGoContentTpl, - }) -} diff --git a/internal/pkg/develop/plugin/template/read.go b/internal/pkg/develop/plugin/template/read.go deleted file mode 100644 index 52541e31f..000000000 --- a/internal/pkg/develop/plugin/template/read.go +++ /dev/null @@ -1,42 +0,0 @@ -package template - -var readGoNameTpl = "read.go" -var readGoDirTpl = "internal/pkg/plugin/[[ .Name | dirFormat ]]/" -var readGoContentTpl = `package [[ .Name | format ]] - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" -) - -func Read(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - // Initialize Operator with Operations - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - // TODO(dtm): Add your PreExecuteOperations here. - }, - GetStatusOperation: func(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - // TODO(dtm): Add your GetStatusOperation here. - return nil, nil - }, - } - - // Execute all Operations in Operator - status, err := operator.Execute(options) - if err != nil { - return nil, err - } - log.Debugf("Return map: %v", status) - return status, nil -} -` - -func init() { - TplFiles = append(TplFiles, TplFile{ - NameTpl: readGoNameTpl, - DirTpl: readGoDirTpl, - ContentTpl: readGoContentTpl, - }) -} diff --git a/internal/pkg/develop/plugin/template/special_plugins.go b/internal/pkg/develop/plugin/template/special_plugins.go deleted file mode 100644 index f9b126149..000000000 --- a/internal/pkg/develop/plugin/template/special_plugins.go +++ /dev/null @@ -1,19 +0,0 @@ -package template - -type SpecialPlugin struct { - DirName string - PackageName string -} - -func NewSpecialPlugin(dirName string, packageName string) *SpecialPlugin { - return &SpecialPlugin{ - DirName: dirName, - PackageName: packageName, - } -} - -var SpecialPluginNameMap = map[string]*SpecialPlugin{ - "gitlabci-golang": NewSpecialPlugin("gitlabci/golang", "golang"), - "gitlabci-java": NewSpecialPlugin("gitlabci/java", "java"), - "gitlabci-generic": NewSpecialPlugin("gitlabci/generic", "generic"), -} diff --git a/internal/pkg/develop/plugin/template/template.go b/internal/pkg/develop/plugin/template/template.go deleted file mode 100644 index 7e6a52ab5..000000000 --- a/internal/pkg/develop/plugin/template/template.go +++ /dev/null @@ -1,22 +0,0 @@ -package template - -// TplFile is a file contains some template tags like "[[ .Name ]]". -// eg. internal/pkg/develop/plugin/template/create.go is a TplFile. -type TplFile struct { - NameTpl string - DirTpl string - ContentTpl string - MustExistFlag bool -} - -// File is a rendered TplFile that doesn't contain any template tags like "[[ .Name ]]". -type File struct { - Name string - Dir string - Content string - MustExistFlag bool -} - -// TplFiles filled by functions at other go files. -// eg. internal/pkg/develop/plugin/template/create.go init() -var TplFiles = make([]TplFile, 0) diff --git a/internal/pkg/develop/plugin/template/update.go b/internal/pkg/develop/plugin/template/update.go deleted file mode 100644 index 21a1dad8c..000000000 --- a/internal/pkg/develop/plugin/template/update.go +++ /dev/null @@ -1,48 +0,0 @@ -package template - -var updateGoNameTpl = "update.go" -var updateGoDirTpl = "internal/pkg/plugin/[[ .Name | dirFormat ]]/" -var updateGoContentTpl = `package [[ .Name | format ]] - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" -) - -func Update(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - // Initialize Operator with Operations - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - // TODO(dtm): Add your PreExecuteOperations here. - }, - ExecuteOperations: installer.ExecuteOperations{ - // TODO(dtm): Add your ExecuteOperations here. - }, - TerminateOperations: installer.TerminateOperations{ - // TODO(dtm): Add your TerminateOperations here. - }, - GetStatusOperation: func(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - // TODO(dtm): Add your GetStatusOperation here. - return nil, nil - }, - } - - // Execute all Operations in Operator - status, err := operator.Execute(options) - if err != nil { - return nil, err - } - log.Debugf("Return map: %v", status) - return status, nil -} -` - -func init() { - TplFiles = append(TplFiles, TplFile{ - NameTpl: updateGoNameTpl, - DirTpl: updateGoDirTpl, - ContentTpl: updateGoContentTpl, - }) -} diff --git a/internal/pkg/develop/plugin/template/validate.go b/internal/pkg/develop/plugin/template/validate.go deleted file mode 100644 index db17361b9..000000000 --- a/internal/pkg/develop/plugin/template/validate.go +++ /dev/null @@ -1,34 +0,0 @@ -package template - -var validateGoNameTpl = "validate.go" -var validateGoDirTpl = "internal/pkg/plugin/[[ .Name | dirFormat ]]/" -var validateGoContentTpl = `package [[ .Name | format ]] - -import ( - "fmt" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/pkg/util/log" - "github.com/devstream-io/devstream/pkg/util/validator" -) - -// validate validates the options provided by the core. -func validate(options configmanager.RawOptions) (configmanager.RawOptions, error) { - opts, err := NewOptions(options) - if err != nil { - return nil, err - } - if errs := validator.CheckStructError(opts).Combine(); len(errs) != 0 { - return nil, errs.Combine() - } - return options, nil -} -` - -func init() { - TplFiles = append(TplFiles, TplFile{ - NameTpl: validateGoNameTpl, - DirTpl: validateGoDirTpl, - ContentTpl: validateGoContentTpl, - }) -} diff --git a/internal/pkg/develop/plugin/validate.go b/internal/pkg/develop/plugin/validate.go deleted file mode 100644 index f492eccfe..000000000 --- a/internal/pkg/develop/plugin/validate.go +++ /dev/null @@ -1,66 +0,0 @@ -package plugin - -import ( - "fmt" - - "github.com/spf13/viper" - - "github.com/devstream-io/devstream/cmd/devstream/list" - "github.com/devstream-io/devstream/pkg/util/log" -) - -func Validate() error { - validateAll := viper.GetBool("all") - - if validateAll { - log.Info("Start validating all plugins.\n") - return ValidatePlugins() - } - - name := viper.GetString("name") - if name == "" { - return fmt.Errorf(`the name must be not "", you can specify it by --name flag`) - } - log.Debugf("Got the name: %s.", name) - - return ValidatePlugin(name) -} - -// ValidatePlugins validate all plugins -// calling ValidatePlugin() via all plugins name -func ValidatePlugins() error { - listPluginsName := list.PluginsNameSlice() - - for _, pluginName := range listPluginsName { - log.Infof("===== start validating <%s> =====", pluginName) - if err := ValidatePlugin(pluginName); err != nil { - return err - } - log.Infof("===== Finished validate <%s> =====\n", pluginName) - } - - return nil -} - -// ValidatePlugin validate a plugin. -// 1. Render template files -// 2. Validate need validate files -func ValidatePlugin(pluginName string) error { - p := NewPlugin(pluginName) - - // 1. Render template files - files, err := p.RenderTplFiles() - if err != nil { - log.Debugf("Failed to render template files: %s.", err) - return err - } - log.Debug("Render template files finished.") - - // 2. Validate need validate files - if err := p.ValidateFiles(files); err != nil { - log.Debugf("Failed to validate files: %s.", err) - return err - } - - return nil -} diff --git a/internal/pkg/github/github.go b/internal/pkg/github/github.go new file mode 100644 index 000000000..956d3ea02 --- /dev/null +++ b/internal/pkg/github/github.go @@ -0,0 +1,7 @@ +package github + +import "fmt" + +func Run() { + fmt.Println("github called") +} diff --git a/internal/pkg/patch/patch.go b/internal/pkg/patch/patch.go new file mode 100644 index 000000000..2823d13aa --- /dev/null +++ b/internal/pkg/patch/patch.go @@ -0,0 +1,197 @@ +package patch + +import ( + "bufio" + "fmt" + "os" + "os/exec" + "regexp" + "strings" + + "github.com/devstream-io/devstream/internal/log" +) + +const ( + processOptionTabToSpace ProcessOption = "tabToSpace" + processOptionSpaceToTab ProcessOption = "spaceToTab" +) + +type ProcessOption string + +// Patch calls the patch command to apply a diff file to an original +func Patch(patchFile string) error { + log.Infof("Patching file: %s", patchFile) + + // Fix patch file if it mixed tab and space indentation + err := fixPatchFile(patchFile) + if err != nil { + return fmt.Errorf("patch file fix failed: %w", err) + } + + // Check if the patch command exists + patchPath, err := exec.LookPath("patch") + if err != nil { + return fmt.Errorf("patch command not found: %w", err) + } + + // Use the patch tool to apply the patch + cmd := exec.Command(patchPath, "-i", patchFile, "-t", "-p0") + output, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("patch command failed: %w\nOutput: %s", err, string(output)) + } + + log.Infof("Successfully patched the file") + return nil +} + +// checkPatchCommand checks if the patch command exists and is executable +func checkPatchCommand() error { + // Check if the patch command exists + path, err := exec.LookPath("patch") + if err != nil { + return fmt.Errorf("patch command not found: %w", err) + } + + // Check if the patch command is executable + fileInfo, err := os.Stat(path) + if err != nil { + return fmt.Errorf("failed to stat patch command: %w", err) + } + + if fileInfo.Mode()&0111 == 0 { + return fmt.Errorf("patch command is not executable") + } + + return nil +} + +// fixPatchFile fixes the patch file if it mixed tab and space indentation. +// The patch file is generated by GPT4, and it may have different indentation with the original file. +// The original file path is contained in the patch file, so we can use the fix the patch file by using the original file. +// If the original file uses tab indentation, we replace all spaces with tabs in the patch file. +// If the original file uses space indentation, we replace all tabs with spaces in the patch file. +func fixPatchFile(patchFile string) error { + // Read the original file path from the patch file + originalFilePath, err := extractOriginalFilePathFromPatchFile(patchFile) + + if err != nil { + return fmt.Errorf("failed to extract original file path from patch string: %w", err) + } + + // Check if the original file contain tabs in the indentation + original, err := os.Open(originalFilePath) + if err != nil { + return fmt.Errorf("failed to open original file: %w", err) + } + defer original.Close() + + hasTab := false + scanner := bufio.NewScanner(original) + for scanner.Scan() { + line := scanner.Text() + if strings.HasPrefix(line, "\t") { + hasTab = true + break + } + } + + if err = scanner.Err(); err != nil { + return fmt.Errorf("failed to read original file: %w", err) + } + + // The original file uses tab indentation + if hasTab { + // Replace all space indentation with tabs in the patch file + if err = processTabSpaceSwitch(patchFile, processOptionSpaceToTab); err != nil { + return fmt.Errorf("failed to process tab to space: %w", err) + } + // The original file uses space indentation + } else { + // Replace all tab indentation with spaces in the patch file + if err = processTabSpaceSwitch(patchFile, processOptionTabToSpace); err != nil { + return fmt.Errorf("failed to process space to tab: %w", err) + } + } + + return nil +} + +// ExtractOriginalFilePathFromPatchString extracts the original file path from a patch string +// e.g. --- pkg/patch/patch.go 2021-08-15 16:00:00.000000000 +0900 -> pkg/patch/patch.go +func extractOriginalFilePathFromPatchFile(patchFile string) (string, error) { + // Read content from the patch file + fileContent, err := os.ReadFile(patchFile) + if err != nil { + return "", fmt.Errorf("failed to read patch file: %w", err) + } + + lines := strings.Split(string(fileContent), "\n") + + for _, line := range lines { + if strings.HasPrefix(line, "--- ") { + fields := strings.Fields(line) + if len(fields) > 1 { + return fields[1], nil + } + } + } + + return "", fmt.Errorf("original file path not found in patch string") +} + +// processTabSpaceSwitch processes the tab/space indentation switch in a file +// If the option is processOptionTabToSpace, it replaces all tabs with spaces +// If the option is processOptionSpaceToTab, it replaces all spaces with tabs +func processTabSpaceSwitch(filePath string, option ProcessOption) error { + file, err := os.Open(filePath) + if err != nil { + return fmt.Errorf("failed to open file: %w", err) + } + defer file.Close() + + scanner := bufio.NewScanner(file) + var processedLines []string + + // Matches the start of the string (^) followed by an optional + or - sign, followed by one or more groups of 4 spaces ( {4})+ + spaceRegex := regexp.MustCompile(`^(\+|\-)?( {4})+`) + // Matches the start of the string (^) followed by an optional + or - sign, followed by one or more tabs (\t)+ + tabRegex := regexp.MustCompile(`^(\+|\-)?\t+`) + + for scanner.Scan() { + line := scanner.Text() + if option == processOptionTabToSpace { + line = tabRegex.ReplaceAllStringFunc(line, func(s string) string { + prefix := "" + if s[0] == '+' || s[0] == '-' { + prefix = string(s[0]) + s = s[1:] + } + return prefix + strings.Repeat(" ", len(s)) + }) + } else if option == processOptionSpaceToTab { + line = spaceRegex.ReplaceAllStringFunc(line, func(s string) string { + prefix := "" + if s[0] == '+' || s[0] == '-' { + prefix = string(s[0]) + s = s[1:] + } + return prefix + strings.Repeat("\t", len(s)/4) + }) + } else { + return fmt.Errorf("invalid process option: %s", option) + } + processedLines = append(processedLines, line) + } + + if err = scanner.Err(); err != nil { + return fmt.Errorf("failed to read file: %w", err) + } + + err = os.WriteFile(filePath, []byte(strings.Join(processedLines, "\n")+"\n"), 0644) + if err != nil { + return fmt.Errorf("failed to write file: %w", err) + } + + return nil +} diff --git a/internal/pkg/patch/patch_test.go b/internal/pkg/patch/patch_test.go new file mode 100644 index 000000000..bd581972b --- /dev/null +++ b/internal/pkg/patch/patch_test.go @@ -0,0 +1,165 @@ +package patch_test + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + . "github.com/devstream-io/devstream/internal/pkg/patch" +) + +func TestPatcher(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Patcher Suite") +} + +var _ = Describe("Patcher", func() { + var ( + originalFile *os.File + patchFile *os.File + tempDir string + ) + + BeforeEach(func() { + // 1. Create a temporary directory + var err error + tempDir, err = os.MkdirTemp("", "patcher-tests") + Expect(err).NotTo(HaveOccurred()) + + // 2. Change the working directory to the temporary directory + err = os.Chdir(tempDir) + Expect(err).NotTo(HaveOccurred()) + + // 3. Create the original file and the patch file + originalFile, err = os.CreateTemp(tempDir, "original-*") + Expect(err).NotTo(HaveOccurred()) + + patchFile, err = os.CreateTemp(tempDir, "patch-*") + Expect(err).NotTo(HaveOccurred()) + }) + + AfterEach(func() { + err := os.RemoveAll(tempDir) + Expect(err).NotTo(HaveOccurred()) + }) + + Context("when patching a file", func() { + It("successfully applies the patch to the original file", func() { + originalContent := `Hello, world! +This is the original file. +` + + err := os.WriteFile(originalFile.Name(), []byte(originalContent), 0644) + Expect(err).NotTo(HaveOccurred()) + + patchContent := fmt.Sprintf(`--- %s ++++ new-file +@@ -1,2 +1,2 @@ + Hello, world! +-This is the original file. ++This is the patched file. +`, + filepath.Base(originalFile.Name())) + + err = os.WriteFile(patchFile.Name(), []byte(patchContent), 0644) + Expect(err).NotTo(HaveOccurred()) + + err = Patch(patchFile.Name()) + Expect(err).NotTo(HaveOccurred()) + + patchedContent, err := os.ReadFile(originalFile.Name()) + Expect(err).NotTo(HaveOccurred()) + + expectedPatchedContent := `Hello, world! +This is the patched file. +` + patchedContentStr := string(patchedContent) + Expect(patchedContentStr).To(Equal(expectedPatchedContent)) + }) + + It("returns an error if the patch file is invalid", func() { + originalContent := `Hello, world! +This is the original file. +` + + err := os.WriteFile(originalFile.Name(), []byte(originalContent), 0644) + Expect(err).NotTo(HaveOccurred()) + + invalidPatchContent := fmt.Sprintf(`--- %s ++++ new-file +@@ -1,2 +1,2 @@ +`, + filepath.Base(originalFile.Name())) + + err = os.WriteFile(patchFile.Name(), []byte(invalidPatchContent), 0644) + Expect(err).NotTo(HaveOccurred()) + + err = Patch(patchFile.Name()) + Expect(err).To(HaveOccurred()) + Expect(strings.Contains(err.Error(), "patch command failed")).To(BeTrue()) + }) + }) + + Context("when patching a file with inconsistent indentation", func() { + It("successfully applies the patch with spaces to the original file with tabs", func() { + originalContent := "Hello, world!\n\tThis is the original file with tabs.\n" + + err := os.WriteFile(originalFile.Name(), []byte(originalContent), 0644) + Expect(err).NotTo(HaveOccurred()) + + patchContent := fmt.Sprintf(`--- %s ++++ new-file +@@ -1,2 +1,2 @@ + Hello, world! +- This is the original file with tabs. ++ This is the patched file with tabs. +`, + filepath.Base(originalFile.Name())) + + err = os.WriteFile(patchFile.Name(), []byte(patchContent), 0644) + Expect(err).NotTo(HaveOccurred()) + + err = Patch(patchFile.Name()) + Expect(err).NotTo(HaveOccurred()) + + patchedContent, err := os.ReadFile(originalFile.Name()) + Expect(err).NotTo(HaveOccurred()) + + expectedPatchedContent := "Hello, world!\n\tThis is the patched file with tabs.\n" + Expect(string(patchedContent)).To(Equal(expectedPatchedContent)) + }) + + It("successfully applies the patch with tabs to the original file with spaces", func() { + originalContent := "Hello, world!\n This is the original file with spaces.\n" + + err := os.WriteFile(originalFile.Name(), []byte(originalContent), 0644) + Expect(err).NotTo(HaveOccurred()) + + patchContent := fmt.Sprintf(`--- %s ++++ new-file +@@ -1,2 +1,2 @@ + Hello, world! +- This is the original file with spaces. ++ This is the patched file with spaces. +`, + filepath.Base(originalFile.Name())) + + err = os.WriteFile(patchFile.Name(), []byte(patchContent), 0644) + Expect(err).NotTo(HaveOccurred()) + + err = Patch(patchFile.Name()) + Expect(err).NotTo(HaveOccurred()) + + patchedContent, err := os.ReadFile(originalFile.Name()) + Expect(err).NotTo(HaveOccurred()) + + expectedPatchedContent := "Hello, world!\n This is the patched file with spaces.\n" + Expect(string(patchedContent)).To(Equal(expectedPatchedContent)) + }) + }) +}) diff --git a/internal/pkg/plugin/argocdapp/argocdapp.go b/internal/pkg/plugin/argocdapp/argocdapp.go deleted file mode 100644 index dc7b9b687..000000000 --- a/internal/pkg/plugin/argocdapp/argocdapp.go +++ /dev/null @@ -1,58 +0,0 @@ -package argocdapp - -import ( - _ "embed" - "fmt" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/pkg/util/downloader" - "github.com/devstream-io/devstream/pkg/util/scm" - "github.com/devstream-io/devstream/pkg/util/scm/git" -) - -//go:embed tpl/helm.tpl.yaml -var helmApplicationConfig string - -func pushArgocdConfigFiles(rawOptions configmanager.RawOptions) error { - const createCommitMsg = "create argocdapp config files" - const commitBranch = "feat-argocdapp-configs" - opts, err := newOptions(rawOptions) - if err != nil { - return err - } - - // 1. init scm client for check argocdapp config exists and push argocdapp files - repoInfo := &git.RepoInfo{ - CloneURL: git.ScmURL(opts.Source.RepoURL), - Branch: opts.Source.RepoBranch, - Token: opts.Source.Token, - } - scmClient, err := scm.NewClientWithAuth(repoInfo) - if err != nil { - return err - } - - // 2. check argocdapp config existed in project repo - configFileExist, err := opts.Source.checkPathExist(scmClient) - if err != nil { - return fmt.Errorf("argocdapp scm client get config path info failed: %w", err) - } - if configFileExist { - return nil - } - - // 3. get argocd configFiles from remote - const configLocation = downloader.ResourceLocation("https://github.com/devstream-io/dtm-pipeline-templates.git//argocdapp/helm") - gitFiles, err := opts.getArgocdDefaultConfigFiles(configLocation) - if err != nil { - return err - } - - // 4. push git files to ProjectRepo - _, err = scmClient.PushFiles(&git.CommitInfo{ - CommitMsg: createCommitMsg, - GitFileMap: gitFiles, - CommitBranch: commitBranch, - }, true) - return err -} diff --git a/internal/pkg/plugin/argocdapp/argocdapp_suite_test.go b/internal/pkg/plugin/argocdapp/argocdapp_suite_test.go deleted file mode 100644 index 974ce657a..000000000 --- a/internal/pkg/plugin/argocdapp/argocdapp_suite_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package argocdapp_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestCommon(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Plugin Argocdapp Suite") -} diff --git a/internal/pkg/plugin/argocdapp/create.go b/internal/pkg/plugin/argocdapp/create.go deleted file mode 100644 index d3be2f1b5..000000000 --- a/internal/pkg/plugin/argocdapp/create.go +++ /dev/null @@ -1,34 +0,0 @@ -package argocdapp - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/kubectl" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - kubectlUtil "github.com/devstream-io/devstream/pkg/util/kubectl" - "github.com/devstream-io/devstream/pkg/util/log" -) - -// Create creates an ArgoCD app YAML and applies it. -func Create(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - // Initialize Operator with Operations - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - setDefault, - validate, - }, - ExecuteOperations: installer.ExecuteOperations{ - pushArgocdConfigFiles, - kubectl.ProcessByContent(kubectlUtil.Create, helmApplicationConfig), - }, - GetStatusOperation: getStaticStatus, - } - - // Execute all Operations in Operator - status, err := operator.Execute(options) - if err != nil { - return nil, err - } - log.Debugf("Return map: %v", status) - return status, nil -} diff --git a/internal/pkg/plugin/argocdapp/delete.go b/internal/pkg/plugin/argocdapp/delete.go deleted file mode 100644 index 9fca8c148..000000000 --- a/internal/pkg/plugin/argocdapp/delete.go +++ /dev/null @@ -1,28 +0,0 @@ -package argocdapp - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/kubectl" - kubectlUtil "github.com/devstream-io/devstream/pkg/util/kubectl" -) - -func Delete(options configmanager.RawOptions) (bool, error) { - // Initialize Operator with Operations - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - setDefault, - validate, - }, - ExecuteOperations: installer.ExecuteOperations{ - kubectl.ProcessByContent(kubectlUtil.Delete, helmApplicationConfig), - }, - } - - // Execute all Operations in Operator - _, err := operator.Execute(options) - if err != nil { - return false, err - } - return true, nil -} diff --git a/internal/pkg/plugin/argocdapp/options.go b/internal/pkg/plugin/argocdapp/options.go deleted file mode 100644 index 208558875..000000000 --- a/internal/pkg/plugin/argocdapp/options.go +++ /dev/null @@ -1,100 +0,0 @@ -package argocdapp - -import ( - "fmt" - "path/filepath" - - "github.com/mitchellh/mapstructure" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/pkg/util/downloader" - "github.com/devstream-io/devstream/pkg/util/file" - "github.com/devstream-io/devstream/pkg/util/log" - "github.com/devstream-io/devstream/pkg/util/scm" - "github.com/devstream-io/devstream/pkg/util/scm/git" - "github.com/devstream-io/devstream/pkg/util/template" -) - -// options is the struct for parameters used by the argocdapp package. -type options struct { - App *app `mapstructure:"app" validate:"required"` - Destination *destination `mapstructure:"destination"` - Source *source `mapstructure:"source" validate:"required"` - ImageRepo *imageRepo `mapstructure:"imageRepo"` -} - -type imageRepo struct { - URL string `mapstructure:"url"` - User string `mapstructure:"user" validate:"required"` - InitalTag string `mapstructure:"initalTag"` -} - -// app is the struct for an ArgoCD app. -type app struct { - Name string `mapstructure:"name" validate:"dns1123subdomain"` - Namespace string `mapstructure:"namespace"` -} - -// destination is the struct for the destination of an ArgoCD app. -type destination struct { - Server string `mapstructure:"server"` - Namespace string `mapstructure:"namespace"` -} - -// source is the struct for the source of an ArgoCD app. -type source struct { - Valuefile string `mapstructure:"valuefile"` - Path string `mapstructure:"path" validate:"required"` - RepoURL string `mapstructure:"repoURL" validate:"required"` - RepoBranch string `mapstructure:"repoBranch"` - Token string `mapstructure:"token"` -} - -// / newOptions create options by raw options -func newOptions(rawOptions configmanager.RawOptions) (options, error) { - var opts options - if err := mapstructure.Decode(rawOptions, &opts); err != nil { - return opts, err - } - return opts, nil -} - -// checkPathExist will check argocdapp config already exist -func (s *source) checkPathExist(scmClient scm.ClientOperation) (bool, error) { - existFiles, err := scmClient.GetPathInfo(s.Path) - if err != nil { - return false, err - } - if len(existFiles) > 0 { - log.Debugf("argocdapp check config path %s already exist, pass...", s.Path) - return true, nil - } - return false, nil -} - -func (o *options) getArgocdDefaultConfigFiles(configLocation downloader.ResourceLocation) (git.GitFileContentMap, error) { - // 1. check imageRepo is configured - if o.ImageRepo == nil { - return nil, fmt.Errorf("argocdapp create config need config imageRepo options") - } - // 2. get configs from remote url - configFiles, err := configLocation.Download() - if err != nil { - return nil, err - } - // 3. get file content - fContentFunc := func(filePath string) ([]byte, error) { - renderContent, err := template.NewRenderClient(&template.TemplateOption{ - Name: "argocd", - }, template.LocalFileGetter).Render(filePath, o) - if err != nil { - return nil, err - } - return []byte(renderContent), nil - } - fNameFunc := func(filePath, srcPath string) string { - relativePath, _ := filepath.Rel(srcPath, filePath) - return filepath.Join(o.Source.Path, relativePath) - } - return file.GetFileMap(configFiles, file.DirFileFilterDefaultFunc, fNameFunc, fContentFunc) -} diff --git a/internal/pkg/plugin/argocdapp/options_test.go b/internal/pkg/plugin/argocdapp/options_test.go deleted file mode 100644 index 77035c4bf..000000000 --- a/internal/pkg/plugin/argocdapp/options_test.go +++ /dev/null @@ -1,140 +0,0 @@ -package argocdapp - -import ( - "errors" - "fmt" - "net/http" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "github.com/onsi/gomega/ghttp" - - "github.com/devstream-io/devstream/pkg/util/downloader" - "github.com/devstream-io/devstream/pkg/util/scm" - "github.com/devstream-io/devstream/pkg/util/scm/git" -) - -var _ = Describe("source struct", func() { - var ( - scmClient scm.ClientOperation - errMsg string - s *source - ) - BeforeEach(func() { - s = &source{ - Path: "test_path", - } - }) - Context("checkPathExist method", func() { - When("scm client error", func() { - BeforeEach(func() { - errMsg = "scm get path error" - scmClient = &scm.MockScmClient{ - GetPathInfoError: errors.New(errMsg), - } - }) - It("should return error", func() { - _, err := s.checkPathExist(scmClient) - Expect(err).Should(HaveOccurred()) - Expect(err.Error()).Should(Equal(errMsg)) - }) - }) - When("path list is empty", func() { - BeforeEach(func() { - scmClient = &scm.MockScmClient{ - GetPathInfoReturnValue: []*git.RepoFileStatus{}, - } - }) - It("should return false", func() { - exist, err := s.checkPathExist(scmClient) - Expect(err).ShouldNot(HaveOccurred()) - Expect(exist).Should(BeFalse()) - }) - }) - When("path list is not empty", func() { - BeforeEach(func() { - scmClient = &scm.MockScmClient{ - GetPathInfoReturnValue: []*git.RepoFileStatus{ - {Path: "gg"}, - }, - } - }) - It("should return true", func() { - exist, err := s.checkPathExist(scmClient) - Expect(err).ShouldNot(HaveOccurred()) - Expect(exist).Should(BeTrue()) - }) - }) - }) -}) - -var _ = Describe("options struct", func() { - var ( - configLocation downloader.ResourceLocation - o *options - s *ghttp.Server - pathName, reqPath string - ) - BeforeEach(func() { - s = ghttp.NewServer() - o = &options{ - App: &app{ - Name: "test", - }, - Source: &source{ - Path: "test_path", - RepoURL: "https://test.com/owner/repo", - }, - ImageRepo: &imageRepo{ - URL: "test.com", - User: "user", - InitalTag: "latest", - }, - } - pathName = "/configLoc" - reqPath = fmt.Sprintf("%s%s", s.URL(), pathName) - s.RouteToHandler("HEAD", pathName, func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, "ok") - }) - }) - Context("getArgocdDefaultConfigFiles method", func() { - When("can't get location", func() { - BeforeEach(func() { - s.RouteToHandler("GET", pathName, ghttp.CombineHandlers( - ghttp.VerifyRequest("GET", pathName), - ghttp.RespondWith(http.StatusNotFound, ""), - )) - configLocation = downloader.ResourceLocation(reqPath) - }) - It("should return error", func() { - _, err := o.getArgocdDefaultConfigFiles(configLocation) - Expect(err).Should(HaveOccurred()) - Expect(err.Error()).Should(ContainSubstring("bad response code: 404")) - }) - }) - When("all valid", func() { - BeforeEach(func() { - configFileContent := ` -image: - repository: [[ .ImageRepo.URL ]]/[[ .ImageRepo.User ]] - tag: [[ .ImageRepo.InitalTag ]] - pullPolicy: Always - -` - s.RouteToHandler("GET", pathName, ghttp.CombineHandlers( - ghttp.VerifyRequest("GET", pathName), - ghttp.RespondWith(http.StatusOK, configFileContent), - )) - configLocation = downloader.ResourceLocation(reqPath) - }) - It("should return valid data", func() { - data, err := o.getArgocdDefaultConfigFiles(configLocation) - Expect(err).ShouldNot(HaveOccurred()) - Expect(data).ShouldNot(BeEmpty()) - }) - }) - }) - AfterEach(func() { - s.Close() - }) -}) diff --git a/internal/pkg/plugin/argocdapp/read.go b/internal/pkg/plugin/argocdapp/read.go deleted file mode 100644 index 5c54002fd..000000000 --- a/internal/pkg/plugin/argocdapp/read.go +++ /dev/null @@ -1,25 +0,0 @@ -package argocdapp - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - "github.com/devstream-io/devstream/internal/pkg/statemanager" -) - -func Read(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - // Initialize Operator with Operations - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - setDefault, - validate, - }, - GetStatusOperation: getDynamicStatus, - } - - // Execute all Operations in Operator - status, err := operator.Execute(options) - if err != nil { - return nil, err - } - return status, nil -} diff --git a/internal/pkg/plugin/argocdapp/state.go b/internal/pkg/plugin/argocdapp/state.go deleted file mode 100644 index db6dbbcef..000000000 --- a/internal/pkg/plugin/argocdapp/state.go +++ /dev/null @@ -1,79 +0,0 @@ -package argocdapp - -import ( - "time" - - "github.com/cenkalti/backoff" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/k8s" -) - -func getStaticStatus(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - opts, err := newOptions(options) - if err != nil { - return nil, err - } - resStatus := make(statemanager.ResourceStatus) - - resStatus["app"] = map[string]interface{}{ - "name": opts.App.Name, - "namespace": opts.App.Namespace, - } - - resStatus["src"] = map[string]interface{}{ - "repoURL": opts.Source.RepoURL, - "path": opts.Source.Path, - "valueFile": opts.Source.Valuefile, - } - - resStatus["dest"] = map[string]interface{}{ - "server": opts.Destination.Server, - "namespace": opts.Destination.Namespace, - } - - return resStatus, nil -} - -func getDynamicStatus(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - opts, err := newOptions(options) - if err != nil { - return nil, err - } - - retStatus := make(statemanager.ResourceStatus) - operation := func() error { - err := getArgoCDAppFromK8sAndSetStatus(retStatus, opts.App.Name, opts.App.Namespace) - if err != nil { - return err - } - return nil - } - bkoff := backoff.NewExponentialBackOff() - bkoff.MaxElapsedTime = 3 * time.Minute - err = backoff.Retry(operation, bkoff) - if err != nil { - return nil, err - } - return retStatus, nil -} - -func getArgoCDAppFromK8sAndSetStatus(status statemanager.ResourceStatus, name, namespace string) error { - kubeClient, err := k8s.NewClient() - if err != nil { - return err - } - - app, err := kubeClient.GetArgocdApplication(namespace, name) - if err != nil { - return err - } - - d := kubeClient.DescribeArgocdApp(app) - status["app"] = d["app"] - status["src"] = d["src"] - status["dest"] = d["dest"] - - return nil -} diff --git a/internal/pkg/plugin/argocdapp/state_test.go b/internal/pkg/plugin/argocdapp/state_test.go deleted file mode 100644 index b4d29edae..000000000 --- a/internal/pkg/plugin/argocdapp/state_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package argocdapp - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/statemanager" -) - -var _ = Describe("getStaticStatus func", func() { - var option configmanager.RawOptions - BeforeEach(func() { - option = configmanager.RawOptions{ - "app": map[string]any{ - "name": "test", - "namespace": "test_namespace", - }, - "source": map[string]any{ - "repoURL": "test.com", - "path": "test_path", - "valueFile": "val_file", - }, - "destination": map[string]any{ - "server": "test_server", - "namespace": "dst_namespace", - }, - } - }) - It("should return status", func() { - state, err := getStaticStatus(option) - Expect(err).ShouldNot(HaveOccurred()) - Expect(state).Should(Equal(statemanager.ResourceStatus{ - "app": map[string]any{ - "namespace": "test_namespace", - "name": "test", - }, - "src": map[string]any{ - "repoURL": "test.com", - "path": "test_path", - "valueFile": "val_file", - }, - "dest": map[string]any{ - "server": "test_server", - "namespace": "dst_namespace", - }, - })) - }) -}) diff --git a/internal/pkg/plugin/argocdapp/tpl/helm.tpl.yaml b/internal/pkg/plugin/argocdapp/tpl/helm.tpl.yaml deleted file mode 100644 index 046b697f3..000000000 --- a/internal/pkg/plugin/argocdapp/tpl/helm.tpl.yaml +++ /dev/null @@ -1,23 +0,0 @@ -apiVersion: argoproj.io/v1alpha1 -kind: Application -metadata: - name: "[[ .app.name ]]" - namespace: "[[ .app.namespace ]]" - finalizers: - - resources-finalizer.argocd.argoproj.io -spec: - destination: - namespace: "[[ .destination.namespace ]]" - server: "[[ .destination.server ]]" - project: default - source: - helm: - valueFiles: - - "[[ .source.valuefile ]]" - path: "[[ .source.path ]]" - repoURL: "[[ .source.repoURL ]]" - targetRevision: HEAD - syncPolicy: - automated: - prune: true - selfHeal: true diff --git a/internal/pkg/plugin/argocdapp/update.go b/internal/pkg/plugin/argocdapp/update.go deleted file mode 100644 index 51afc0031..000000000 --- a/internal/pkg/plugin/argocdapp/update.go +++ /dev/null @@ -1,10 +0,0 @@ -package argocdapp - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/statemanager" -) - -func Update(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return Create(options) -} diff --git a/internal/pkg/plugin/argocdapp/validate.go b/internal/pkg/plugin/argocdapp/validate.go deleted file mode 100644 index 1d5cdf888..000000000 --- a/internal/pkg/plugin/argocdapp/validate.go +++ /dev/null @@ -1,42 +0,0 @@ -package argocdapp - -import ( - "fmt" - "strings" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/pkg/util/mapz" - "github.com/devstream-io/devstream/pkg/util/validator" -) - -// validate validates the options provided by the core. -func validate(options configmanager.RawOptions) (configmanager.RawOptions, error) { - opts, err := newOptions(options) - if err != nil { - return nil, err - } - if err := validator.CheckStructError(opts).Combine(); err != nil { - return nil, err - } - return options, nil -} - -func setDefault(options configmanager.RawOptions) (configmanager.RawOptions, error) { - const defaultImageTag = "0.0.1" - opts, err := newOptions(options) - if err != nil { - return nil, err - } - // imageRepo initalTag use latest by default - if opts.ImageRepo != nil { - if opts.ImageRepo.InitalTag == "" { - opts.ImageRepo.InitalTag = defaultImageTag - } - if opts.ImageRepo.URL != "" && strings.HasSuffix(opts.ImageRepo.URL, "") { - opts.ImageRepo.URL = fmt.Sprintf("%s/", opts.ImageRepo.URL) - } - } - - // set ci file config - return mapz.DecodeStructToMap(opts) -} diff --git a/internal/pkg/plugin/argocdapp/validate_test.go b/internal/pkg/plugin/argocdapp/validate_test.go deleted file mode 100644 index ff12bd017..000000000 --- a/internal/pkg/plugin/argocdapp/validate_test.go +++ /dev/null @@ -1,38 +0,0 @@ -package argocdapp - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" -) - -var _ = Describe("setDefault func", func() { - var option configmanager.RawOptions - - BeforeEach(func() { - option = configmanager.RawOptions{ - "imageRepo": map[string]any{ - "user": "test_user", - "url": "http://test.com", - }, - } - }) - It("should return with default value", func() { - var appOption *app - var sourceOption *source - var destinationOption *destination - opt, err := setDefault(option) - Expect(err).ShouldNot(HaveOccurred()) - Expect(opt).Should(Equal(configmanager.RawOptions{ - "app": appOption, - "destination": destinationOption, - "source": sourceOption, - "imageRepo": map[string]any{ - "url": "http://test.com/", - "user": "test_user", - "initalTag": "0.0.1", - }, - })) - }) -}) diff --git a/internal/pkg/plugin/cigeneric/cigeneric.go b/internal/pkg/plugin/cigeneric/cigeneric.go deleted file mode 100644 index 113f2c05f..000000000 --- a/internal/pkg/plugin/cigeneric/cigeneric.go +++ /dev/null @@ -1 +0,0 @@ -package cigeneric diff --git a/internal/pkg/plugin/cigeneric/create.go b/internal/pkg/plugin/cigeneric/create.go deleted file mode 100644 index 6bd179bc2..000000000 --- a/internal/pkg/plugin/cigeneric/create.go +++ /dev/null @@ -1,30 +0,0 @@ -package cigeneric - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/cifile" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" -) - -func Create(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - // Initialize Operator with Operations - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - cifile.Validate, - }, - ExecuteOperations: installer.ExecuteOperations{ - cifile.PushCIFiles, - }, - GetStatusOperation: cifile.GetCIFileStatus, - } - - // Execute all Operations in Operator - status, err := operator.Execute(options) - if err != nil { - return nil, err - } - log.Debugf("Return map: %v", status) - return status, nil -} diff --git a/internal/pkg/plugin/cigeneric/delete.go b/internal/pkg/plugin/cigeneric/delete.go deleted file mode 100644 index cc3931616..000000000 --- a/internal/pkg/plugin/cigeneric/delete.go +++ /dev/null @@ -1,26 +0,0 @@ -package cigeneric - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/cifile" -) - -func Delete(options configmanager.RawOptions) (bool, error) { - // Initialize Operator with Operations - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - cifile.Validate, - }, - ExecuteOperations: installer.ExecuteOperations{ - cifile.DeleteCIFiles, - }, - } - - // Execute all Operations in Operator - _, err := operator.Execute(options) - if err != nil { - return false, err - } - return true, nil -} diff --git a/internal/pkg/plugin/cigeneric/read.go b/internal/pkg/plugin/cigeneric/read.go deleted file mode 100644 index 466a5b74d..000000000 --- a/internal/pkg/plugin/cigeneric/read.go +++ /dev/null @@ -1,27 +0,0 @@ -package cigeneric - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/cifile" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" -) - -func Read(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - cifile.Validate, - }, - GetStatusOperation: cifile.GetCIFileStatus, - } - - status, err := operator.Execute(options) - if err != nil { - return nil, err - } - - log.Debugf("Return map: %v", status) - return status, nil - -} diff --git a/internal/pkg/plugin/cigeneric/update.go b/internal/pkg/plugin/cigeneric/update.go deleted file mode 100644 index defa4c13a..000000000 --- a/internal/pkg/plugin/cigeneric/update.go +++ /dev/null @@ -1,10 +0,0 @@ -package cigeneric - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/statemanager" -) - -func Update(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return Create(options) -} diff --git a/internal/pkg/plugin/devlakeconfig/create.go b/internal/pkg/plugin/devlakeconfig/create.go deleted file mode 100644 index d45482ccd..000000000 --- a/internal/pkg/plugin/devlakeconfig/create.go +++ /dev/null @@ -1,33 +0,0 @@ -package devlakeconfig - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" -) - -func Create(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - // Initialize Operator with Operations - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - validate, - RenderAuthConfig, - }, - ExecuteOperations: installer.ExecuteOperations{ - ApplyConfig, - }, - TerminateOperations: installer.TerminateOperations{ - // TODO(dtm): Add your TerminateOperations here. - }, - GetStatusOperation: GetStatus, - } - - // Execute all Operations in Operator - status, err := operator.Execute(options) - if err != nil { - return nil, err - } - log.Debugf("Return map: %v", status) - return status, nil -} diff --git a/internal/pkg/plugin/devlakeconfig/delete.go b/internal/pkg/plugin/devlakeconfig/delete.go deleted file mode 100644 index c75ae5267..000000000 --- a/internal/pkg/plugin/devlakeconfig/delete.go +++ /dev/null @@ -1,27 +0,0 @@ -package devlakeconfig - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" -) - -func Delete(options configmanager.RawOptions) (bool, error) { - // Initialize Operator with Operations - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - validate, - RenderAuthConfig, - }, - ExecuteOperations: installer.ExecuteOperations{ - DeleteConfig, - }, - } - - // Execute all Operations in Operator - _, err := operator.Execute(options) - if err != nil { - return false, err - } - - return true, nil -} diff --git a/internal/pkg/plugin/devlakeconfig/devlakeconfig.go b/internal/pkg/plugin/devlakeconfig/devlakeconfig.go deleted file mode 100644 index a8d1f638a..000000000 --- a/internal/pkg/plugin/devlakeconfig/devlakeconfig.go +++ /dev/null @@ -1,201 +0,0 @@ -package devlakeconfig - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "net/http" - "strings" - "time" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" -) - -var httpClient = &http.Client{ - Timeout: 5 * time.Second, -} - -func ApplyConfig(options configmanager.RawOptions) error { - opts, err := NewOptions(options) - if err != nil { - return err - } - - for _, p := range opts.Plugins { - log.Infof("Got DevLake plugin config: %s. Connections: ", p.Name) - if err := createConnections(opts.DevLakeAddr, p.Name, p.Connections); err != nil { - return err - } - } - - return nil -} - -func createConnections(host string, pluginName string, connections []Connection) error { - for i, c := range connections { - log.Infof("Connection %d: %s", i, c.Name) - if err := createConnection(host, pluginName, c); err != nil { - return err - } - } - log.Infof("All %s connections have been created.", pluginName) - return nil -} - -func createConnection(host string, pluginName string, c Connection) error { - configs, err := json.Marshal(c) - if err != nil { - return err - } - log.Debugf("Connection configs: %s", string(configs)) - url := fmt.Sprintf("%s/plugins/%s/connections", strings.TrimRight(host, "/"), pluginName) - log.Debugf("URL: %s", url) - if _, err = apiRequest(http.MethodPost, url, configs); err != nil { - return err - } - return nil -} - -// update existed connection in devlake backend -func updateConnection(host string, pluginName string, c Connection) error { - configs, err := json.Marshal(c) - if err != nil { - return err - } - log.Debugf("UPDATE Connection configs: %s", string(configs)) - url := fmt.Sprintf("%s/plugins/%s/connections/%d", strings.TrimRight(host, "/"), pluginName, c.ID) - log.Debugf("URL: %s", url) - if _, err = apiRequest(http.MethodPatch, url, configs); err != nil { - return err - } - return nil -} - -// delete existed connection in devlake backend -func deleteConnection(host string, pluginName string, c Connection) error { - configs, err := json.Marshal(c) - if err != nil { - return err - } - log.Debugf("DELETE Connection configs: %s", string(configs)) - url := fmt.Sprintf("%s/plugins/%s/connections/%d", strings.TrimRight(host, "/"), pluginName, c.ID) - log.Debugf("URL: %s", url) - if _, err = apiRequest(http.MethodDelete, url, configs); err != nil { - return err - } - return nil -} - -func getConnections(host string, pluginName string) ([]Connection, error) { - url := fmt.Sprintf("%s/plugins/%s/connections", strings.TrimRight(host, "/"), pluginName) - log.Debugf("URL: %s", url) - resp, err := apiRequest(http.MethodGet, url, nil) - if err != nil { - return nil, err - } - defer resp.Body.Close() - resBody, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - connections := make([]Connection, 0) - err = json.Unmarshal(resBody, &connections) - if err != nil { - return nil, err - } - return connections, nil -} - -func apiRequest(method string, url string, bodyWithJson []byte) (*http.Response, error) { - req, err := http.NewRequest(method, url, bytes.NewBuffer(bodyWithJson)) - if err != nil { - return nil, err - } - req.Header.Set("Content-Type", "application/json") - resp, err := httpClient.Do(req) - if err != nil { - return nil, err - } - if resp.StatusCode == http.StatusOK { - return resp, nil - } - return nil, fmt.Errorf(resp.Status) -} - -func DeleteConfig(options configmanager.RawOptions) error { - // TODO(daniel-hutao): implement later - return nil -} - -func UpdateConfig(options configmanager.RawOptions) error { - opts, err := NewOptions(options) - if err != nil { - return err - } - for _, p := range opts.Plugins { - if updatePluginConfig(opts.DevLakeAddr, p) != nil { - return err - } - } - return nil -} - -func updatePluginConfig(devlakeAddr string, plugin DevLakePlugin) error { - connections, err := getConnections(devlakeAddr, plugin.Name) - if err != nil { - return err - } - - // Connection.Name -> Connection for config - connMapForConfig := make(map[string]Connection) - // Connection.Name -> Connection for status - connMapForStatus := make(map[string]Connection) - for _, c := range plugin.Connections { - connMapForConfig[c.Name] = c - } - for _, c := range connections { - connMapForStatus[c.Name] = c - } - - return updatePluginConnections(connMapForConfig, connMapForStatus, devlakeAddr, plugin) -} - -func updatePluginConnections(connMapForConfig, connMapForStatus map[string]Connection, devlakeAddr string, plugin DevLakePlugin) error { - for name := range connMapForConfig { - // Create connection which is not in ResourceStatus - c, ok := connMapForStatus[name] - if ok { - if err := createConnection(devlakeAddr, plugin.Name, connMapForConfig[name]); err != nil { - return err - } - continue - } - - // Update connection which is different from State - if c != connMapForConfig[name] { - if err := updateConnection(devlakeAddr, plugin.Name, connMapForStatus[name]); err != nil { - return err - } - } - } - - // Delete connection which is not in Config - for name := range connMapForStatus { - if _, ok := connMapForConfig[name]; !ok { - if err := deleteConnection(devlakeAddr, plugin.Name, connMapForStatus[name]); err != nil { - return err - } - } - } - - return nil -} - -func GetStatus(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - // TODO(daniel-hutao): get the real status later - resStatus := statemanager.ResourceStatus(options) - return resStatus, nil -} diff --git a/internal/pkg/plugin/devlakeconfig/options.go b/internal/pkg/plugin/devlakeconfig/options.go deleted file mode 100644 index 7f078a979..000000000 --- a/internal/pkg/plugin/devlakeconfig/options.go +++ /dev/null @@ -1,100 +0,0 @@ -package devlakeconfig - -import ( - "github.com/mitchellh/mapstructure" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/devlakeconfig/staging" -) - -// Options is the struct for configurations of the devlake-config plugin. -type Options struct { - DevLakeAddr string `mapstructure:"devlakeAddr" validate:"url"` - Plugins []DevLakePlugin `mapstructure:"plugins" validate:"required"` -} - -// NewOptions create options by raw options -func NewOptions(options configmanager.RawOptions) (Options, error) { - var opts Options - if err := mapstructure.Decode(options, &opts); err != nil { - return opts, err - } - return opts, nil -} - -type DevLakePlugin struct { - Name string `mapstructure:"name" validate:"required"` - Connections []Connection `mapstructure:"connections"` -} - -// TODO(daniel-hutao): uncomment the code below after DevLake fix the umimportable issue: -// -//github.com/devstream-io/devstream/internal/pkg/plugin/devlakeconfig imports -// github.com/apache/incubator-devlake/plugins/helper tested by -// github.com/apache/incubator-devlake/plugins/helper.test imports -// github.com/apache/incubator-devlake/mocks: module github.com/apache/incubator-devlake@latest found (v0.14.0), but does not contain package github.com/apache/incubator-devlake/mocks -// -//type Connection struct { -// helper.RestConnection `mapstructure:",squash"` -// helper.BasicAuth `mapstructure:",squash"` -// helper.AccessToken `mapstructure:",squash"` -// helper.AppKey `mapstructure:",squash"` -//} - -type Connection struct { - staging.RestConnection `mapstructure:",squash"` - // a connection format in dtm config: - // - name: "" - // endpoint: "" - // proxy: "" - // rateLimitPerHour: 0 - // auth: - // username: "changeme" - // password: "changeme" - Auth Auth `mapstructure:"auth" validate:"required"` - // a connection format in DevLake api: - // { - // "name": "" - // "endpoint": "" - // "proxy": "" - // "rateLimitPerHour": 0 - // "username": "changeme" - // "password": "changeme" - // } - InlineAuth `mapstructure:",squash"` -} - -type Auth struct { - staging.BasicAuth `mapstructure:",squash"` - staging.AccessToken `mapstructure:",squash"` - staging.AppKey `mapstructure:",squash"` -} - -type InlineAuth Auth - -func (o *Options) Encode() (configmanager.RawOptions, error) { - var options configmanager.RawOptions - if err := mapstructure.Decode(o, &options); err != nil { - return nil, err - } - return options, nil -} - -func RenderAuthConfig(options configmanager.RawOptions) (configmanager.RawOptions, error) { - opts, err := NewOptions(options) - if err != nil { - return nil, err - } - - for _, p := range opts.Plugins { - for _, c := range p.Connections { - c.Token = c.Auth.Token - c.Username = c.Auth.Username - c.Password = c.Auth.Password - c.AppId = c.Auth.AppId - c.SecretKey = c.Auth.SecretKey - } - } - - return opts.Encode() -} diff --git a/internal/pkg/plugin/devlakeconfig/read.go b/internal/pkg/plugin/devlakeconfig/read.go deleted file mode 100644 index 12e2e0a20..000000000 --- a/internal/pkg/plugin/devlakeconfig/read.go +++ /dev/null @@ -1,26 +0,0 @@ -package devlakeconfig - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" -) - -func Read(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - // Initialize Operator with Operations - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - validate, - }, - GetStatusOperation: GetStatus, - } - - // Execute all Operations in Operator - status, err := operator.Execute(options) - if err != nil { - return nil, err - } - log.Debugf("Return map: %v", status) - return status, nil -} diff --git a/internal/pkg/plugin/devlakeconfig/staging/common/base.go b/internal/pkg/plugin/devlakeconfig/staging/common/base.go deleted file mode 100644 index 5efa2133d..000000000 --- a/internal/pkg/plugin/devlakeconfig/staging/common/base.go +++ /dev/null @@ -1,54 +0,0 @@ -/* -Licensed to the Apache Software Foundation (ASF) under one or more -contributor license agreements. See the NOTICE file distributed with -this work for additional information regarding copyright ownership. -The ASF licenses this file to You 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. -*/ - -package common - -import ( - "regexp" - "time" -) - -type Model struct { - ID uint64 `gorm:"primaryKey" json:"id"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` -} - -type NoPKModel struct { - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` - RawDataOrigin -} - -// embedded fields for tool layer tables -type RawDataOrigin struct { - // can be used for flushing outdated records from table - RawDataParams string `gorm:"column:_raw_data_params;type:varchar(255);index" json:"_raw_data_params"` - RawDataTable string `gorm:"column:_raw_data_table;type:varchar(255)" json:"_raw_data_table"` - // can be used for debugging - RawDataId uint64 `gorm:"column:_raw_data_id" json:"_raw_data_id"` - // we can store record index into this field, which is helpful for debugging - RawDataRemark string `gorm:"column:_raw_data_remark" json:"_raw_data_remark"` -} - -var ( - DUPLICATE_REGEX = regexp.MustCompile(`(?i)\bduplicate\b`) -) - -func IsDuplicateError(err error) bool { - return err != nil && DUPLICATE_REGEX.MatchString(err.Error()) -} diff --git a/internal/pkg/plugin/devlakeconfig/staging/connection.go b/internal/pkg/plugin/devlakeconfig/staging/connection.go deleted file mode 100644 index ccb5b155c..000000000 --- a/internal/pkg/plugin/devlakeconfig/staging/connection.go +++ /dev/null @@ -1,36 +0,0 @@ -package staging - -import ( - "github.com/devstream-io/devstream/internal/pkg/plugin/devlakeconfig/staging/common" -) - -// BaseConnection FIXME ... -type BaseConnection struct { - Name string `gorm:"type:varchar(100);uniqueIndex" json:"name" validate:"required"` - common.Model -} - -// BasicAuth FIXME ... -type BasicAuth struct { - Username string `mapstructure:"username" validate:"required" json:"username"` - Password string `mapstructure:"password" validate:"required" json:"password" encrypt:"yes"` -} - -// AccessToken FIXME ... -type AccessToken struct { - Token string `mapstructure:"token" validate:"required" json:"token" encrypt:"yes"` -} - -// AppKey FIXME ... -type AppKey struct { - AppId string `mapstructure:"app_id" validate:"required" json:"appId"` - SecretKey string `mapstructure:"secret_key" validate:"required" json:"secretKey" encrypt:"yes"` -} - -// RestConnection FIXME ... -type RestConnection struct { - BaseConnection `mapstructure:",squash"` - Endpoint string `mapstructure:"endpoint" validate:"required" json:"endpoint"` - Proxy string `mapstructure:"proxy" json:"proxy"` - RateLimitPerHour int `comment:"api request rate limit per hour" json:"rateLimitPerHour"` -} diff --git a/internal/pkg/plugin/devlakeconfig/update.go b/internal/pkg/plugin/devlakeconfig/update.go deleted file mode 100644 index 2e9853a9f..000000000 --- a/internal/pkg/plugin/devlakeconfig/update.go +++ /dev/null @@ -1,33 +0,0 @@ -package devlakeconfig - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" -) - -func Update(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - // Initialize Operator with Operations - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - validate, - RenderAuthConfig, - }, - ExecuteOperations: installer.ExecuteOperations{ - UpdateConfig, - }, - TerminateOperations: installer.TerminateOperations{ - // TODO(dtm): Add your TerminateOperations here. - }, - GetStatusOperation: GetStatus, - } - - // Execute all Operations in Operator - status, err := operator.Execute(options) - if err != nil { - return nil, err - } - log.Debugf("Return map: %v", status) - return status, nil -} diff --git a/internal/pkg/plugin/devlakeconfig/validate.go b/internal/pkg/plugin/devlakeconfig/validate.go deleted file mode 100644 index 57d8d5ff9..000000000 --- a/internal/pkg/plugin/devlakeconfig/validate.go +++ /dev/null @@ -1,18 +0,0 @@ -package devlakeconfig - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/pkg/util/validator" -) - -// validate validates the options provided by the core. -func validate(options configmanager.RawOptions) (configmanager.RawOptions, error) { - opts, err := NewOptions(options) - if err != nil { - return nil, err - } - if err := validator.CheckStructError(opts).Combine(); err != nil { - return nil, err - } - return options, nil -} diff --git a/internal/pkg/plugin/githubactions/create.go b/internal/pkg/plugin/githubactions/create.go deleted file mode 100644 index d03e62b44..000000000 --- a/internal/pkg/plugin/githubactions/create.go +++ /dev/null @@ -1,31 +0,0 @@ -package general - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/cifile" - "github.com/devstream-io/devstream/pkg/util/log" -) - -func Create(options map[string]interface{}) (map[string]interface{}, error) { - // Initialize Operator with Operations - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - setDefault, - validate, - }, - ExecuteOperations: installer.ExecuteOperations{ - preConfigGithub, - cifile.PushCIFiles, - }, - GetStatusOperation: cifile.GetCIFileStatus, - } - - // Execute all Operations in Operator - status, err := operator.Execute(configmanager.RawOptions(options)) - if err != nil { - return nil, err - } - log.Debugf("Return map: %v", status) - return status, nil -} diff --git a/internal/pkg/plugin/githubactions/delete.go b/internal/pkg/plugin/githubactions/delete.go deleted file mode 100644 index 44334a171..000000000 --- a/internal/pkg/plugin/githubactions/delete.go +++ /dev/null @@ -1,30 +0,0 @@ -package general - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/cifile" -) - -func Delete(options map[string]interface{}) (bool, error) { - // Initialize Operator with Operations - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - setDefault, - validate, - }, - ExecuteOperations: installer.ExecuteOperations{ - //TODO(jiafeng meng): delete github secret - cifile.DeleteCIFiles, - }, - GetStatusOperation: cifile.GetCIFileStatus, - } - - // Execute all Operations in Operator - _, err := operator.Execute(configmanager.RawOptions(options)) - if err != nil { - return false, err - } - - return true, nil -} diff --git a/internal/pkg/plugin/githubactions/githubactions.go b/internal/pkg/plugin/githubactions/githubactions.go deleted file mode 100644 index 85b8bb300..000000000 --- a/internal/pkg/plugin/githubactions/githubactions.go +++ /dev/null @@ -1,32 +0,0 @@ -package general - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/step" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/util" - "github.com/devstream-io/devstream/pkg/util/log" - "github.com/devstream-io/devstream/pkg/util/scm" -) - -func preConfigGithub(options configmanager.RawOptions) error { - opts := new(ci.CIConfig) - if err := util.DecodePlugin(options, opts); err != nil { - return err - } - - stepConfigs := step.ExtractValidStepConfig(opts.Pipeline) - githubClient, err := scm.NewClientWithAuth(opts.ProjectRepo) - if err != nil { - log.Debugf("init github client failed: %+v", err) - return err - } - for _, c := range stepConfigs { - err := c.ConfigSCM(githubClient) - if err != nil { - log.Debugf("githubaction config github failed: %+v", err) - return err - } - } - return nil -} diff --git a/internal/pkg/plugin/githubactions/githubactions_suite_test.go b/internal/pkg/plugin/githubactions/githubactions_suite_test.go deleted file mode 100644 index 757aff347..000000000 --- a/internal/pkg/plugin/githubactions/githubactions_suite_test.go +++ /dev/null @@ -1,36 +0,0 @@ -package general_test - -import ( - "os" - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestGitlabcedocker(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Plugin github-actions Suite") -} - -var ( - githubEnv, gitlabEnv string -) - -var _ = BeforeSuite(func() { - githubEnv = os.Getenv("GITHUB_TOKEN") - gitlabEnv = os.Getenv("GITLAB_TOKEN") - err := os.Unsetenv("GITHUB_TOKEN") - Expect(err).Error().ShouldNot(HaveOccurred()) - err = os.Unsetenv("GITLAB_TOKEN") - Expect(err).Error().ShouldNot(HaveOccurred()) -}) - -var _ = AfterSuite(func() { - if githubEnv != "" { - os.Setenv("GITHUB_TOKEN", githubEnv) - } - if gitlabEnv != "" { - os.Setenv("GITLAB_TOKEN", gitlabEnv) - } -}) diff --git a/internal/pkg/plugin/githubactions/read.go b/internal/pkg/plugin/githubactions/read.go deleted file mode 100644 index fc481dc32..000000000 --- a/internal/pkg/plugin/githubactions/read.go +++ /dev/null @@ -1,27 +0,0 @@ -package general - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/cifile" - "github.com/devstream-io/devstream/pkg/util/log" -) - -func Read(options map[string]interface{}) (map[string]interface{}, error) { - // Initialize Operator with Operations - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - setDefault, - validate, - }, - GetStatusOperation: cifile.GetCIFileStatus, - } - - // Execute all Operations in Operator - status, err := operator.Execute(configmanager.RawOptions(options)) - if err != nil { - return nil, err - } - log.Debugf("Return map: %v", status) - return status, nil -} diff --git a/internal/pkg/plugin/githubactions/update.go b/internal/pkg/plugin/githubactions/update.go deleted file mode 100644 index 954422a24..000000000 --- a/internal/pkg/plugin/githubactions/update.go +++ /dev/null @@ -1,5 +0,0 @@ -package general - -func Update(options map[string]interface{}) (map[string]interface{}, error) { - return Create(options) -} diff --git a/internal/pkg/plugin/githubactions/validate.go b/internal/pkg/plugin/githubactions/validate.go deleted file mode 100644 index 904eaa84a..000000000 --- a/internal/pkg/plugin/githubactions/validate.go +++ /dev/null @@ -1,43 +0,0 @@ -package general - -import ( - "fmt" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/cifile/server" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/util" - "github.com/devstream-io/devstream/pkg/util/mapz" - "github.com/devstream-io/devstream/pkg/util/validator" -) - -// validate validates the options provided by the core. -func validate(options configmanager.RawOptions) (configmanager.RawOptions, error) { - opts := new(ci.CIConfig) - if err := util.DecodePlugin(options, opts); err != nil { - return nil, err - } - // check struct data - if err := validator.CheckStructError(opts).Combine(); err != nil { - return nil, err - } - - // check repo is valid - if opts.ProjectRepo.RepoType != "github" { - return nil, fmt.Errorf("github action only support github repo") - } - return options, nil -} - -func setDefault(options configmanager.RawOptions) (configmanager.RawOptions, error) { - opts := new(ci.CIConfig) - if err := util.DecodePlugin(options, opts); err != nil { - return nil, err - } - // set default value of pipeline location - if opts.Pipeline.ConfigLocation == "" { - opts.Pipeline.ConfigLocation = "https://raw.githubusercontent.com/devstream-io/dtm-pipeline-templates/main/github-actions/workflows/main.yml" - } - opts.CIFileConfig = opts.Pipeline.BuildCIFileConfig(server.CIGithubType, opts.ProjectRepo) - return mapz.DecodeStructToMap(opts) -} diff --git a/internal/pkg/plugin/githubactions/validate_test.go b/internal/pkg/plugin/githubactions/validate_test.go deleted file mode 100644 index c56125452..000000000 --- a/internal/pkg/plugin/githubactions/validate_test.go +++ /dev/null @@ -1,56 +0,0 @@ -package general - -import ( - "os" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" -) - -var _ = Describe("validate func", func() { - var ( - options configmanager.RawOptions - configLocation string - ) - BeforeEach(func() { - configLocation = "workflows" - options = configmanager.RawOptions{ - "pipeline": map[string]interface{}{ - "configLocation": configLocation, - }, - "scm": map[string]interface{}{ - "scmType": "github", - "owner": "test", - "name": "gg", - "branch": "main", - "needAuth": true, - }, - } - }) - - When("scm repo is gitlab", func() { - BeforeEach(func() { - options["scm"] = map[string]interface{}{ - "url": "http://exmaple.gitlab.com", - } - }) - It("should return error", func() { - _, err := validate(options) - Expect(err).Error().Should(HaveOccurred()) - }) - }) - When("all valid", func() { - BeforeEach(func() { - os.Setenv("GITHUB_TOKEN", "test") - }) - It("should not raise error", func() { - _, err := validate(options) - Expect(err).Error().ShouldNot(HaveOccurred()) - }) - AfterEach(func() { - os.Unsetenv("GITHUB_TOKEN") - }) - }) -}) diff --git a/internal/pkg/plugin/gitlabcedocker/create.go b/internal/pkg/plugin/gitlabcedocker/create.go deleted file mode 100644 index 2ef9f3a5d..000000000 --- a/internal/pkg/plugin/gitlabcedocker/create.go +++ /dev/null @@ -1,46 +0,0 @@ -package gitlabcedocker - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - dockerInstaller "github.com/devstream-io/devstream/internal/pkg/plugin/installer/docker" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" - "github.com/devstream-io/devstream/pkg/util/types" -) - -func Create(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - // 1. create config and pre-handle operations - opts, err := validateAndDefault(options) - if err != nil { - return nil, err - } - - // 2. config install operations - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - dockerInstaller.Validate, - }, - ExecuteOperations: installer.ExecuteOperations{ - dockerInstaller.Install, - showHelpMsg, - }, - TerminateOperations: installer.TerminateOperations{ - dockerInstaller.ClearWhenInterruption, - }, - GetStatusOperation: dockerInstaller.GetStaticStatus, - } - - // 3. execute installer get status and error - rawOptions, err := types.EncodeStruct(buildDockerOptions(opts)) - if err != nil { - return nil, err - } - status, err := operator.Execute(rawOptions) - if err != nil { - return nil, err - } - log.Debugf("Return map: %v", status) - - return status, nil -} diff --git a/internal/pkg/plugin/gitlabcedocker/delete.go b/internal/pkg/plugin/gitlabcedocker/delete.go deleted file mode 100644 index bc6c7585b..000000000 --- a/internal/pkg/plugin/gitlabcedocker/delete.go +++ /dev/null @@ -1,38 +0,0 @@ -package gitlabcedocker - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - dockerInstaller "github.com/devstream-io/devstream/internal/pkg/plugin/installer/docker" - "github.com/devstream-io/devstream/pkg/util/types" -) - -func Delete(options configmanager.RawOptions) (bool, error) { - // 1. create config and pre-handle operations - opts, err := validateAndDefault(options) - if err != nil { - return false, err - } - - // 2. config delete operations - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - dockerInstaller.Validate, - }, - ExecuteOperations: installer.ExecuteOperations{ - dockerInstaller.DeleteAll, - }, - } - - // 3. delete and get status - rawOptions, err := types.EncodeStruct(buildDockerOptions(opts)) - if err != nil { - return false, err - } - _, err = operator.Execute(rawOptions) - if err != nil { - return false, err - } - - return true, nil -} diff --git a/internal/pkg/plugin/gitlabcedocker/gitlabcedocker.go b/internal/pkg/plugin/gitlabcedocker/gitlabcedocker.go deleted file mode 100644 index 547001a18..000000000 --- a/internal/pkg/plugin/gitlabcedocker/gitlabcedocker.go +++ /dev/null @@ -1,19 +0,0 @@ -package gitlabcedocker - -import "github.com/devstream-io/devstream/pkg/util/types" - -const ( - defaultHostname = "gitlab.example.com" - defaultGitlabHome = "/srv/gitlab" - defaultSSHPort = 22 - defaultHTTPPort = 80 - defaultHTTPSPort = 443 - defaultImageTag = "rc" - gitlabImageName = "gitlab/gitlab-ce" - gitlabContainerName = "gitlab" - dockerRunShmSizeParam = "--shm-size 256m" -) - -var ( - defaultRMDataAfterDelete = types.Bool(false) -) diff --git a/internal/pkg/plugin/gitlabcedocker/gitlabcedocker_suite_test.go b/internal/pkg/plugin/gitlabcedocker/gitlabcedocker_suite_test.go deleted file mode 100644 index 42ca05d6b..000000000 --- a/internal/pkg/plugin/gitlabcedocker/gitlabcedocker_suite_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package gitlabcedocker - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestGitlabcedocker(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Gitlabcedocker Suite") -} diff --git a/internal/pkg/plugin/gitlabcedocker/options.go b/internal/pkg/plugin/gitlabcedocker/options.go deleted file mode 100644 index f8e5fb02d..000000000 --- a/internal/pkg/plugin/gitlabcedocker/options.go +++ /dev/null @@ -1,98 +0,0 @@ -package gitlabcedocker - -import ( - "fmt" - "path/filepath" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - dockerInstaller "github.com/devstream-io/devstream/internal/pkg/plugin/installer/docker" - "github.com/devstream-io/devstream/pkg/util/docker" - "github.com/devstream-io/devstream/pkg/util/log" -) - -// Options is the struct for configurations of the gitlab-ce-docker plugin. -type Options struct { - Hostname string `validate:"hostname" mapstructure:"hostname"` - // GitLab home directory, we assume the path set by user is always correct. - GitLabHome string `mapstructure:"gitlabHome"` - SSHPort uint `mapstructure:"sshPort"` - HTTPPort uint `mapstructure:"httpPort"` - HTTPSPort uint `mapstructure:"httpsPort"` - RmDataAfterDelete *bool `mapstructure:"rmDataAfterDelete"` - ImageTag string `mapstructure:"imageTag"` -} - -func (opts *Options) Defaults() { - if opts.Hostname == "" { - opts.Hostname = defaultHostname - } - if opts.GitLabHome == "" { - opts.GitLabHome = defaultGitlabHome - } - if opts.SSHPort == 0 { - opts.SSHPort = defaultSSHPort - } - if opts.HTTPPort == 0 { - opts.HTTPPort = defaultHTTPPort - } - if opts.HTTPSPort == 0 { - opts.HTTPSPort = defaultHTTPSPort - } - if opts.RmDataAfterDelete == nil { - opts.RmDataAfterDelete = defaultRMDataAfterDelete - } - if opts.ImageTag == "" { - opts.ImageTag = defaultImageTag - } -} - -// gitlabURL is the access URL of GitLab. -var gitlabURL string - -func (opts *Options) setGitLabURL() { - if gitlabURL != "" { - return - } - gitlabURL = fmt.Sprintf("http://%s:%d", opts.Hostname, opts.HTTPPort) -} - -func showHelpMsg(options configmanager.RawOptions) error { - log.Infof("GitLab access URL: %s", gitlabURL) - log.Info("Execute these two command to get/set GitLab root password: ") - log.Info("1. docker exec -it gitlab bash") - log.Info(`2. gitlab-rake "gitlab:password:reset"`) - - return nil -} - -func buildDockerOptions(opts *Options) *dockerInstaller.Options { - portPublishes := []docker.PortPublish{ - {HostPort: opts.SSHPort, ContainerPort: 22}, - {HostPort: opts.HTTPPort, ContainerPort: 80}, - {HostPort: opts.HTTPSPort, ContainerPort: 443}, - } - - dockerOpts := &dockerInstaller.Options{ - RmDataAfterDelete: opts.RmDataAfterDelete, - ImageName: gitlabImageName, - ImageTag: opts.ImageTag, - Hostname: opts.Hostname, - ContainerName: gitlabContainerName, - RestartAlways: true, - Volumes: buildDockerVolumes(opts), - RunParams: []string{dockerRunShmSizeParam}, - PortPublishes: portPublishes, - } - - return dockerOpts -} - -func buildDockerVolumes(opts *Options) docker.Volumes { - volumes := []docker.Volume{ - {HostPath: filepath.Join(opts.GitLabHome, "config"), ContainerPath: "/etc/gitlab"}, - {HostPath: filepath.Join(opts.GitLabHome, "data"), ContainerPath: "/var/opt/gitlab"}, - {HostPath: filepath.Join(opts.GitLabHome, "logs"), ContainerPath: "/var/log/gitlab"}, - } - - return volumes -} diff --git a/internal/pkg/plugin/gitlabcedocker/options_test.go b/internal/pkg/plugin/gitlabcedocker/options_test.go deleted file mode 100644 index 3320620c7..000000000 --- a/internal/pkg/plugin/gitlabcedocker/options_test.go +++ /dev/null @@ -1,51 +0,0 @@ -package gitlabcedocker - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - dockerInstaller "github.com/devstream-io/devstream/internal/pkg/plugin/installer/docker" - "github.com/devstream-io/devstream/pkg/util/docker" -) - -var _ = Describe("Options", func() { - - var opts *Options - BeforeEach(func() { - opts = &Options{ - GitLabHome: "/srv/gitlab", - Hostname: "gitlab.example.com", - SSHPort: 8122, - HTTPPort: 8180, - HTTPSPort: 8443, - RmDataAfterDelete: nil, - ImageTag: "rc", - } - }) - - Describe("buildDockerRunOptions func", func() { - It("should build the docker run options successfully", func() { - OptsBuild := *buildDockerOptions(opts) - OptsExpect := dockerInstaller.Options{ - ImageName: "gitlab/gitlab-ce", - ImageTag: "rc", - Hostname: "gitlab.example.com", - ContainerName: "gitlab", - RestartAlways: true, - PortPublishes: []docker.PortPublish{ - {HostPort: 8122, ContainerPort: 22}, - {HostPort: 8180, ContainerPort: 80}, - {HostPort: 8443, ContainerPort: 443}, - }, - Volumes: []docker.Volume{ - {HostPath: "/srv/gitlab/config", ContainerPath: "/etc/gitlab"}, - {HostPath: "/srv/gitlab/data", ContainerPath: "/var/opt/gitlab"}, - {HostPath: "/srv/gitlab/logs", ContainerPath: "/var/log/gitlab"}, - }, - RunParams: []string{dockerRunShmSizeParam}, - } - - Expect(OptsBuild).To(Equal(OptsExpect)) - }) - }) -}) diff --git a/internal/pkg/plugin/gitlabcedocker/read.go b/internal/pkg/plugin/gitlabcedocker/read.go deleted file mode 100644 index b3f1e6f8d..000000000 --- a/internal/pkg/plugin/gitlabcedocker/read.go +++ /dev/null @@ -1,39 +0,0 @@ -package gitlabcedocker - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - dockerInstaller "github.com/devstream-io/devstream/internal/pkg/plugin/installer/docker" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" - "github.com/devstream-io/devstream/pkg/util/types" -) - -func Read(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - // 1. create config and pre-handle operations - opts, err := validateAndDefault(options) - if err != nil { - return nil, err - } - - // 2. config read operations - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - dockerInstaller.Validate, - }, - GetStatusOperation: dockerInstaller.GetRunningStatus, - } - - // 3. get status - rawOptions, err := types.EncodeStruct(buildDockerOptions(opts)) - if err != nil { - return nil, err - } - status, err := operator.Execute(rawOptions) - if err != nil { - return nil, err - } - - log.Debugf("Return map: %v", status) - return status, nil -} diff --git a/internal/pkg/plugin/gitlabcedocker/update.go b/internal/pkg/plugin/gitlabcedocker/update.go deleted file mode 100644 index 11292b65d..000000000 --- a/internal/pkg/plugin/gitlabcedocker/update.go +++ /dev/null @@ -1,47 +0,0 @@ -package gitlabcedocker - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - dockerInstaller "github.com/devstream-io/devstream/internal/pkg/plugin/installer/docker" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" - "github.com/devstream-io/devstream/pkg/util/types" -) - -func Update(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - // 1. create config and pre-handle operations - opts, err := validateAndDefault(options) - if err != nil { - return nil, err - } - - // reserve data when updated - opts.RmDataAfterDelete = types.Bool(false) - - // 2. config install operations - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - dockerInstaller.Validate, - }, - ExecuteOperations: installer.ExecuteOperations{ - dockerInstaller.DeleteAll, - dockerInstaller.Install, - showHelpMsg, - }, - GetStatusOperation: dockerInstaller.GetRunningStatus, - } - - // 3. update and get status - rawOptions, err := types.EncodeStruct(buildDockerOptions(opts)) - if err != nil { - return nil, err - } - status, err := operator.Execute(rawOptions) - if err != nil { - return nil, err - } - log.Debugf("Return map: %v", status) - - return status, nil -} diff --git a/internal/pkg/plugin/gitlabcedocker/validate.go b/internal/pkg/plugin/gitlabcedocker/validate.go deleted file mode 100644 index 5f7641455..000000000 --- a/internal/pkg/plugin/gitlabcedocker/validate.go +++ /dev/null @@ -1,40 +0,0 @@ -package gitlabcedocker - -import ( - "fmt" - "os" - "path/filepath" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - - "github.com/mitchellh/mapstructure" - - "github.com/devstream-io/devstream/pkg/util/validator" -) - -func validateAndDefault(options configmanager.RawOptions) (*Options, error) { - var opts *Options - if err := mapstructure.Decode(options, &opts); err != nil { - return nil, err - } - - opts.Defaults() - - // validate - errs := validator.CheckStructError(opts) - // volume directory must be absolute path - if !filepath.IsAbs(opts.GitLabHome) { - errs = append(errs, fmt.Errorf("field gitLabHome must be an absolute path")) - } - if len(errs) != 0 { - return nil, errs.Combine() - } - - if err := os.MkdirAll(opts.GitLabHome, 0755); err != nil { - return nil, err - } - - opts.setGitLabURL() - - return opts, nil -} diff --git a/internal/pkg/plugin/gitlabci/create.go b/internal/pkg/plugin/gitlabci/create.go deleted file mode 100644 index c4238039d..000000000 --- a/internal/pkg/plugin/gitlabci/create.go +++ /dev/null @@ -1,31 +0,0 @@ -package gitlabci - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/cifile" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" -) - -func Create(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - setDefault, - validate, - }, - ExecuteOperations: installer.ExecuteOperations{ - preConfigGitlab, - cifile.PushCIFiles, - }, - GetStatusOperation: cifile.GetCIFileStatus, - } - - // Execute all Operations in Operator - status, err := operator.Execute(options) - if err != nil { - return nil, err - } - log.Debugf("Return map: %v", status) - return status, nil -} diff --git a/internal/pkg/plugin/gitlabci/delete.go b/internal/pkg/plugin/gitlabci/delete.go deleted file mode 100644 index 3e274857e..000000000 --- a/internal/pkg/plugin/gitlabci/delete.go +++ /dev/null @@ -1,27 +0,0 @@ -package gitlabci - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/cifile" -) - -func Delete(options configmanager.RawOptions) (bool, error) { - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - setDefault, - validate, - }, - ExecuteOperations: installer.ExecuteOperations{ - //TODO(steinliber): delete gitlab runner if it exists - cifile.DeleteCIFiles, - }, - } - - // Execute all Operations in Operator - _, err := operator.Execute(options) - if err != nil { - return false, err - } - return true, nil -} diff --git a/internal/pkg/plugin/gitlabci/gitlabci.go b/internal/pkg/plugin/gitlabci/gitlabci.go deleted file mode 100644 index 07d6daa33..000000000 --- a/internal/pkg/plugin/gitlabci/gitlabci.go +++ /dev/null @@ -1,104 +0,0 @@ -package gitlabci - -import ( - _ "embed" - "fmt" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/helminstaller" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/step" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/util" - "github.com/devstream-io/devstream/pkg/util/log" - "github.com/devstream-io/devstream/pkg/util/scm" - "github.com/devstream-io/devstream/pkg/util/scm/git" - "github.com/devstream-io/devstream/pkg/util/scm/gitlab" - "github.com/devstream-io/devstream/pkg/util/template" -) - -//go:embed tpl/helmValue.tpl.yaml -var helmValueTpl string - -func preConfigGitlab(rawOptions configmanager.RawOptions) error { - opts := new(options) - if err := util.DecodePlugin(rawOptions, opts); err != nil { - return err - } - - // PreConfig steps - stepConfigs := step.ExtractValidStepConfig(opts.Pipeline) - scmClient, err := scm.NewClientWithAuth(opts.ProjectRepo) - if err != nil { - log.Debugf("init gitlab client failed: %+v", err) - return err - } - for _, c := range stepConfigs { - err := c.ConfigSCM(scmClient) - if err != nil { - log.Debugf("gitlabci config step failed: %+v", err) - return err - } - } - // create runner in self-host gitlab - if opts.needCreateRunner() { - return createGitlabRunnerByHelm(opts.ProjectRepo) - } - return nil -} - -// createGitlabRunnerByHelm will install gitlab runner if it's not exist -func createGitlabRunnerByHelm(repoInfo *git.RepoInfo) error { - gitlabClient, err := gitlab.NewClient(repoInfo) - if err != nil { - log.Debugf("gitlabci init gitlab client failed: %+v", err) - return err - } - // 1. check project has runner, if project has runner, just return - runner, err := gitlabClient.ListRepoRunner() - if err != nil { - log.Debugf("gitlabci get project runner failed: %+v", err) - return err - } - if len(runner) > 0 { - log.Debugf("gitlabci runner exist") - return nil - } - runnerToken, err := gitlabClient.ResetRepoRunnerToken() - if err != nil { - log.Debugf("gitlabci reset project runner token failed: %+v", err) - return err - } - // 2. else create runner for this project - valuesYaml, err := template.NewRenderClient(&template.TemplateOption{ - Name: "gitlab-runner"}, template.ContentGetter).Render( - helmValueTpl, map[string]any{ - "GitlabURL": repoInfo.BaseURL, - "RegisterToken": runnerToken, - }) - if err != nil { - return err - } - helmOptions := configmanager.RawOptions{ - "instanceID": "gitlab-global-runner", - "repo": map[string]interface{}{ - "name": "gitlab", - "url": "https://charts.gitlab.io", - }, - "chart": map[string]interface{}{ - "chartPath": "", - "chartName": "gitlab/gitlab-runner", - "ValuesYaml": valuesYaml, - "wait": true, - "timeout": "10m", - "upgradeCRDs": false, - "namespace": "gitlab", - "releaseName": fmt.Sprintf("%s-runner", repoInfo.Repo), - }, - } - _, err = helminstaller.Create(helmOptions) - if err != nil { - log.Debugf("gitlab ci install runner by helm failed: %s", err) - return fmt.Errorf("gitlabci create runner by helm failed, please check your kubeneretes config") - } - // disable gitlab shared runner - return gitlabClient.DisableRepoSharedRunner() -} diff --git a/internal/pkg/plugin/gitlabci/gitlabci_suite_test.go b/internal/pkg/plugin/gitlabci/gitlabci_suite_test.go deleted file mode 100644 index e63702e80..000000000 --- a/internal/pkg/plugin/gitlabci/gitlabci_suite_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package gitlabci_test - -import ( - "os" - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestGitlabcedocker(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Plugin github-actions Suite") -} - -var ( - gitlabEnv string -) - -var _ = BeforeSuite(func() { - gitlabEnv = os.Getenv("GITLAB_TOKEN") - err := os.Unsetenv("GITLAB_TOKEN") - Expect(err).Error().ShouldNot(HaveOccurred()) -}) - -var _ = AfterSuite(func() { - if gitlabEnv != "" { - os.Setenv("GITLAB_TOKEN", gitlabEnv) - } -}) diff --git a/internal/pkg/plugin/gitlabci/options.go b/internal/pkg/plugin/gitlabci/options.go deleted file mode 100644 index 55a93e78a..000000000 --- a/internal/pkg/plugin/gitlabci/options.go +++ /dev/null @@ -1,19 +0,0 @@ -package gitlabci - -import "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci" - -type options struct { - Runner *runnerOptions `mapstructure:"runner"` - ci.CIConfig `mapstructure:",squash"` -} - -type runnerOptions struct { - Enable bool `mapstructure:"enable"` -} - -func (o *options) needCreateRunner() bool { - if o.Runner != nil { - return o.Runner.Enable - } - return false -} diff --git a/internal/pkg/plugin/gitlabci/options_test.go b/internal/pkg/plugin/gitlabci/options_test.go deleted file mode 100644 index a097c70d2..000000000 --- a/internal/pkg/plugin/gitlabci/options_test.go +++ /dev/null @@ -1,84 +0,0 @@ -package gitlabci - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/cifile" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/cifile/server" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/step" - "github.com/devstream-io/devstream/pkg/util/downloader" - "github.com/devstream-io/devstream/pkg/util/scm/git" -) - -var _ = Describe("action struct", func() { - var ( - pipelineConfig *ci.PipelineConfig - imageRepoURL, user, repoName string - configLocation downloader.ResourceLocation - repoInfo *git.RepoInfo - ciType server.CIServerType - ) - BeforeEach(func() { - imageRepoURL = "exmaple.com" - user = "test_user" - repoName = "test_repo" - configLocation = "123/workflows" - ciType = server.CIServerType("gitlab") - pipelineConfig = &ci.PipelineConfig{ - ConfigLocation: configLocation, - ImageRepo: &step.ImageRepoStepConfig{ - URL: imageRepoURL, - User: user, - }, - } - repoInfo = &git.RepoInfo{ - Repo: repoName, - RepoType: "gitlab", - } - }) - Context("buildCIFileConfig method", func() { - It("should work normal", func() { - var nilStepConfig *step.SonarQubeStepConfig - var nilDingTalkConfig *step.DingtalkStepConfig - var nilBool *bool - var nilArray []string - CIFileConfig := pipelineConfig.BuildCIFileConfig(ciType, repoInfo) - Expect(string(CIFileConfig.Type)).Should(Equal("gitlab")) - Expect(CIFileConfig.ConfigLocation).Should(Equal(configLocation)) - expectVars := cifile.CIFileVarsMap{ - "SonarqubeSecretKey": "SONAR_SECRET_TOKEN", - "AppName": "test_repo", - "ImageRepoSecret": "IMAGE_REPO_SECRET", - "ImageRepoDockerSecret": "image-repo-auth", - "RepoType": "gitlab", - "imageRepo": map[string]interface{}{ - "url": "exmaple.com", - "user": "test_user", - "password": "", - }, - "dingTalk": nilDingTalkConfig, - "DingTalkSecretKey": "DINGTALK_SECURITY_VALUE", - "DingTalkSecretToken": "DINGTALK_SECURITY_TOKEN", - "StepGlobalVars": "", - "configLocation": downloader.ResourceLocation("123/workflows"), - "sonarqube": nilStepConfig, - "GitlabConnectionID": "gitlabConnection", - "language": map[string]interface{}{ - "name": "", - "version": "", - "frameWork": "", - }, - "test": map[string]interface{}{ - "enable": nilBool, - "command": nilArray, - "containerName": "", - "coverageCommand": "", - "CoverageStatusCommand": "", - }, - } - Expect(CIFileConfig.Vars).Should(Equal(expectVars)) - }) - }) -}) diff --git a/internal/pkg/plugin/gitlabci/read.go b/internal/pkg/plugin/gitlabci/read.go deleted file mode 100644 index 8a5e4e379..000000000 --- a/internal/pkg/plugin/gitlabci/read.go +++ /dev/null @@ -1,27 +0,0 @@ -package gitlabci - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/cifile" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" -) - -func Read(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - setDefault, - validate, - }, - GetStatusOperation: cifile.GetCIFileStatus, - } - - status, err := operator.Execute(options) - if err != nil { - return nil, err - } - - log.Debugf("Return map: %v", status) - return status, nil -} diff --git a/internal/pkg/plugin/gitlabci/tpl/helmValue.tpl.yaml b/internal/pkg/plugin/gitlabci/tpl/helmValue.tpl.yaml deleted file mode 100644 index a8b7cfcbc..000000000 --- a/internal/pkg/plugin/gitlabci/tpl/helmValue.tpl.yaml +++ /dev/null @@ -1,638 +0,0 @@ -## GitLab Runner Image -## -## By default it's using registry.gitlab.com/gitlab-org/gitlab-runner:alpine-v{VERSION} -## where {VERSION} is taken from Chart.yaml from appVersion field -## -## DEPRECATED: Setting `image: registry.gitlab.com/gitlab-org/gitlab-runner:alpine-v11.6.0` is deprecated -## -## ref: https://gitlab.com/gitlab-org/gitlab-runner/container_registry/29383?orderBy=NAME&sort=asc&search[]=alpine-v&search[]= -## -## Note: If you change the image to the ubuntu release -## don't forget to change the securityContext; -## these images run on different user IDs. -## -image: - registry: registry.gitlab.com - image: gitlab-org/gitlab-runner - # tag: alpine-v11.6.0 - -## Specify a imagePullPolicy for the main runner deployment -## 'Always' if imageTag is 'latest', else set to 'IfNotPresent' -## -## Note: it does not apply to job containers launched by this executor. -## Use `pull_policy` in [runners.kubernetes] to change it. -## -## ref: https://kubernetes.io/docs/concepts/containers/images/#pre-pulled-images -## -imagePullPolicy: IfNotPresent - -## Specifying ImagePullSecrets on a Pod -## Kubernetes supports specifying container image registry keys on a Pod. -## ref: https://kubernetes.io/docs/concepts/containers/images/#specifying-imagepullsecrets-on-a-pod -## -# imagePullSecrets: -# - name: "image-pull-secret" - -## Timeout, in seconds, for liveness and readiness probes of a runner pod. -# probeTimeoutSeconds: 1 - -## How many runner pods to launch. -## -## Note: Using more than one replica is not supported with a runnerToken. Use a runnerRegistrationToken -## to create multiple runner replicas. -# replicas: 1 - -## How many old ReplicaSets for this Deployment you want to retain -# revisionHistoryLimit: 10 - -## The GitLab Server URL (with protocol) that want to register the runner against -## ref: https://docs.gitlab.com/runner/commands/index.html#gitlab-runner-register -## -gitlabUrl: [[ .GitlabURL ]] - -## The Registration Token for adding new Runners to the GitLab Server. This must -## be retrieved from your GitLab Instance. -## ref: https://docs.gitlab.com/ce/ci/runners/index.html -## -runnerRegistrationToken: [[ .RegisterToken ]] - -## The Runner Token for adding new Runners to the GitLab Server. This must -## be retrieved from your GitLab Instance. It is token of already registered runner. -## ref: (we don't yet have docs for that, but we want to use existing token) -## -# runnerToken: "" -# - -## Unregister all runners before termination -## -## Updating the runner's chart version or configuration will cause the runner container -## to be terminated and created again. This may cause your Gitlab instance to reference -## non-existant runners. Un-registering the runner before termination mitigates this issue. -## ref: https://docs.gitlab.com/runner/commands/index.html#gitlab-runner-unregister -## -# unregisterRunners: true - -## When stopping the runner, give it time to wait for its jobs to terminate. -## -## Updating the runner's chart version or configuration will cause the runner container -## to be terminated with a graceful stop request. terminationGracePeriodSeconds -## instructs Kubernetes to wait long enough for the runner pod to terminate gracefully. -## ref: https://docs.gitlab.com/runner/commands/#signals -terminationGracePeriodSeconds: 3600 - -## Set the certsSecretName in order to pass custom certficates for GitLab Runner to use -## Provide resource name for a Kubernetes Secret Object in the same namespace, -## this is used to populate the /home/gitlab-runner/.gitlab-runner/certs/ directory -## ref: https://docs.gitlab.com/runner/configuration/tls-self-signed.html#supported-options-for-self-signed-certificates-targeting-the-gitlab-server -## -# certsSecretName: - -## Configure the maximum number of concurrent jobs -## ref: https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-global-section -## -concurrent: 10 - -## Defines in seconds how often to check GitLab for a new builds -## ref: https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-global-section -## -checkInterval: 30 - -## Configure GitLab Runner's logging level. Available values are: debug, info, warn, error, fatal, panic -## ref: https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-global-section -## -# logLevel: - -## Configure GitLab Runner's logging format. Available values are: runner, text, json -## ref: https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-global-section -## -# logFormat: - -## Configure GitLab Runner's Sentry DSN. -## ref https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-global-section -## -# sentryDsn: - -## A custom bash script that will be executed prior to the invocation -## gitlab-runner process -# -#preEntrypointScript: | -# echo "hello" - -## Specify whether the runner should start the session server. -## Defaults to false -## ref: -## -## When sessionServer is enabled, the user can either provide a public publicIP -## or either rely on the external IP auto discovery -## When a serviceAccountName is used with the automounting to the pod disable, -## we recommend the usage of the publicIP -sessionServer: - enabled: false - # annotations: {} - # timeout: 1800 - # internalPort: 8093 - # externalPort: 9000 - # publicIP: "" - # loadBalancerSourceRanges: - # - 1.2.3.4/32 - -## For RBAC support: -rbac: - create: true - - ## Define specific rbac permissions. - ## DEPRECATED: see .Values.rbac.rules - # resources: ["pods", "pods/exec", "secrets"] - # verbs: ["get", "list", "watch", "create", "patch", "delete"] - - ## Define list of rules to be added to the rbac role permissions. - ## Each rule supports the keys: - ## - apiGroups: default "" (indicates the core API group) if missing or empty. - ## - resources: default "*" if missing or empty. - ## - verbs: default "*" if missing or empty. - ## - ## Read more about the recommended rules on the following link - ## - ## ref: https://docs.gitlab.com/runner/executors/kubernetes.html#configuring-executor-service-account - ## - rules: [] - # - resources: ["configmaps", "pods", "pods/attach", "secrets", "services"] - # verbs: ["get", "list", "watch", "create", "patch", "update", "delete"] - # - apiGroups: [""] - # resources: ["pods/exec"] - # verbs: ["create", "patch", "delete"] - - ## Run the gitlab-bastion container with the ability to deploy/manage containers of jobs - ## cluster-wide or only within namespace - clusterWideAccess: false - - ## Use the following Kubernetes Service Account name if RBAC is disabled in this Helm chart (see rbac.create) - ## - # serviceAccountName: default - - ## Specify annotations for Service Accounts, useful for annotations such as eks.amazonaws.com/role-arn - ## - ## ref: https://docs.aws.amazon.com/eks/latest/userguide/specify-service-account-role.html - ## - # serviceAccountAnnotations: {} - - ## Use podSecurity Policy - ## ref: https://kubernetes.io/docs/concepts/policy/pod-security-policy/ - podSecurityPolicy: - enabled: false - resourceNames: - - gitlab-runner - - ## Specify one or more imagePullSecrets used for pulling the runner image - ## - ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#add-imagepullsecrets-to-a-service-account - ## - # imagePullSecrets: [] - -## Configure integrated Prometheus metrics exporter -## -## ref: https://docs.gitlab.com/runner/monitoring/#configuration-of-the-metrics-http-server -## -metrics: - enabled: false - - ## Define a name for the metrics port - ## - portName: metrics - - ## Provide a port number for the integrated Prometheus metrics exporter - ## - port: 9252 - - ## Configure a prometheus-operator serviceMonitor to allow autodetection of - ## the scraping target. Requires enabling the service resource below. - ## - serviceMonitor: - enabled: false - - ## Provide additional labels to the service monitor ressource - ## - ## labels: {} - - ## Define a scrape interval (otherwise prometheus default is used) - ## - ## ref: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#scrape_config - ## - # interval: "" - - ## Specify the scrape protocol scheme e.g., https or http - ## - # scheme: "http" - - ## Supply a tls configuration for the service monitor - ## - ## ref: https://github.com/helm/charts/blob/master/stable/prometheus-operator/crds/crd-servicemonitor.yaml - ## - # tlsConfig: {} - - ## The URI path where prometheus metrics can be scraped from - ## - # path: "/metrics" - - ## A list of MetricRelabelConfigs to apply to samples before ingestion - ## - ## ref: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#metric_relabel_configs - ## - # metricRelabelings: [] - - ## A list of RelabelConfigs to apply to samples before scraping - ## - ## ref: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config - ## - ## relabelings: [] - -## Configure a service resource e.g., to allow scraping metrics via -## prometheus-operator serviceMonitor -service: - enabled: false - - ## Provide additonal labels for the service - ## - # labels: {} - - ## Provide additonal annotations for the service - ## - # annotations: {} - - ## Define a specific ClusterIP if you do not want a dynamic one - ## - ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#choosing-your-own-ip-address - ## - # clusterIP: "" - - ## Define a list of one or more external IPs for this service - ## - ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#external-ips - ## - # externalIPs: [] - - ## Provide a specific loadbalancerIP e.g., of an external Loadbalancer - ## - ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#loadbalancer - ## - # loadBalancerIP: "" - - ## Provide a list of source IP ranges to have access to this service - ## - ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#aws-nlb-support - ## - # loadBalancerSourceRanges: [] - - ## Specify the service type e.g., ClusterIP, NodePort, Loadbalancer or ExternalName - ## - ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types - ## - type: ClusterIP - - ## Specify the services metrics nodeport if you use a service of type nodePort - ## - # metrics: - - ## Specify the node port under which the prometheus metrics of the runner are made - ## available. - ## - ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#nodeport - ## - # nodePort: "" - - ## Provide a list of additional ports to be exposed by this service - ## - ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service - ## - # additionalPorts: [] - -## Configuration for the Pods that the runner launches for each new job -## -runners: - # runner configuration, where the multi line strings is evaluated as - # template so you can specify helm values inside of it. - # - # tpl: https://helm.sh/docs/howto/charts_tips_and_tricks/#using-the-tpl-function - # runner configuration: https://docs.gitlab.com/runner/configuration/advanced-configuration.html - config: | - [[`[[runners]]`]] - [runners.kubernetes] - namespace = "{{.Release.Namespace}}" - image = "ubuntu:16.04" - - ## Which executor should be used - ## - # executor: kubernetes - - ## Default container image to use for builds when none is specified - ## - ## DEPRECATED: See https://docs.gitlab.com/runner/install/kubernetes.html#additional-configuration - # image: ubuntu:16.04 - - ## Specify one or more imagePullSecrets - ## - ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ - ## - ## DEPRECATED: See https://docs.gitlab.com/runner/install/kubernetes.html#additional-configuration - # imagePullSecrets: [] - - ## Specify the image pull policy: never, if-not-present, always. The cluster default will be used if not set. - ## - ## DEPRECATED: See https://docs.gitlab.com/runner/install/kubernetes.html#additional-configuration - imagePullPolicy: "if-not-present" - - ## Defines number of concurrent requests for new job from GitLab - ## ref: https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runners-section - ## - ## DEPRECATED: See https://docs.gitlab.com/runner/install/kubernetes.html#additional-configuration - # requestConcurrency: 1 - - ## Specify whether the runner should be locked to a specific project: true, false. Defaults to true. - ## - # locked: true - - ## Specify the tags associated with the runner. Comma-separated list of tags. - ## - ## ref: https://docs.gitlab.com/ee/ci/runners/configure_runners.html#use-tags-to-control-which-jobs-a-runner-can-run - ## - tags: ci - - ## Specify the name for the runner. - ## - # name: "" - - ## Specify the maximum timeout (in seconds) that will be set for job when using this Runner - ## - # maximumTimeout: "" - - ## Specify if jobs without tags should be run. - ## If not specified, Runner will default to true if no tags were specified. In other case it will - ## default to false. - ## - ## ref: https://docs.gitlab.com/ee/ci/runners/configure_runners.html#set-a-runner-to-run-untagged-jobs - ## - runUntagged: true - privileged: true - - ## Specify whether the runner should only run protected branches. - ## Defaults to false. - ## - ## ref: https://docs.gitlab.com/ee/ci/runners/configure_runners.html#prevent-runners-from-revealing-sensitive-information - ## - # protected: true - - ## Run all containers with the privileged flag enabled - ## This will allow the docker:dind image to run if you need to run Docker - ## commands. Please read the docs before turning this on: - ## ref: https://docs.gitlab.com/runner/executors/kubernetes.html#using-dockerdind - ## - ## DEPRECATED: See https://docs.gitlab.com/runner/install/kubernetes.html#additional-configuration - - ## The name of the secret containing runner-token and runner-registration-token - # secret: gitlab-runner - - ## Namespace to run Kubernetes jobs in (defaults to the same namespace of this release) - ## - ## DEPRECATED: See https://docs.gitlab.com/runner/install/kubernetes.html#additional-configuration - # namespace: - - ## The amount of time, in seconds, that needs to pass before the runner will - ## timeout attempting to connect to the container it has just created. - ## ref: https://docs.gitlab.com/runner/executors/kubernetes.html - ## DEPRECATED: See https://docs.gitlab.com/runner/install/kubernetes.html#additional-configuration - # pollTimeout: 180 - - ## Set maximum build log size in kilobytes, by default set to 4096 (4MB) - ## ref: https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runners-section - ## DEPRECATED: See https://docs.gitlab.com/runner/install/kubernetes.html#additional-configuration - # outputLimit: 4096 - - ## Distributed runners caching - ## ref: https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching - ## - ## If you want to use s3 based distributing caching: - ## First of all you need to uncomment General settings and S3 settings sections. - ## - ## Create a secret 's3access' containing 'accesskey' & 'secretkey' - ## ref: https://aws.amazon.com/blogs/security/wheres-my-secret-access-key/ - ## - ## $ kubectl create secret generic s3access \ - ## --from-literal=accesskey="YourAccessKey" \ - ## --from-literal=secretkey="YourSecretKey" - ## ref: https://kubernetes.io/docs/concepts/configuration/secret/ - ## - ## If you want to use gcs based distributing caching: - ## First of all you need to uncomment General settings and GCS settings sections. - ## - ## Access using credentials file: - ## Create a secret 'google-application-credentials' containing your application credentials file. - ## ref: https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runnerscachegcs-section - ## You could configure - ## $ kubectl create secret generic google-application-credentials \ - ## --from-file=gcs-application-credentials-file=./path-to-your-google-application-credentials-file.json - ## ref: https://kubernetes.io/docs/concepts/configuration/secret/ - ## - ## Access using access-id and private-key: - ## Create a secret 'gcsaccess' containing 'gcs-access-id' & 'gcs-private-key'. - ## ref: https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runnerscachegcs-section - ## You could configure - ## $ kubectl create secret generic gcsaccess \ - ## --from-literal=gcs-access-id="YourAccessID" \ - ## --from-literal=gcs-private-key="YourPrivateKey" - ## ref: https://kubernetes.io/docs/concepts/configuration/secret/ - ## - ## If you want to use Azure-based distributed caching: - ## First, uncomment General settings. - ## - ## Create a secret 'azureaccess' containing 'azure-account-name' & 'azure-account-key' - ## ref: https://docs.microsoft.com/en-us/azure/storage/blobs/storage-blobs-introduction - ## - ## $ kubectl create secret generic azureaccess \ - ## --from-literal=azure-account-name="YourAccountName" \ - ## --from-literal=azure-account-key="YourAccountKey" - ## ref: https://kubernetes.io/docs/concepts/configuration/secret/ - - cache: {} - ## General settings - ## DEPRECATED: See https://docs.gitlab.com/runner/install/kubernetes.html#additional-configuration and https://docs.gitlab.com/runner/install/kubernetes.html#using-cache-with-configuration-template - # cacheType: s3 - # cachePath: "gitlab_runner" - # cacheShared: true - - ## S3 settings - ## DEPRECATED: See https://docs.gitlab.com/runner/install/kubernetes.html#additional-configuration and https://docs.gitlab.com/runner/install/kubernetes.html#using-cache-with-configuration-template - # s3ServerAddress: s3.amazonaws.com - # s3BucketName: - # s3BucketLocation: - # s3CacheInsecure: false - - ## GCS settings - ## DEPRECATED: See https://docs.gitlab.com/runner/install/kubernetes.html#additional-configuration and https://docs.gitlab.com/runner/install/kubernetes.html#using-cache-with-configuration-template - # gcsBucketName: - - ## S3 the name of the secret. - # secretName: s3access - ## Use this line for access using gcs-access-id and gcs-private-key - # secretName: gcsaccess - ## Use this line for access using google-application-credentials file - # secretName: google-application-credentials - ## Use this line for access using Azure with azure-account-name and azure-account-key - # secretName: azureaccess - - - ## Build Container specific configuration - ## - ## DEPRECATED: See https://docs.gitlab.com/runner/install/kubernetes.html#additional-configuration - builds: {} - # cpuLimit: 200m - # cpuLimitOverwriteMaxAllowed: 400m - # memoryLimit: 256Mi - # memoryLimitOverwriteMaxAllowed: 512Mi - # cpuRequests: 100m - # cpuRequestsOverwriteMaxAllowed: 200m - # memoryRequests: 128Mi - # memoryRequestsOverwriteMaxAllowed: 256Mi - - ## Service Container specific configuration - ## - ## DEPRECATED: See https://docs.gitlab.com/runner/install/kubernetes.html#additional-configuration - services: {} - # cpuLimit: 200m - # memoryLimit: 256Mi - # cpuRequests: 100m - # memoryRequests: 128Mi - - ## Helper Container specific configuration - ## - ## DEPRECATED: See https://docs.gitlab.com/runner/install/kubernetes.html#additional-configuration - helpers: {} - # cpuLimit: 200m - # memoryLimit: 256Mi - # cpuRequests: 100m - # memoryRequests: 128Mi - # image: "registry.gitlab.com/gitlab-org/gitlab-runner-helper:x86_64-${CI_RUNNER_REVISION}" - - ## Helper container security context configuration - ## Refer to https://docs.gitlab.com/runner/executors/kubernetes.html#using-security-context - ## DEPRECATED: See https://docs.gitlab.com/runner/install/kubernetes.html#additional-configuration - # pod_security_context: - # run_as_non_root: true - # run_as_user: 100 - # run_as_group: 100 - # fs_group: 65533 - # supplemental_groups: [101, 102] - - ## Service Account to be used for runners - ## - # serviceAccountName: - - ## If Gitlab is not reachable through $CI_SERVER_URL - ## - ## DEPRECATED: See https://docs.gitlab.com/runner/install/kubernetes.html#additional-configuration - # cloneUrl: - - ## Specify node labels for CI job pods assignment - ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ - ## - ## DEPRECATED: See https://docs.gitlab.com/runner/install/kubernetes.html#additional-configuration - # nodeSelector: {} - - ## Specify node tolerations for CI job pods assignment - ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ - ## - ## DEPRECATED: See https://docs.gitlab.com/runner/install/kubernetes.html#additional-configuration - # nodeTolerations: {} - - ## Specify pod labels for CI job pods - ## - ## DEPRECATED: See https://docs.gitlab.com/runner/install/kubernetes.html#additional-configuration - # podLabels: {} - - ## Specify annotations for job pods, useful for annotations such as iam.amazonaws.com/role - ## DEPRECATED: See https://docs.gitlab.com/runner/install/kubernetes.html#additional-configuration - # podAnnotations: {} - - ## Configure environment variables that will be injected to the pods that are created while - ## the build is running. These variables are passed as parameters, i.e. `--env "NAME=VALUE"`, - ## to `gitlab-runner register` command. - ## - ## Note that `envVars` (see below) are only present in the runner pod, not the pods that are - ## created for each build. - ## - ## ref: https://docs.gitlab.com/runner/commands/#gitlab-runner-register - ## - ## DEPRECATED: See https://docs.gitlab.com/runner/install/kubernetes.html#additional-configuration - # env: - # NAME: VALUE - - -## Specify the name of the scheduler which used to schedule runner pods. -## Kubernetes supports multiple scheduler configurations. -## ref: https://kubernetes.io/docs/reference/scheduling -# schedulerName: "my-custom-scheduler" - -## Configure securitycontext for the main container -## ref: http://kubernetes.io/docs/user-guide/security-context/ -## -securityContext: - allowPrivilegeEscalation: false - readOnlyRootFilesystem: false - runAsNonRoot: true - privileged: false - capabilities: - drop: ["ALL"] - -## Configure securitycontext valid for the whole pod -## ref: http://kubernetes.io/docs/user-guide/security-context/ -## -podSecurityContext: - runAsUser: 100 - # runAsGroup: 65533 - fsGroup: 65533 - # supplementalGroups: [65533] - - ## Note: values for the ubuntu image: - # runAsUser: 999 - # fsGroup: 999 - -## Configure resource requests and limits -## ref: http://kubernetes.io/docs/user-guide/compute-resources/ -## -resources: {} - # limits: - # memory: 256Mi - # cpu: 200m - # requests: - # memory: 128Mi - # cpu: 100m - -## Affinity for pod assignment -## Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity -## -affinity: {} - -## Node labels for pod assignment -## Ref: https://kubernetes.io/docs/user-guide/node-selection/ -## -nodeSelector: {} - # Example: The gitlab runner manager should not run on spot instances so you can assign - # them to the regular worker nodes only. - # node-role.kubernetes.io/worker: "true" - -## List of node taints to tolerate (requires Kubernetes >= 1.6) -## Ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ -## -tolerations: [] - # Example: Regular worker nodes may have a taint, thus you need to tolerate the taint - # when you assign the gitlab runner manager with nodeSelector or affinity to the nodes. - # - key: "node-role.kubernetes.io/worker" - # operator: "Exists" - -## Configure environment variables that will be present when the registration command runs -## This provides further control over the registration process and the config.toml file -## ref: `gitlab-runner register --help` -## ref: https://docs.gitlab.com/runner/configuration/advanced-configuration.html -## -# envVars: -# - name: RUNNER_EXECUTOR -# value: kubernetes diff --git a/internal/pkg/plugin/gitlabci/update.go b/internal/pkg/plugin/gitlabci/update.go deleted file mode 100644 index aa0e8e696..000000000 --- a/internal/pkg/plugin/gitlabci/update.go +++ /dev/null @@ -1,10 +0,0 @@ -package gitlabci - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/statemanager" -) - -func Update(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return Create(options) -} diff --git a/internal/pkg/plugin/gitlabci/validate.go b/internal/pkg/plugin/gitlabci/validate.go deleted file mode 100644 index af7c84cd4..000000000 --- a/internal/pkg/plugin/gitlabci/validate.go +++ /dev/null @@ -1,42 +0,0 @@ -package gitlabci - -import ( - "fmt" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/cifile/server" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/util" - "github.com/devstream-io/devstream/pkg/util/mapz" - "github.com/devstream-io/devstream/pkg/util/validator" -) - -// validate validates the options provided by the core. -func validate(rawOptions configmanager.RawOptions) (configmanager.RawOptions, error) { - opts := new(options) - if err := util.DecodePlugin(rawOptions, opts); err != nil { - return nil, err - } - // check struct data - if err := validator.CheckStructError(opts).Combine(); err != nil { - return nil, err - } - - // check repo is valid - if opts.ProjectRepo.RepoType != "gitlab" { - return nil, fmt.Errorf("gitlab ci only support gitlab repo") - } - return rawOptions, nil -} - -func setDefault(rawOptions configmanager.RawOptions) (configmanager.RawOptions, error) { - opts := new(options) - if err := util.DecodePlugin(rawOptions, opts); err != nil { - return nil, err - } - // set default value of pipeline location - if opts.Pipeline.ConfigLocation == "" { - opts.Pipeline.ConfigLocation = "https://raw.githubusercontent.com/devstream-io/dtm-pipeline-templates/main/gitlab-ci/.gitlab-ci.yml" - } - opts.CIFileConfig = opts.Pipeline.BuildCIFileConfig(server.CIGitLabType, opts.ProjectRepo) - return mapz.DecodeStructToMap(opts) -} diff --git a/internal/pkg/plugin/harbordocker/create.go b/internal/pkg/plugin/harbordocker/create.go deleted file mode 100644 index 72420f2d9..000000000 --- a/internal/pkg/plugin/harbordocker/create.go +++ /dev/null @@ -1,30 +0,0 @@ -package harbordocker - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - dockerInstaller "github.com/devstream-io/devstream/internal/pkg/plugin/installer/docker" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" -) - -func Create(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - // Initialize Operator with Operations - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - renderConfig, - }, - ExecuteOperations: installer.ExecuteOperations{ - Install, - }, - GetStatusOperation: dockerInstaller.ComposeStatus, - } - - // Execute all Operations in Operator - state, err := operator.Execute(options) - if err != nil { - return nil, err - } - log.Debugf("Return map: %v", state) - return state, nil -} diff --git a/internal/pkg/plugin/harbordocker/delete.go b/internal/pkg/plugin/harbordocker/delete.go deleted file mode 100644 index 092231ac8..000000000 --- a/internal/pkg/plugin/harbordocker/delete.go +++ /dev/null @@ -1,27 +0,0 @@ -package harbordocker - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - dockerInstaller "github.com/devstream-io/devstream/internal/pkg/plugin/installer/docker" -) - -func Delete(options configmanager.RawOptions) (bool, error) { - // Initialize Operator with Operations - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - renderConfig, - }, - ExecuteOperations: installer.ExecuteOperations{ - dockerInstaller.ComposeDown, - }, - GetStatusOperation: dockerInstaller.ComposeStatus, - } - - // Execute all Operations in Operator - _, err := operator.Execute(options) - if err != nil { - return false, err - } - return true, nil -} diff --git a/internal/pkg/plugin/harbordocker/harbordocker.go b/internal/pkg/plugin/harbordocker/harbordocker.go deleted file mode 100644 index cb15defdf..000000000 --- a/internal/pkg/plugin/harbordocker/harbordocker.go +++ /dev/null @@ -1,93 +0,0 @@ -package harbordocker - -import ( - _ "embed" - "os" - - "github.com/mitchellh/mapstructure" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/pkg/util/docker/dockersh" - "github.com/devstream-io/devstream/pkg/util/log" - "github.com/devstream-io/devstream/pkg/util/template" -) - -const ( - HarborConfigFileName = "harbor.yml" - HarborScriptInstallFileName = "install.sh" - HarborScriptCommonFileName = "common.sh" - HarborScriptPrepareFileName = "prepare" -) - -//go:embed sh/harbor.tmpl.yml -var HarborConfigTemplate string - -//go:embed sh/install.sh -var ScriptInstall string - -//go:embed sh/common.sh -var ScriptCommon string - -//go:embed sh/prepare -var ScriptPrepare string - -var scripts = map[string]string{ - HarborScriptInstallFileName: ScriptInstall, - HarborScriptCommonFileName: ScriptCommon, - HarborScriptPrepareFileName: ScriptPrepare, -} - -func Install(options configmanager.RawOptions) error { - if err := writeScripts(); err != nil { - return err - } - - // TODO(daniel-hutao): refactor is needed - err := dockersh.ExecInSystemWithParams(".", []string{"./" + HarborScriptInstallFileName}, nil, true) - if err != nil { - return err - } - return nil -} - -// renderConfig will render HarborConfigTemplate and then write it to disk. -func renderConfig(options configmanager.RawOptions) (configmanager.RawOptions, error) { - opts := Options{} - if err := mapstructure.Decode(options, &opts); err != nil { - return nil, err - } - - content, err := template.NewRenderClient(&template.TemplateOption{ - Name: "docker-compose", - }, template.ContentGetter).Render(HarborConfigTemplate, opts) - if err != nil { - return nil, err - } - - configFile, err := os.Create(HarborConfigFileName) - if err != nil { - return nil, err - } - - defer func() { - if err := configFile.Close(); err != nil { - log.Errorf("Failed to close opened file (%s): %s.", configFile.Name(), err) - } - }() - - if _, err := configFile.Write([]byte(content)); err != nil { - return nil, err - } - - return options, err -} - -func writeScripts() error { - for name, sh := range scripts { - err := os.WriteFile(name, []byte(sh), 0744) - if err != nil { - return err - } - } - return nil -} diff --git a/internal/pkg/plugin/harbordocker/options.go b/internal/pkg/plugin/harbordocker/options.go deleted file mode 100644 index 7f8c76e92..000000000 --- a/internal/pkg/plugin/harbordocker/options.go +++ /dev/null @@ -1,7 +0,0 @@ -package harbordocker - -// Options is the struct for configurations of the harbor-docker plugin. -type Options struct { - // TODO(daniel-hutao): add more options here asap - Hostname string `validate:"hostname" mapstructure:"hostname"` -} diff --git a/internal/pkg/plugin/harbordocker/read.go b/internal/pkg/plugin/harbordocker/read.go deleted file mode 100644 index 7600e2571..000000000 --- a/internal/pkg/plugin/harbordocker/read.go +++ /dev/null @@ -1,27 +0,0 @@ -package harbordocker - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - dockerInstaller "github.com/devstream-io/devstream/internal/pkg/plugin/installer/docker" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" -) - -func Read(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - // Initialize Operator with Operations - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - renderConfig, - }, - GetStatusOperation: dockerInstaller.ComposeStatus, - } - - // Execute all Operations in Operator - state, err := operator.Execute(options) - if err != nil { - return nil, err - } - log.Debugf("Return map: %v", state) - return state, nil -} diff --git a/internal/pkg/plugin/harbordocker/sh/LICENSE b/internal/pkg/plugin/harbordocker/sh/LICENSE deleted file mode 100644 index 4b9cffeef..000000000 --- a/internal/pkg/plugin/harbordocker/sh/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright Project Harbor Authors - - 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/internal/pkg/plugin/harbordocker/sh/common.sh b/internal/pkg/plugin/harbordocker/sh/common.sh deleted file mode 100644 index 871cd77bb..000000000 --- a/internal/pkg/plugin/harbordocker/sh/common.sh +++ /dev/null @@ -1,130 +0,0 @@ -#!/bin/bash -#docker version: 17.06.0+ -#docker-compose version: 1.18.0+ -#golang version: 1.12.0+ - -set +e -set -o noglob - -# -# Set Colors -# - -bold=$(tput bold) -underline=$(tput sgr 0 1) -reset=$(tput sgr0) - -red=$(tput setaf 1) -green=$(tput setaf 76) -white=$(tput setaf 7) -tan=$(tput setaf 202) -blue=$(tput setaf 25) - -# -# Headers and Logging -# - -underline() { printf "${underline}${bold}%s${reset}\n" "$@" -} -h1() { printf "\n${underline}${bold}${blue}%s${reset}\n" "$@" -} -h2() { printf "\n${underline}${bold}${white}%s${reset}\n" "$@" -} -debug() { printf "${white}%s${reset}\n" "$@" -} -info() { printf "${white}➜ %s${reset}\n" "$@" -} -success() { printf "${green}✔ %s${reset}\n" "$@" -} -error() { printf "${red}✖ %s${reset}\n" "$@" -} -warn() { printf "${tan}➜ %s${reset}\n" "$@" -} -bold() { printf "${bold}%s${reset}\n" "$@" -} -note() { printf "\n${underline}${bold}${blue}Note:${reset} ${blue}%s${reset}\n" "$@" -} - -set -e - -function check_golang { - if ! go version &> /dev/null - then - warn "No golang package in your enviroment. You should use golang docker image build binary." - return - fi - - # docker has been installed and check its version - if [[ $(go version) =~ (([0-9]+)\.([0-9]+)([\.0-9]*)) ]] - then - golang_version=${BASH_REMATCH[1]} - golang_version_part1=${BASH_REMATCH[2]} - golang_version_part2=${BASH_REMATCH[3]} - - # the version of golang does not meet the requirement - if [ "$golang_version_part1" -lt 1 ] || ([ "$golang_version_part1" -eq 1 ] && [ "$golang_version_part2" -lt 12 ]) - then - warn "Better to upgrade golang package to 1.12.0+ or use golang docker image build binary." - return - else - note "golang version: $golang_version" - fi - else - warn "Failed to parse golang version." - return - fi -} - -function check_docker { - if ! docker --version &> /dev/null - then - error "Need to install docker(17.06.0+) first and run this script again." - exit 1 - fi - - # docker has been installed and check its version - if [[ $(docker --version) =~ (([0-9]+)\.([0-9]+)([\.0-9]*)) ]] - then - docker_version=${BASH_REMATCH[1]} - docker_version_part1=${BASH_REMATCH[2]} - docker_version_part2=${BASH_REMATCH[3]} - - note "docker version: $docker_version" - # the version of docker does not meet the requirement - if [ "$docker_version_part1" -lt 17 ] || ([ "$docker_version_part1" -eq 17 ] && [ "$docker_version_part2" -lt 6 ]) - then - error "Need to upgrade docker package to 17.06.0+." - exit 1 - fi - else - error "Failed to parse docker version." - exit 1 - fi -} - -function check_dockercompose { - if ! docker-compose --version &> /dev/null - then - error "Need to install docker-compose(1.18.0+) by yourself first and run this script again." - exit 1 - fi - - # docker-compose has been installed, check its version - if [[ $(docker-compose --version) =~ (([0-9]+)\.([0-9]+)([\.0-9]*)) ]] - then - docker_compose_version=${BASH_REMATCH[1]} - docker_compose_version_part1=${BASH_REMATCH[2]} - docker_compose_version_part2=${BASH_REMATCH[3]} - - note "docker-compose version: $docker_compose_version" - # the version of docker-compose does not meet the requirement - if [ "$docker_compose_version_part1" -lt 1 ] || ([ "$docker_compose_version_part1" -eq 1 ] && [ "$docker_compose_version_part2" -lt 18 ]) - then - error "Need to upgrade docker-compose package to 1.18.0+." - exit 1 - fi - else - error "Failed to parse docker-compose version." - exit 1 - fi -} diff --git a/internal/pkg/plugin/harbordocker/sh/harbor.tmpl.yml b/internal/pkg/plugin/harbordocker/sh/harbor.tmpl.yml deleted file mode 100644 index b6cdbb3f2..000000000 --- a/internal/pkg/plugin/harbordocker/sh/harbor.tmpl.yml +++ /dev/null @@ -1,238 +0,0 @@ -# Configuration file of Harbor - -# The IP address or hostname to access admin UI and registry service. -# DO NOT use localhost or 127.0.0.1, because Harbor needs to be accessed by external clients. -hostname: "[[ .Hostname ]]" - -# http related config -http: - # port for http, default is 80. If https enabled, this port will redirect to https port - port: 80 - -## https related config -#https: -# # https port for harbor, default is 443 -# port: 443 -# # The path of cert and key files for nginx -# certificate: /your/certificate/path -# private_key: /your/private/key/path - -# # Uncomment following will enable tls communication between all harbor components -# internal_tls: -# # set enabled to true means internal tls is enabled -# enabled: true -# # put your cert and key files on dir -# dir: /etc/harbor/tls/internal - -# Uncomment external_url if you want to enable external proxy -# And when it enabled the hostname will no longer used -# external_url: https://reg.mydomain.com:8433 - -# The initial password of Harbor admin -# It only works in first time to install harbor -# Remember Change the admin password from UI after launching Harbor. -harbor_admin_password: Harbor12345 - -# Harbor DB configuration -database: - # The password for the root user of Harbor DB. Change this before any production use. - password: root123 - # The maximum number of connections in the idle connection pool. If it <=0, no idle connections are retained. - max_idle_conns: 100 - # The maximum number of open connections to the database. If it <= 0, then there is no limit on the number of open connections. - # Note: the default number of connections is 1024 for postgres of harbor. - max_open_conns: 900 - -# The default data volume -data_volume: /data - -# Harbor Storage settings by default is using /data dir on local filesystem -# Uncomment storage_service setting If you want to using external storage -# storage_service: -# # ca_bundle is the path to the custom root ca certificate, which will be injected into the truststore -# # of registry's and chart repository's containers. This is usually needed when the user hosts a internal storage with self signed certificate. -# ca_bundle: - -# # storage backend, default is filesystem, options include filesystem, azure, gcs, s3, swift and oss -# # for more info about this configuration please refer https://docs.docker.com/registry/configuration/ -# filesystem: -# maxthreads: 100 -# # set disable to true when you want to disable registry redirect -# redirect: -# disabled: false - -# Trivy configuration -# -# Trivy DB contains vulnerability information from NVD, Red Hat, and many other upstream vulnerability databases. -# It is downloaded by Trivy from the GitHub release page https://github.com/aquasecurity/trivy-db/releases and cached -# in the local file system. In addition, the database contains the update timestamp so Trivy can detect whether it -# should download a newer version from the Internet or use the cached one. Currently, the database is updated every -# 12 hours and published as a new release to GitHub. -trivy: - # ignoreUnfixed The flag to display only fixed vulnerabilities - ignore_unfixed: false - # skipUpdate The flag to enable or disable Trivy DB downloads from GitHub - # - # You might want to enable this flag in test or CI/CD environments to avoid GitHub rate limiting issues. - # If the flag is enabled you have to download the `trivy-offline.tar.gz` archive manually, extract `trivy.db` and - # `metadata.json` files and mount them in the `/home/scanner/.cache/trivy/db` path. - skip_update: false - # - # The offline_scan option prevents Trivy from sending API requests to identify dependencies. - # Scanning JAR files and pom.xml may require Internet access for better detection, but this option tries to avoid it. - # For example, the offline mode will not try to resolve transitive dependencies in pom.xml when the dependency doesn't - # exist in the local repositories. It means a number of detected vulnerabilities might be fewer in offline mode. - # It would work if all the dependencies are in local. - # This option doesn’t affect DB download. You need to specify "skip-update" as well as "offline-scan" in an air-gapped environment. - offline_scan: false - # - # insecure The flag to skip verifying registry certificate - insecure: false - # github_token The GitHub access token to download Trivy DB - # - # Anonymous downloads from GitHub are subject to the limit of 60 requests per hour. Normally such rate limit is enough - # for production operations. If, for any reason, it's not enough, you could increase the rate limit to 5000 - # requests per hour by specifying the GitHub access token. For more details on GitHub rate limiting please consult - # https://developer.github.com/v3/#rate-limiting - # - # You can create a GitHub token by following the instructions in - # https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line - # - # github_token: xxx - -jobservice: - # Maximum number of job workers in job service - max_job_workers: 10 - -notification: - # Maximum retry count for webhook job - webhook_job_max_retry: 10 - -chart: - # Change the value of absolute_url to enabled can enable absolute url in chart - absolute_url: disabled - -# Log configurations -log: - # options are debug, info, warning, error, fatal - level: info - # configs for logs in local storage - local: - # Log files are rotated log_rotate_count times before being removed. If count is 0, old versions are removed rather than rotated. - rotate_count: 50 - # Log files are rotated only if they grow bigger than log_rotate_size bytes. If size is followed by k, the size is assumed to be in kilobytes. - # If the M is used, the size is in megabytes, and if G is used, the size is in gigabytes. So size 100, size 100k, size 100M and size 100G - # are all valid. - rotate_size: 200M - # The directory on your host that store log - location: /var/log/harbor - - # Uncomment following lines to enable external syslog endpoint. - # external_endpoint: - # # protocol used to transmit log to external endpoint, options is tcp or udp - # protocol: tcp - # # The host of external endpoint - # host: localhost - # # Port of external endpoint - # port: 5140 - -#This attribute is for migrator to detect the version of the .cfg file, DO NOT MODIFY! -_version: 2.4.0 - -# Uncomment external_database if using external database. -# external_database: -# harbor: -# host: harbor_db_host -# port: harbor_db_port -# db_name: harbor_db_name -# username: harbor_db_username -# password: harbor_db_password -# ssl_mode: disable -# max_idle_conns: 2 -# max_open_conns: 0 -# notary_signer: -# host: notary_signer_db_host -# port: notary_signer_db_port -# db_name: notary_signer_db_name -# username: notary_signer_db_username -# password: notary_signer_db_password -# ssl_mode: disable -# notary_server: -# host: notary_server_db_host -# port: notary_server_db_port -# db_name: notary_server_db_name -# username: notary_server_db_username -# password: notary_server_db_password -# ssl_mode: disable - -# Uncomment external_redis if using external Redis server -# external_redis: -# # support redis, redis+sentinel -# # host for redis: : -# # host for redis+sentinel: -# # :,:,: -# host: redis:6379 -# password: -# # sentinel_master_set must be set to support redis+sentinel -# #sentinel_master_set: -# # db_index 0 is for core, it's unchangeable -# registry_db_index: 1 -# jobservice_db_index: 2 -# chartmuseum_db_index: 3 -# trivy_db_index: 5 -# idle_timeout_seconds: 30 - -# Uncomment uaa for trusting the certificate of uaa instance that is hosted via self-signed cert. -# uaa: -# ca_file: /path/to/ca - -# Global proxy -# Config http proxy for components, e.g. http://my.proxy.com:3128 -# Components doesn't need to connect to each others via http proxy. -# Remove component from `components` array if want disable proxy -# for it. If you want use proxy for replication, MUST enable proxy -# for core and jobservice, and set `http_proxy` and `https_proxy`. -# Add domain to the `no_proxy` field, when you want disable proxy -# for some special registry. -proxy: - http_proxy: - https_proxy: - no_proxy: - components: - - core - - jobservice - - trivy - -# metric: -# enabled: false -# port: 9090 -# path: /metrics - -# Trace related config -# only can enable one trace provider(jaeger or otel) at the same time, -# and when using jaeger as provider, can only enable it with agent mode or collector mode. -# if using jaeger collector mode, uncomment endpoint and uncomment username, password if needed -# if using jaeger agetn mode uncomment agent_host and agent_port -# trace: -# enabled: true -# # set sample_rate to 1 if you wanna sampling 100% of trace data; set 0.5 if you wanna sampling 50% of trace data, and so forth -# sample_rate: 1 -# # # namespace used to differenciate different harbor services -# # namespace: -# # # attributes is a key value dict contains user defined attributes used to initialize trace provider -# # attributes: -# # application: harbor -# # # jaeger should be 1.26 or newer. -# # jaeger: -# # endpoint: http://hostname:14268/api/traces -# # username: -# # password: -# # agent_host: hostname -# # # export trace data by jaeger.thrift in compact mode -# # agent_port: 6831 -# # otel: -# # endpoint: hostname:4318 -# # url_path: /v1/traces -# # compression: false -# # insecure: true -# # timeout: 10s diff --git a/internal/pkg/plugin/harbordocker/sh/install.sh b/internal/pkg/plugin/harbordocker/sh/install.sh deleted file mode 100755 index 1cc2070d3..000000000 --- a/internal/pkg/plugin/harbordocker/sh/install.sh +++ /dev/null @@ -1,101 +0,0 @@ -#!/bin/bash - -set -e - -DIR="$(cd "$(dirname "$0")" && pwd)" -source $DIR/common.sh - -set +o noglob - -usage=$'Please set hostname and other necessary attributes in harbor.yml first. DO NOT use localhost or 127.0.0.1 for hostname, because Harbor needs to be accessed by external clients. -Please set --with-notary if needs enable Notary in Harbor, and set ui_url_protocol/ssl_cert/ssl_cert_key in harbor.yml bacause notary must run under https. -Please set --with-trivy if needs enable Trivy in Harbor -Please set --with-chartmuseum if needs enable Chartmuseum in Harbor' -item=0 - -# notary is not enabled by default -with_notary=$false -# clair is deprecated -with_clair=$false -# trivy is not enabled by default -with_trivy=$false -# chartmuseum is not enabled by default -with_chartmuseum=$false - -while [ $# -gt 0 ]; do - case $1 in - --help) - note "$usage" - exit 0;; - --with-notary) - with_notary=true;; - --with-clair) - with_clair=true;; - --with-trivy) - with_trivy=true;; - --with-chartmuseum) - with_chartmuseum=true;; - *) - note "$usage" - exit 1;; - esac - shift || true -done - -if [ $with_clair ] -then - error "Clair is deprecated please remove it from installation arguments !!!" - exit 1 -fi - -workdir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -cd $workdir - -h2 "[Step $item]: checking if docker is installed ..."; let item+=1 -check_docker - -h2 "[Step $item]: checking docker-compose is installed ..."; let item+=1 -check_dockercompose - -if [ -f harbor*.tar.gz ] -then - h2 "[Step $item]: loading Harbor images ..."; let item+=1 - docker load -i ./harbor*.tar.gz -fi -echo "" - -h2 "[Step $item]: preparing environment ..."; let item+=1 -if [ -n "$host" ] -then - sed "s/^hostname: .*/hostname: $host/g" -i ./harbor.yml -fi - -h2 "[Step $item]: preparing harbor configs ..."; let item+=1 -prepare_para= -if [ $with_notary ] -then - prepare_para="${prepare_para} --with-notary" -fi -if [ $with_trivy ] -then - prepare_para="${prepare_para} --with-trivy" -fi -if [ $with_chartmuseum ] -then - prepare_para="${prepare_para} --with-chartmuseum" -fi - -./prepare $prepare_para -echo "" - -if [ -n "$(docker-compose ps -q)" ] -then - note "stopping existing Harbor instance ..." - docker-compose down -v -fi -echo "" - -h2 "[Step $item]: starting Harbor ..." -docker-compose up -d - -success $"----Harbor has been installed and started successfully.----" diff --git a/internal/pkg/plugin/harbordocker/sh/prepare b/internal/pkg/plugin/harbordocker/sh/prepare deleted file mode 100755 index 584614719..000000000 --- a/internal/pkg/plugin/harbordocker/sh/prepare +++ /dev/null @@ -1,64 +0,0 @@ -#!/bin/bash -set -e - -# If compiling source code this dir is harbor's make dir. -# If installing harbor via package, this dir is harbor's root dir. -if [[ -n "$HARBOR_BUNDLE_DIR" ]]; then - harbor_prepare_path=$HARBOR_BUNDLE_DIR -else - harbor_prepare_path="$( cd "$(dirname "$0")" ; pwd -P )" -fi -echo "prepare base dir is set to ${harbor_prepare_path}" - -# Clean up input dir -rm -rf ${harbor_prepare_path}/input -# Create a input dirs -mkdir -p ${harbor_prepare_path}/input -input_dir=${harbor_prepare_path}/input - -# Copy harbor.yml to input dir -if [[ ! "$1" =~ ^\-\- ]] && [ -f "$1" ] -then - cp $1 $input_dir/harbor.yml - shift -else - if [ -f "${harbor_prepare_path}/harbor.yml" ];then - cp ${harbor_prepare_path}/harbor.yml $input_dir/harbor.yml - else - echo "no config file: ${harbor_prepare_path}/harbor.yml" - exit 1 - fi -fi - -data_path=$(grep '^[^#]*data_volume:' $input_dir/harbor.yml | awk '{print $NF}') - -# If previous secretkeys exist, move it to new location -previous_secretkey_path=/data/secretkey -previous_defaultalias_path=/data/defaultalias - -if [ -f $previous_secretkey_path ]; then - mkdir -p $data_path/secret/keys - mv $previous_secretkey_path $data_path/secret/keys -fi -if [ -f $previous_defaultalias_path ]; then - mkdir -p $data_path/secret/keys - mv $previous_defaultalias_path $data_path/secret/keys -fi - - -# Create secret dir -secret_dir=${data_path}/secret -config_dir=$harbor_prepare_path/common/config - -# Run prepare script -docker run --rm -v $input_dir:/input \ - -v $data_path:/data \ - -v $harbor_prepare_path:/compose_location \ - -v $config_dir:/config \ - -v /:/hostfs \ - --privileged \ - goharbor/prepare:v2.4.3 prepare $@ - -echo "Clean up the input dir" -# Clean up input dir -rm -rf ${harbor_prepare_path}/input diff --git a/internal/pkg/plugin/harbordocker/update.go b/internal/pkg/plugin/harbordocker/update.go deleted file mode 100644 index 9837a2154..000000000 --- a/internal/pkg/plugin/harbordocker/update.go +++ /dev/null @@ -1,31 +0,0 @@ -package harbordocker - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - dockerInstaller "github.com/devstream-io/devstream/internal/pkg/plugin/installer/docker" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" -) - -func Update(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - // Initialize Operator with Operations - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - renderConfig, - }, - ExecuteOperations: installer.ExecuteOperations{ - dockerInstaller.ComposeDown, - dockerInstaller.ComposeUp, - }, - GetStatusOperation: dockerInstaller.ComposeStatus, - } - - // Execute all Operations in Operator - state, err := operator.Execute(options) - if err != nil { - return nil, err - } - log.Debugf("Return map: %v", state) - return state, nil -} diff --git a/internal/pkg/plugin/harbordocker/validate.go b/internal/pkg/plugin/harbordocker/validate.go deleted file mode 100644 index 0838aecf4..000000000 --- a/internal/pkg/plugin/harbordocker/validate.go +++ /dev/null @@ -1 +0,0 @@ -package harbordocker diff --git a/internal/pkg/plugin/helminstaller/create.go b/internal/pkg/plugin/helminstaller/create.go deleted file mode 100644 index 3efd9b072..000000000 --- a/internal/pkg/plugin/helminstaller/create.go +++ /dev/null @@ -1,31 +0,0 @@ -package helminstaller - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/helm" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" -) - -func Create(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - // Initialize Operator with Operations - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - renderDefaultConfig, - renderValuesYaml, - validate, - }, - ExecuteOperations: helm.DefaultCreateOperations, - TerminateOperations: helm.DefaultTerminateOperations, - GetStatusOperation: indexStatusGetterFunc(options), - } - - // Execute all Operations in Operator - status, err := operator.Execute(options) - if err != nil { - return nil, err - } - log.Debugf("Return map: %v", status) - return status, nil -} diff --git a/internal/pkg/plugin/helminstaller/defaults/argocd.go b/internal/pkg/plugin/helminstaller/defaults/argocd.go deleted file mode 100644 index 43dda739d..000000000 --- a/internal/pkg/plugin/helminstaller/defaults/argocd.go +++ /dev/null @@ -1,36 +0,0 @@ -package defaults - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/helm" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - helmCommon "github.com/devstream-io/devstream/pkg/util/helm" - "github.com/devstream-io/devstream/pkg/util/types" -) - -const toolArgoCD = "argocd" - -var DefaultConfigWithArgoCD = helm.Options{ - Chart: helmCommon.Chart{ - ChartPath: "", - ChartName: "argo/argo-cd", - Version: "", - Timeout: "10m", - Wait: types.Bool(true), - UpgradeCRDs: types.Bool(true), - ReleaseName: "argocd", - Namespace: "argocd", - }, - Repo: helmCommon.Repo{ - URL: "https://argoproj.github.io/argo-helm", - Name: "argo", - }, -} - -func init() { - RegisterDefaultHelmAppInstance(toolArgoCD, &DefaultConfigWithArgoCD, GetArgoCDStatus) -} - -func GetArgoCDStatus(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return helm.GetAllResourcesStatus(options) -} diff --git a/internal/pkg/plugin/helminstaller/defaults/artifactory.go b/internal/pkg/plugin/helminstaller/defaults/artifactory.go deleted file mode 100644 index d2bc2c18f..000000000 --- a/internal/pkg/plugin/helminstaller/defaults/artifactory.go +++ /dev/null @@ -1,36 +0,0 @@ -package defaults - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/helm" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - helmCommon "github.com/devstream-io/devstream/pkg/util/helm" - "github.com/devstream-io/devstream/pkg/util/types" -) - -const toolArtifactory = "artifactory" - -var DefaultConfigWithArtifactory = helm.Options{ - Chart: helmCommon.Chart{ - ChartPath: "", - ChartName: "jfrog/artifactory", - Version: "", - Timeout: "10m", - UpgradeCRDs: types.Bool(true), - Wait: types.Bool(true), - ReleaseName: "artifactory", - Namespace: "artifactory", - }, - Repo: helmCommon.Repo{ - URL: "https://charts.jfrog.io", - Name: "jfrog", - }, -} - -func init() { - RegisterDefaultHelmAppInstance(toolArtifactory, &DefaultConfigWithArtifactory, GetArtifactoryStatus) -} - -func GetArtifactoryStatus(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return helm.GetAllResourcesStatus(options) -} diff --git a/internal/pkg/plugin/helminstaller/defaults/defaults_suite_test.go b/internal/pkg/plugin/helminstaller/defaults/defaults_suite_test.go deleted file mode 100644 index bb2d29aac..000000000 --- a/internal/pkg/plugin/helminstaller/defaults/defaults_suite_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package defaults_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestDefaults(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Defaults Suite") -} diff --git a/internal/pkg/plugin/helminstaller/defaults/devlake.go b/internal/pkg/plugin/helminstaller/defaults/devlake.go deleted file mode 100644 index b5d769f41..000000000 --- a/internal/pkg/plugin/helminstaller/defaults/devlake.go +++ /dev/null @@ -1,83 +0,0 @@ -package defaults - -import ( - "fmt" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/helm" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - helmCommon "github.com/devstream-io/devstream/pkg/util/helm" - "github.com/devstream-io/devstream/pkg/util/k8s" - "github.com/devstream-io/devstream/pkg/util/types" -) - -const ( - DevLakeSvcName = "devlake-lake" - toolDevLake = "devlake" -) - -var DefaultConfigWithDevLake = helm.Options{ - Chart: helmCommon.Chart{ - ChartPath: "", - ChartName: "devlake/devlake", - Version: "", - Timeout: "10m", - Wait: types.Bool(true), - UpgradeCRDs: types.Bool(true), - ReleaseName: "devlake", - Namespace: "devlake", - }, - Repo: helmCommon.Repo{ - URL: "https://apache.github.io/incubator-devlake-helm-chart", - Name: "devlake", - }, -} - -func init() { - RegisterDefaultHelmAppInstance(toolDevLake, &DefaultConfigWithDevLake, GetDevLakeStatus) -} - -func GetDevLakeStatus(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - resStatus, err := helm.GetAllResourcesStatus(options) - if err != nil { - return nil, err - } - - // values.yaml - opt, err := helm.NewOptions(options) - if err != nil { - return nil, err - } - valuesYaml := opt.GetHelmParam().Chart.ValuesYaml - resStatus["valuesYaml"] = valuesYaml - - // TODO(daniel-hutao): Use Ingress later. - ip, err := getDevLakeClusterIP(opt.Chart.Namespace, DevLakeSvcName) - if err != nil { - return nil, err - } - url := fmt.Sprintf("http://%s:8080", ip) - outputs := statemanager.ResourceOutputs{ - "devlake_url": url, - } - resStatus.SetOutputs(outputs) - - return resStatus, nil -} - -func getDevLakeClusterIP(namespace, name string) (string, error) { - kClient, err := k8s.NewClient() - if err != nil { - return "", err - } - - svc, err := kClient.GetService(namespace, name) - if err != nil { - return "", err - } - - if svc.Spec.ClusterIP == "" { - return "", fmt.Errorf("cluster ip is empty") - } - return svc.Spec.ClusterIP, nil -} diff --git a/internal/pkg/plugin/helminstaller/defaults/harbor.go b/internal/pkg/plugin/helminstaller/defaults/harbor.go deleted file mode 100644 index 663bb0f30..000000000 --- a/internal/pkg/plugin/helminstaller/defaults/harbor.go +++ /dev/null @@ -1,36 +0,0 @@ -package defaults - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/helm" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - helmCommon "github.com/devstream-io/devstream/pkg/util/helm" - "github.com/devstream-io/devstream/pkg/util/types" -) - -const toolHarbor = "harbor" - -var DefaultConfigWithHarbor = helm.Options{ - Chart: helmCommon.Chart{ - ChartPath: "", - ChartName: "harbor/harbor", - Version: "", - Timeout: "10m", - UpgradeCRDs: types.Bool(true), - Wait: types.Bool(true), - ReleaseName: "harbor", - Namespace: "harbor", - }, - Repo: helmCommon.Repo{ - URL: "https://helm.goharbor.io", - Name: "harbor", - }, -} - -func init() { - RegisterDefaultHelmAppInstance(toolHarbor, &DefaultConfigWithHarbor, GetHarborStatus) -} - -func GetHarborStatus(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return helm.GetAllResourcesStatus(options) -} diff --git a/internal/pkg/plugin/helminstaller/defaults/instance.go b/internal/pkg/plugin/helminstaller/defaults/instance.go deleted file mode 100644 index cfa6d9b4f..000000000 --- a/internal/pkg/plugin/helminstaller/defaults/instance.go +++ /dev/null @@ -1,52 +0,0 @@ -package defaults - -import ( - "strings" - "sync" - - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/helm" -) - -type HelmAppInstance struct { - Name string - HelmOptions *helm.Options - StatusGetter installer.StatusGetterOperation -} - -var ( - helmAppInstances map[string]*HelmAppInstance - once sync.Once -) - -func RegisterDefaultHelmAppInstance(name string, options *helm.Options, statusGetter installer.StatusGetterOperation) { - // make sure the map is initialized only once - once.Do(func() { - helmAppInstances = make(map[string]*HelmAppInstance) - }) - - helmAppInstances[name] = &HelmAppInstance{ - Name: name, - HelmOptions: options, - StatusGetter: statusGetter, - } -} - -// GetDefaultHelmAppInstanceByInstanceID will return the default helm app instance -// by matching the prefix of given instanceID and the list of helm app instances names. -// It will return the longest matched name if there are multiple matched names, and return nil if no matched name is found. -// e.g. instanceID="argocd-config-001", "argocd" and "argocd-config" both are supported helm charts, -// then the HelmAppInstance named "argocd-config" will be returned. -func GetDefaultHelmAppInstanceByInstanceID(instanceID string) *HelmAppInstance { - longestMatchedName := "" - for curNameToMatch := range helmAppInstances { - if strings.HasPrefix(instanceID, curNameToMatch) && len(curNameToMatch) > len(longestMatchedName) { - longestMatchedName = curNameToMatch - } - } - - if longestMatchedName != "" { - return helmAppInstances[longestMatchedName] - } - return nil -} diff --git a/internal/pkg/plugin/helminstaller/defaults/instance_test.go b/internal/pkg/plugin/helminstaller/defaults/instance_test.go deleted file mode 100644 index f0737c661..000000000 --- a/internal/pkg/plugin/helminstaller/defaults/instance_test.go +++ /dev/null @@ -1,60 +0,0 @@ -package defaults - -import ( - "reflect" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -var _ = Describe("instance test", func() { - var ( - instanceID string - instance *HelmAppInstance - ) - - // main logic to get the default instance - JustBeforeEach(func() { - instance = GetDefaultHelmAppInstanceByInstanceID(instanceID) - }) - - Context("GetDefaultHelmAppInstanceByInstanceID", func() { - When("instanceID is not exists", func() { - BeforeEach(func() { - instanceID = "not-exists" - }) - It("should return nil", func() { - // main logic is in JustBeforeEach - Expect(instance).To(BeNil()) - }) - }) - - When("instanceID is exists(argocd)", func() { - JustAfterEach(func() { - Expect(instance).NotTo(BeNil()) - Expect(instance.Name).To(Equal("argocd")) - Expect(*instance.HelmOptions).To(Equal(DefaultConfigWithArgoCD)) - pointer1 := reflect.ValueOf(instance.StatusGetter).Pointer() - pointer2 := reflect.ValueOf(GetArgoCDStatus).Pointer() - Expect(pointer1).To(Equal(pointer2)) - }) - When(`instanceID is the same as "argocd"`, func() { - BeforeEach(func() { - instanceID = "argocd" - }) - It("should return instance", func() { - // main logic is in JustAfterEach - }) - }) - - When(`instanceID has prefix "argocd"`, func() { - BeforeEach(func() { - instanceID = "argocd-001" - }) - It("should return instance", func() { - // main logic is in JustAfterEach - }) - }) - }) - }) -}) diff --git a/internal/pkg/plugin/helminstaller/defaults/jenkins.go b/internal/pkg/plugin/helminstaller/defaults/jenkins.go deleted file mode 100644 index 2d9554538..000000000 --- a/internal/pkg/plugin/helminstaller/defaults/jenkins.go +++ /dev/null @@ -1,90 +0,0 @@ -package defaults - -import ( - "fmt" - "strings" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/helm" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - helmCommon "github.com/devstream-io/devstream/pkg/util/helm" - "github.com/devstream-io/devstream/pkg/util/types" -) - -const toolJenkins = "jenkins" - -var DefaultConfigWithJenkins = helm.Options{ - Chart: helmCommon.Chart{ - ChartPath: "", - ChartName: "jenkins/jenkins", - Version: "", - Timeout: "10m", - UpgradeCRDs: types.Bool(true), - Wait: types.Bool(true), - ReleaseName: "jenkins", - Namespace: "jenkins", - }, - Repo: helmCommon.Repo{ - URL: "https://charts.jenkins.io", - Name: "jenkins", - }, -} - -func init() { - RegisterDefaultHelmAppInstance(toolJenkins, &DefaultConfigWithJenkins, GetJenkinsStatus) -} - -func GetJenkinsStatus(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - resStatus, err := helm.GetAllResourcesStatus(options) - if err != nil { - return nil, err - } - - // values.yaml - opt, err := helm.NewOptions(options) - if err != nil { - return nil, err - } - valuesYaml := opt.GetHelmParam().Chart.ValuesYaml - resStatus["valuesYaml"] = valuesYaml - - svcName, err := genJenkinsSvcName(options) - if err != nil { - return nil, err - } - - // svc_name.svc_ns:svc_port - url := fmt.Sprintf("http://%s.%s:8080", svcName, opt.Chart.Namespace) - outputs := statemanager.ResourceOutputs{ - "jenkins_url": url, - } - - resStatus.SetOutputs(outputs) - - return resStatus, nil -} - -// See https://github.com/devstream-io/devstream/pull/1025#discussion_r952277174 and -// https://github.com/devstream-io/devstream/pull/1027#discussion_r953415932 for more info. -func genJenkinsSvcName(options configmanager.RawOptions) (string, error) { - opts, err := helm.NewOptions(options) - if err != nil { - return "", err - } - - pipe := func(s string) string { - if len(s) > 63 { - s = s[:63] - } - return strings.TrimSuffix(s, "-") - } - - var tmpName string - if strings.Contains(opts.Chart.ChartName, opts.Chart.ReleaseName) { - tmpName = opts.Chart.ReleaseName - } else { - tmpName = fmt.Sprintf("%s-%s", opts.Chart.ReleaseName, opts.Chart.ChartName) - } - - return pipe(tmpName), nil -} diff --git a/internal/pkg/plugin/helminstaller/defaults/kubeprometheus.go b/internal/pkg/plugin/helminstaller/defaults/kubeprometheus.go deleted file mode 100644 index 6eb3294cf..000000000 --- a/internal/pkg/plugin/helminstaller/defaults/kubeprometheus.go +++ /dev/null @@ -1,36 +0,0 @@ -package defaults - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/helm" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - helmCommon "github.com/devstream-io/devstream/pkg/util/helm" - "github.com/devstream-io/devstream/pkg/util/types" -) - -const toolKubePrometheus = "kube-prometheus" - -var DefaultConfigWithKubePrometheus = helm.Options{ - Chart: helmCommon.Chart{ - ChartPath: "", - ChartName: "prometheus-community/kube-prometheus-stack", - Version: "", - Timeout: "10m", - UpgradeCRDs: types.Bool(true), - Wait: types.Bool(true), - ReleaseName: "prometheus", - Namespace: "prometheus", - }, - Repo: helmCommon.Repo{ - URL: "https://prometheus-community.github.io/helm-charts", - Name: "prometheus-community", - }, -} - -func init() { - RegisterDefaultHelmAppInstance(toolKubePrometheus, &DefaultConfigWithKubePrometheus, GetKubePrometheusStatus) -} - -func GetKubePrometheusStatus(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return helm.GetAllResourcesStatus(options) -} diff --git a/internal/pkg/plugin/helminstaller/defaults/openldap.go b/internal/pkg/plugin/helminstaller/defaults/openldap.go deleted file mode 100644 index 75b1f02b3..000000000 --- a/internal/pkg/plugin/helminstaller/defaults/openldap.go +++ /dev/null @@ -1,36 +0,0 @@ -package defaults - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/helm" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - helmCommon "github.com/devstream-io/devstream/pkg/util/helm" - "github.com/devstream-io/devstream/pkg/util/types" -) - -const toolOpenLDAP = "openldap" - -var DefaultConfigWithOpenLDAP = helm.Options{ - Chart: helmCommon.Chart{ - ChartPath: "", - ChartName: "helm-openldap/openldap-stack-ha", - Version: "", - Timeout: "10m", - UpgradeCRDs: types.Bool(true), - Wait: types.Bool(true), - ReleaseName: "openldap", - Namespace: "openldap", - }, - Repo: helmCommon.Repo{ - URL: "https://jp-gouin.github.io/helm-openldap/", - Name: "helm-openldap", - }, -} - -func init() { - RegisterDefaultHelmAppInstance(toolOpenLDAP, &DefaultConfigWithOpenLDAP, GetOpenLDAPStatus) -} - -func GetOpenLDAPStatus(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return helm.GetAllResourcesStatus(options) -} diff --git a/internal/pkg/plugin/helminstaller/defaults/sonarqube.go b/internal/pkg/plugin/helminstaller/defaults/sonarqube.go deleted file mode 100644 index 1384d5d96..000000000 --- a/internal/pkg/plugin/helminstaller/defaults/sonarqube.go +++ /dev/null @@ -1,35 +0,0 @@ -package defaults - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/helm" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - helmCommon "github.com/devstream-io/devstream/pkg/util/helm" - "github.com/devstream-io/devstream/pkg/util/types" -) - -const toolSonarQube = "sonarqube" - -var DefaultConfigWithSonarQube = helm.Options{ - Chart: helmCommon.Chart{ - ChartName: "sonarqube/sonarqube", - Timeout: "10m", - Version: "", - Wait: types.Bool(true), - UpgradeCRDs: types.Bool(true), - ReleaseName: "sonarqube", - Namespace: "sonarqube", - }, - Repo: helmCommon.Repo{ - URL: "https://SonarSource.github.io/helm-chart-sonarqube", - Name: "sonarqube", - }, -} - -func init() { - RegisterDefaultHelmAppInstance(toolSonarQube, &DefaultConfigWithSonarQube, GetSonarQubeStatus) -} - -func GetSonarQubeStatus(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return helm.GetAllResourcesStatus(options) -} diff --git a/internal/pkg/plugin/helminstaller/defaults/tekton.go b/internal/pkg/plugin/helminstaller/defaults/tekton.go deleted file mode 100644 index 5d6243bb5..000000000 --- a/internal/pkg/plugin/helminstaller/defaults/tekton.go +++ /dev/null @@ -1,36 +0,0 @@ -package defaults - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/helm" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - helmCommon "github.com/devstream-io/devstream/pkg/util/helm" - "github.com/devstream-io/devstream/pkg/util/types" -) - -const toolTekton = "tekton" - -var DefaultConfigWithTekton = helm.Options{ - Chart: helmCommon.Chart{ - ChartPath: "", - ChartName: "tekton/tekton-pipeline", - Version: "", - Timeout: "10m", - UpgradeCRDs: types.Bool(true), - Wait: types.Bool(true), - ReleaseName: "tekton", - Namespace: "tekton", - }, - Repo: helmCommon.Repo{ - URL: "https://steinliber.github.io/tekton-helm-chart/", - Name: "tekton", - }, -} - -func init() { - RegisterDefaultHelmAppInstance(toolTekton, &DefaultConfigWithTekton, GetTektonStatus) -} - -func GetTektonStatus(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return helm.GetAllResourcesStatus(options) -} diff --git a/internal/pkg/plugin/helminstaller/defaults/vault.go b/internal/pkg/plugin/helminstaller/defaults/vault.go deleted file mode 100644 index 45b0ca75a..000000000 --- a/internal/pkg/plugin/helminstaller/defaults/vault.go +++ /dev/null @@ -1,36 +0,0 @@ -package defaults - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/helm" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - helmCommon "github.com/devstream-io/devstream/pkg/util/helm" - "github.com/devstream-io/devstream/pkg/util/types" -) - -const toolVault = "vault" - -var DefaultConfigWithVault = helm.Options{ - Chart: helmCommon.Chart{ - ChartPath: "", - ChartName: "hashicorp/vault", - Version: "", - Timeout: "10m", - UpgradeCRDs: types.Bool(true), - Wait: types.Bool(true), - ReleaseName: "vault", - Namespace: "vault", - }, - Repo: helmCommon.Repo{ - URL: "https://helm.releases.hashicorp.com", - Name: "hashicorp", - }, -} - -func init() { - RegisterDefaultHelmAppInstance(toolVault, &DefaultConfigWithVault, GetVaultStatus) -} - -func GetVaultStatus(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return helm.GetAllResourcesStatus(options) -} diff --git a/internal/pkg/plugin/helminstaller/delete.go b/internal/pkg/plugin/helminstaller/delete.go deleted file mode 100644 index bd94193d5..000000000 --- a/internal/pkg/plugin/helminstaller/delete.go +++ /dev/null @@ -1,27 +0,0 @@ -package helminstaller - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/helm" -) - -func Delete(options configmanager.RawOptions) (bool, error) { - // Initialize Operator with Operations - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - renderDefaultConfig, - renderValuesYaml, - validate, - }, - ExecuteOperations: helm.DefaultDeleteOperations, - } - - // Execute all Operations in Operator - _, err := operator.Execute(options) - if err != nil { - return false, err - } - - return true, nil -} diff --git a/internal/pkg/plugin/helminstaller/helminstaller.go b/internal/pkg/plugin/helminstaller/helminstaller.go deleted file mode 100644 index 6763021f3..000000000 --- a/internal/pkg/plugin/helminstaller/helminstaller.go +++ /dev/null @@ -1,82 +0,0 @@ -package helminstaller - -import ( - "os" - "strings" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/helminstaller/defaults" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/helm" - "github.com/devstream-io/devstream/pkg/util/log" - "github.com/devstream-io/devstream/pkg/util/mapz" - "github.com/devstream-io/devstream/pkg/util/types" -) - -func renderDefaultConfig(options configmanager.RawOptions) (configmanager.RawOptions, error) { - helmOptions, err := helm.NewOptions(options) - if err != nil { - return nil, err - } - - instanceID := helmOptions.InstanceID - - defaultIns := defaults.GetDefaultHelmAppInstanceByInstanceID(instanceID) - if defaultIns == nil { - log.Debugf("Default config for %s wasn't found.", instanceID) - return options, nil - } - - log.Infof("Filling default config with instance: %s.", instanceID) - helmOptions.FillDefaultValue(defaultIns.HelmOptions) - log.Debugf("Options with default config filled: %v.", helmOptions) - - return mapz.DecodeStructToMap(helmOptions) -} - -// renderValuesYaml renders options.valuesYaml to options.chart.valuesYaml; -// If options.valuesYaml doesn't contain ":", it should be a path like "./values.yaml", then read it and transfer to options.chart.valuesYaml -func renderValuesYaml(options configmanager.RawOptions) (configmanager.RawOptions, error) { - helmOptions, err := helm.NewOptions(options) - if err != nil { - return nil, err - } - - // 1. valuesYaml isn't set - if helmOptions.ValuesYaml == "" { - return options, nil - } - - // 2. valuesYaml is a YAML string - if strings.Contains(helmOptions.ValuesYaml, ": ") { - helmOptions.Chart.ValuesYaml = helmOptions.ValuesYaml - helmOptions.ValuesYaml = "" - return types.EncodeStruct(helmOptions) - } - - // 3. valuesYaml is a file path - valuesYamlBytes, err := os.ReadFile(helmOptions.ValuesYaml) - if err != nil { - return nil, err - } - helmOptions.Chart.ValuesYaml = string(valuesYamlBytes) - helmOptions.ValuesYaml = "" - - return types.EncodeStruct(helmOptions) -} - -func indexStatusGetterFunc(options configmanager.RawOptions) installer.StatusGetterOperation { - helmOptions, err := helm.NewOptions(options) - if err != nil { - // It's ok to return GetAllResourcesStatus here when err != nil. - return helm.GetAllResourcesStatus - } - - defaultIns := defaults.GetDefaultHelmAppInstanceByInstanceID(helmOptions.InstanceID) - - if defaultIns == nil { - return helm.GetAllResourcesStatus - } - - return defaultIns.StatusGetter -} diff --git a/internal/pkg/plugin/helminstaller/helminstaller_suite_test.go b/internal/pkg/plugin/helminstaller/helminstaller_suite_test.go deleted file mode 100644 index 6f6fcc81f..000000000 --- a/internal/pkg/plugin/helminstaller/helminstaller_suite_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package helminstaller_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestHelmInstaller(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "helminstaller suite") -} diff --git a/internal/pkg/plugin/helminstaller/helminstaller_test.go b/internal/pkg/plugin/helminstaller/helminstaller_test.go deleted file mode 100644 index 8c8bcd3d5..000000000 --- a/internal/pkg/plugin/helminstaller/helminstaller_test.go +++ /dev/null @@ -1,70 +0,0 @@ -package helminstaller - -import ( - "os" - "reflect" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/helminstaller/defaults" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/helm" -) - -var _ = Describe("helm installer test", func() { - - Context("renderDefaultConfig", func() { - opts := configmanager.RawOptions{} - opts["instanceID"] = interface{}("argocd-001") - newOpts, err := renderDefaultConfig(opts) - Expect(err).To(BeNil()) - - helmOpts, err := helm.NewOptions(newOpts) - Expect(err).To(BeNil()) - - Expect(helmOpts.Chart.ChartName).To(Equal(defaults.DefaultConfigWithArgoCD.Chart.ChartName)) - Expect(helmOpts.Repo.URL).To(Equal(defaults.DefaultConfigWithArgoCD.Repo.URL)) - }) - - Context("renderValuesYaml", func() { - It("config with yaml", func() { - opts := configmanager.RawOptions{} - opts["valuesYaml"] = interface{}("foo: bar") - newOpts, err := renderValuesYaml(opts) - Expect(err).To(BeNil()) - - helmOpts, err := helm.NewOptions(newOpts) - Expect(err).To(BeNil()) - - Expect(helmOpts.Chart.ValuesYaml).To(Equal("foo: bar")) - }) - - It("config with file path", func() { - err := os.WriteFile("./values.yaml", []byte("foo: bar"), 0644) - Expect(err).To(BeNil()) - - opts := configmanager.RawOptions{} - opts["valuesYaml"] = interface{}("./values.yaml") - newOpts, err := renderValuesYaml(opts) - Expect(err).To(BeNil()) - - helmOpts, err := helm.NewOptions(newOpts) - Expect(err).To(BeNil()) - - Expect(helmOpts.Chart.ValuesYaml).To(Equal("foo: bar")) - - err = os.RemoveAll("./values.yaml") - Expect(err).To(BeNil()) - }) - }) - - Context("indexStatusGetterFunc", func() { - opts1 := configmanager.RawOptions{ - "instanceID": interface{}("argocd-001"), - } - - fn1 := indexStatusGetterFunc(opts1) - Expect(reflect.ValueOf(fn1).Pointer()).To(Equal(reflect.ValueOf(defaults.GetArgoCDStatus).Pointer())) - }) -}) diff --git a/internal/pkg/plugin/helminstaller/read.go b/internal/pkg/plugin/helminstaller/read.go deleted file mode 100644 index 85f47ce7a..000000000 --- a/internal/pkg/plugin/helminstaller/read.go +++ /dev/null @@ -1,28 +0,0 @@ -package helminstaller - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" -) - -func Read(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - // Initialize Operator with Operations - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - renderDefaultConfig, - renderValuesYaml, - validate, - }, - GetStatusOperation: indexStatusGetterFunc(options), - } - - // Execute all Operations in Operator - status, err := operator.Execute(options) - if err != nil { - return nil, err - } - log.Debugf("Return map: %v", status) - return status, nil -} diff --git a/internal/pkg/plugin/helminstaller/update.go b/internal/pkg/plugin/helminstaller/update.go deleted file mode 100644 index 4cb02586a..000000000 --- a/internal/pkg/plugin/helminstaller/update.go +++ /dev/null @@ -1,31 +0,0 @@ -package helminstaller - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/helm" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" -) - -func Update(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - // Initialize Operator with Operations - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - renderDefaultConfig, - renderValuesYaml, - validate, - }, - ExecuteOperations: helm.DefaultUpdateOperations, - TerminateOperations: helm.DefaultTerminateOperations, - GetStatusOperation: indexStatusGetterFunc(options), - } - - // Execute all Operations in Operator - status, err := operator.Execute(options) - if err != nil { - return nil, err - } - log.Debugf("Return map: %v", status) - return status, nil -} diff --git a/internal/pkg/plugin/helminstaller/validate.go b/internal/pkg/plugin/helminstaller/validate.go deleted file mode 100644 index c9430cfff..000000000 --- a/internal/pkg/plugin/helminstaller/validate.go +++ /dev/null @@ -1,19 +0,0 @@ -package helminstaller - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/helm" - "github.com/devstream-io/devstream/pkg/util/validator" -) - -// validate validates the options provided by the core. -func validate(options configmanager.RawOptions) (configmanager.RawOptions, error) { - helmOptions, err := helm.NewOptions(options) - if err != nil { - return nil, err - } - if err := validator.CheckStructError(helmOptions).Combine(); err != nil { - return nil, err - } - return options, nil -} diff --git a/internal/pkg/plugin/installer/ci/ci.go b/internal/pkg/plugin/installer/ci/ci.go deleted file mode 100644 index f1072e33d..000000000 --- a/internal/pkg/plugin/installer/ci/ci.go +++ /dev/null @@ -1,81 +0,0 @@ -package ci - -import ( - "github.com/imdario/mergo" - - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/cifile" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/cifile/server" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/config" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/step" - "github.com/devstream-io/devstream/pkg/util/downloader" - "github.com/devstream-io/devstream/pkg/util/log" - "github.com/devstream-io/devstream/pkg/util/mapz" - "github.com/devstream-io/devstream/pkg/util/scm/git" - "github.com/devstream-io/devstream/pkg/util/types" -) - -type PipelineConfig struct { - ConfigLocation downloader.ResourceLocation `mapstructure:"configLocation" validate:"required"` - ImageRepo *step.ImageRepoStepConfig `mapstructure:"imageRepo"` - Dingtalk *step.DingtalkStepConfig `mapstructure:"dingTalk"` - Sonarqube *step.SonarQubeStepConfig `mapstructure:"sonarqube"` - Lanuage config.LanguageOption `mapstructure:"language"` - Test config.TestOption `mapstructure:"test"` -} - -type CIConfig struct { - ProjectRepo *git.RepoInfo `mapstructure:"scm" validate:"required"` - Pipeline *PipelineConfig `mapstructure:"pipeline" validate:"required"` - - // used in package - CIFileConfig *cifile.CIFileConfig `mapstructure:"ci"` -} - -func (p *PipelineConfig) BuildCIFileConfig(ciType server.CIServerType, repoInfo *git.RepoInfo) *cifile.CIFileConfig { - CIFileConfig := &cifile.CIFileConfig{ - Type: ciType, - ConfigLocation: downloader.ResourceLocation(p.ConfigLocation), - } - // update ci render variables by plugins - rawConfigVars := p.GenerateCIFileVars(repoInfo) - CIFileConfig.Vars = rawConfigVars - log.Debugf("gitlab-ci pipeline get render vars: %+v", CIFileConfig) - return CIFileConfig -} - -func (p *PipelineConfig) GenerateCIFileVars(repoInfo *git.RepoInfo) cifile.CIFileVarsMap { - // set default command for language - p.setDefault() - varMap, _ := mapz.DecodeStructToMap(p) - globalVarsMap, _ := mapz.DecodeStructToMap( - step.GetStepGlobalVars(repoInfo), - ) - err := mergo.Merge(&varMap, globalVarsMap) - if err != nil { - log.Warnf("cifile merge CIFileVarsMap failed: %+v", err) - } - varMap["AppName"] = repoInfo.Repo - return varMap -} - -func (p *PipelineConfig) setDefault() { - if !p.Lanuage.IsConfigured() { - return - } - // get language default options - defaultOpt := p.Lanuage.GetGeneralDefaultOpt() - if defaultOpt == nil { - log.Debugf("pipeline language [%+v] done's have default options", p.Lanuage) - return - } - if p.Test.Enable != types.Bool(false) { - if err := mergo.Merge(&p.Test, defaultOpt.Test); err != nil { - log.Warnf("ci merge default config failed: %+v", err) - return - } - } - // set image default url - if p.ImageRepo != nil { - p.ImageRepo.URL = p.ImageRepo.GetImageRepoURL() - } -} diff --git a/internal/pkg/plugin/installer/ci/ci_suite_test.go b/internal/pkg/plugin/installer/ci/ci_suite_test.go deleted file mode 100644 index 1a01d7fb3..000000000 --- a/internal/pkg/plugin/installer/ci/ci_suite_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package ci_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestCommon(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "common CI Suite") -} diff --git a/internal/pkg/plugin/installer/ci/ci_test.go b/internal/pkg/plugin/installer/ci/ci_test.go deleted file mode 100644 index c6f29d6ae..000000000 --- a/internal/pkg/plugin/installer/ci/ci_test.go +++ /dev/null @@ -1,121 +0,0 @@ -package ci - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/cifile" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/cifile/server" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/step" - "github.com/devstream-io/devstream/pkg/util/downloader" - "github.com/devstream-io/devstream/pkg/util/scm/git" -) - -var _ = Describe("PipelineConfig struct", func() { - var ( - a *PipelineConfig - imageRepoURL, user, repoName string - configLocation downloader.ResourceLocation - r *git.RepoInfo - ciType server.CIServerType - ) - BeforeEach(func() { - imageRepoURL = "exmaple.com" - user = "test_user" - repoName = "test_repo" - configLocation = "123/workflows" - ciType = "gitlab" - a = &PipelineConfig{ - ConfigLocation: configLocation, - ImageRepo: &step.ImageRepoStepConfig{ - URL: imageRepoURL, - User: user, - }, - } - r = &git.RepoInfo{ - Repo: repoName, - RepoType: "gitlab", - } - }) - Context("BuildCIFileConfig method", func() { - It("should work normal", func() { - var nilStepConfig *step.SonarQubeStepConfig - var nilDingTalkConfig *step.DingtalkStepConfig - var emptyBool *bool - var emptyArray []string - CIFileConfig := a.BuildCIFileConfig(ciType, r) - Expect(string(CIFileConfig.Type)).Should(Equal("gitlab")) - Expect(CIFileConfig.ConfigLocation).Should(Equal(configLocation)) - expectVars := cifile.CIFileVarsMap{ - "SonarqubeSecretKey": "SONAR_SECRET_TOKEN", - "AppName": "test_repo", - "ImageRepoSecret": "IMAGE_REPO_SECRET", - "ImageRepoDockerSecret": "image-repo-auth", - "imageRepo": map[string]interface{}{ - "url": "exmaple.com", - "user": "test_user", - "password": "", - }, - "dingTalk": nilDingTalkConfig, - "DingTalkSecretKey": "DINGTALK_SECURITY_VALUE", - "DingTalkSecretToken": "DINGTALK_SECURITY_TOKEN", - "StepGlobalVars": "", - "configLocation": downloader.ResourceLocation("123/workflows"), - "sonarqube": nilStepConfig, - "RepoType": "gitlab", - "GitlabConnectionID": "gitlabConnection", - "test": map[string]interface{}{ - "enable": emptyBool, - "command": emptyArray, - "containerName": "", - "coverageCommand": "", - "CoverageStatusCommand": "", - }, - "language": map[string]interface{}{ - "name": "", - "version": "", - "frameWork": "", - }, - } - Expect(CIFileConfig.Vars).Should(Equal(expectVars)) - }) - }) - It("should return file Vars", func() { - varMap := a.GenerateCIFileVars(r) - var emptyDingtalk *step.DingtalkStepConfig - var emptySonar *step.SonarQubeStepConfig - var emptyBool *bool - var emptyArray []string - Expect(varMap).Should(Equal(cifile.CIFileVarsMap{ - "configLocation": downloader.ResourceLocation("123/workflows"), - "DingTalkSecretToken": "DINGTALK_SECURITY_TOKEN", - "ImageRepoSecret": "IMAGE_REPO_SECRET", - "ImageRepoDockerSecret": "image-repo-auth", - "AppName": "test_repo", - "StepGlobalVars": "", - "RepoType": "gitlab", - "imageRepo": map[string]interface{}{ - "url": "exmaple.com", - "user": "test_user", - "password": "", - }, - "dingTalk": emptyDingtalk, - "sonarqube": emptySonar, - "SonarqubeSecretKey": "SONAR_SECRET_TOKEN", - "GitlabConnectionID": "gitlabConnection", - "DingTalkSecretKey": "DINGTALK_SECURITY_VALUE", - "test": map[string]interface{}{ - "enable": emptyBool, - "command": emptyArray, - "containerName": "", - "coverageCommand": "", - "CoverageStatusCommand": "", - }, - "language": map[string]interface{}{ - "name": "", - "version": "", - "frameWork": "", - }, - })) - }) -}) diff --git a/internal/pkg/plugin/installer/ci/cifile/cifile.go b/internal/pkg/plugin/installer/ci/cifile/cifile.go deleted file mode 100644 index 846d8e143..000000000 --- a/internal/pkg/plugin/installer/ci/cifile/cifile.go +++ /dev/null @@ -1,95 +0,0 @@ -package cifile - -import ( - "errors" - "fmt" - - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/cifile/server" - "github.com/devstream-io/devstream/pkg/util/downloader" - "github.com/devstream-io/devstream/pkg/util/file" - "github.com/devstream-io/devstream/pkg/util/log" - "github.com/devstream-io/devstream/pkg/util/scm/git" - "github.com/devstream-io/devstream/pkg/util/template" -) - -type CIFileConfigMap map[string]string -type CIFileVarsMap map[string]interface{} - -type CIFileConfig struct { - Type server.CIServerType `validate:"oneof=jenkins github gitlab" mapstructure:"type"` - // ConfigLocation represent location of ci config, it can be a remote location or local location - ConfigLocation downloader.ResourceLocation `validate:"required_without=ConfigContentMap" mapstructure:"configLocation"` - // Contents respent map of ci fileName to fileContent - ConfigContentMap CIFileConfigMap `validate:"required_without=ConfigLocation" mapstructure:"configContents"` - Vars CIFileVarsMap `mapstructure:"vars"` -} - -// SetContent is used to config ConfigContentMap for ci -func (c *CIFileConfig) SetContent(content string) { - ciFileName := c.newCIServerClient().CIFilePath() - if c.ConfigContentMap == nil { - c.ConfigContentMap = CIFileConfigMap{} - } - c.ConfigContentMap[ciFileName] = content -} - -func (c *CIFileConfig) SetContentMap(contentMap map[string]string) { - c.ConfigContentMap = contentMap -} - -func (c *CIFileConfig) getGitfileMap() (gitFileMap git.GitFileContentMap, err error) { - if len(c.ConfigContentMap) == 0 { - // 1. if ConfigContentMap is empty, get GitFileContentMap from ConfigLocation - gitFileMap, err = c.getConfigContentFromLocation() - } else { - // 2. else render CIFileConfig.ConfigContentMap values - gitFileMap = make(git.GitFileContentMap) - ciServerClient := c.newCIServerClient() - for filePath, content := range c.ConfigContentMap { - scmFilePath := ciServerClient.GetGitNameFunc()(filePath, "") - scmFileContent, err := c.renderContent(content) - if err != nil { - log.Debugf("ci render git files failed: %+v", err) - return nil, err - } - gitFileMap[scmFilePath] = []byte(scmFileContent) - } - } - if len(gitFileMap) == 0 { - return nil, errors.New("ci can't get valid ci files") - } - return gitFileMap, err -} - -func (c *CIFileConfig) newCIServerClient() (ciClient server.CIServerOptions) { - return server.NewCIServer(c.Type) -} - -func (c *CIFileConfig) renderContent(ciFileContent string) (string, error) { - needRenderContent := len(c.Vars) > 0 - if needRenderContent { - return template.NewRenderClient(&template.TemplateOption{ - Name: fmt.Sprintf("ci-%s", ciTemplateName), - }, template.ContentGetter).Render(ciFileContent, c.Vars) - } - return ciFileContent, nil - -} -func (c *CIFileConfig) getConfigContentFromLocation() (git.GitFileContentMap, error) { - // 1. get resource - log.Debugf("ci start to get config files [%s]...", c.ConfigLocation) - CIFileConfigPath, err := c.ConfigLocation.Download() - if err != nil { - return nil, fmt.Errorf("ci get files by %s failed: %w", c.ConfigLocation, err) - } - // 2. get ci content map from CIFileConfigPath - ciClient := c.newCIServerClient() - return file.GetFileMap( - CIFileConfigPath, ciClient.FilterCIFilesFunc(), - ciClient.GetGitNameFunc(), processCIFilesFunc(c.Vars), - ) -} - -func (m CIFileVarsMap) Set(k, v string) { - m[k] = v -} diff --git a/internal/pkg/plugin/installer/ci/cifile/cifile_suite_test.go b/internal/pkg/plugin/installer/ci/cifile/cifile_suite_test.go deleted file mode 100644 index c0fe08b36..000000000 --- a/internal/pkg/plugin/installer/ci/cifile/cifile_suite_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package cifile_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestCommon(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "common CiFile Suite") -} diff --git a/internal/pkg/plugin/installer/ci/cifile/cifile_test.go b/internal/pkg/plugin/installer/ci/cifile/cifile_test.go deleted file mode 100644 index 94dbf4501..000000000 --- a/internal/pkg/plugin/installer/ci/cifile/cifile_test.go +++ /dev/null @@ -1,161 +0,0 @@ -package cifile - -import ( - "os" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/cifile/server" - "github.com/devstream-io/devstream/pkg/util/downloader" -) - -var _ = Describe("Options struct", func() { - var ( - c *CIFileConfig - ciFilePath, ciType, ciFileContent string - ) - BeforeEach(func() { - ciType = "jenkins" - ciFileContent = "test_content" - c = &CIFileConfig{ - Type: server.CIServerType(ciType), - } - ciFilePath = c.newCIServerClient().CIFilePath() - }) - Context("SetContent method", func() { - When("contentMap is existed", func() { - BeforeEach(func() { - c.SetContent(ciFileContent) - }) - It("should update map", func() { - existV, ok := c.ConfigContentMap[ciFilePath] - Expect(ok).Should(BeTrue()) - Expect(existV).Should(Equal(ciFileContent)) - testContent := "another_test" - c.SetContent(testContent) - changedV, ok := c.ConfigContentMap[ciFilePath] - Expect(ok).Should(BeTrue()) - Expect(changedV).Should(Equal(testContent)) - }) - }) - }) - - Context("SetContentMap method", func() { - It("should set contentMap", func() { - testMap := map[string]string{ - "test": "gg", - } - c.SetContentMap(testMap) - v, ok := c.ConfigContentMap["test"] - Expect(ok).Should(BeTrue()) - Expect(v).Should(Equal("gg")) - Expect(len(c.ConfigContentMap)).Should(Equal(1)) - }) - }) - - Context("getGitfileMap method", func() { - When("ConfigContentMap is not empty", func() { - When("render contentMap failed", func() { - BeforeEach(func() { - ciFileContent = "rendered_test[[ .APP ]]" - c.SetContent(ciFileContent) - c.Vars = map[string]interface{}{ - "GG": "not_exist", - } - }) - It("should return render err", func() { - _, err := c.getGitfileMap() - Expect(err).Error().Should(HaveOccurred()) - Expect(err.Error()).Should(ContainSubstring("map has no entry for key")) - }) - }) - When("not render map content", func() { - BeforeEach(func() { - c.SetContent(ciFileContent) - c.Vars = map[string]interface{}{} - }) - It("should return render err", func() { - gitFileMap, err := c.getGitfileMap() - Expect(err).Error().ShouldNot(HaveOccurred()) - v, ok := gitFileMap[ciFilePath] - Expect(ok).Should(BeTrue()) - Expect(v).Should(Equal([]byte(v))) - }) - }) - }) - When("content and location are all empty", func() { - BeforeEach(func() { - c.ConfigContentMap = map[string]string{} - c.ConfigLocation = "" - }) - It("should return error", func() { - _, err := c.getGitfileMap() - Expect(err).Error().Should(HaveOccurred()) - Expect(err.Error()).Should(ContainSubstring("ci can't get valid ci files")) - }) - }) - }) - - Context("renderContent method", func() { - BeforeEach(func() { - c.Vars = map[string]interface{}{ - "APP": "here", - } - ciFileContent = "test,[[ .APP ]]" - }) - It("should work normal", func() { - content, err := c.renderContent(ciFileContent) - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(content).Should(Equal("test,here")) - }) - }) - - Context("getConfigContentFromLocation method", func() { - var localFilePath string - When("get ci file failed", func() { - BeforeEach(func() { - localFilePath = "not_exist" - c.ConfigContentMap = map[string]string{} - c.ConfigLocation = downloader.ResourceLocation(localFilePath) - }) - It("should return error", func() { - _, err := c.getConfigContentFromLocation() - Expect(err).Error().Should(HaveOccurred()) - Expect(err.Error()).Should(ContainSubstring("no such file or directory")) - }) - }) - When("get ci file success", func() { - BeforeEach(func() { - ciFileContent = "get ci file content" - localFilePath, err := os.CreateTemp("", "test") - Expect(err).ShouldNot(HaveOccurred()) - testContent := []byte(ciFileContent) - err = os.WriteFile(localFilePath.Name(), testContent, 0755) - Expect(err).Error().ShouldNot(HaveOccurred()) - c.ConfigContentMap = map[string]string{} - c.ConfigLocation = downloader.ResourceLocation(localFilePath.Name()) - }) - It("should return error", func() { - gitMap, err := c.getConfigContentFromLocation() - Expect(err).Error().ShouldNot(HaveOccurred()) - v, ok := gitMap[c.newCIServerClient().CIFilePath()] - Expect(ok).Should(BeTrue()) - Expect(v).Should(Equal([]byte(ciFileContent))) - }) - }) - }) -}) - -var _ = Describe("CIFileVarsMap struct", func() { - var m CIFileVarsMap - Context("Set method", func() { - m = CIFileVarsMap{} - k := "test_key" - v := "test_val" - m.Set(k, v) - expectV, ok := m[k] - Expect(ok).Should(BeTrue()) - Expect(expectV).Should(Equal(v)) - }) -}) diff --git a/internal/pkg/plugin/installer/ci/cifile/installer.go b/internal/pkg/plugin/installer/ci/cifile/installer.go deleted file mode 100644 index 5f9b33625..000000000 --- a/internal/pkg/plugin/installer/ci/cifile/installer.go +++ /dev/null @@ -1,67 +0,0 @@ -package cifile - -import ( - "errors" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/pkg/util/scm" - "github.com/devstream-io/devstream/pkg/util/scm/git" -) - -const ( - createCommitMsg = "update ci config" - deleteCommitMsg = "delete ci files" - // this variable is only used for github to fork a branch and create pr - defaultBranch = "feat-repo-ci-update" -) - -func PushCIFiles(options configmanager.RawOptions) error { - opts, err := NewOptions(options) - if err != nil { - return err - } - // 1. get git content by config - gitMap, err := opts.CIFileConfig.getGitfileMap() - if err != nil { - return err - } - //2. init git client - gitClient, err := scm.NewClientWithAuth(opts.ProjectRepo) - if err != nil { - return err - } - //3. push ci files to git repo - _, err = gitClient.PushFiles(&git.CommitInfo{ - CommitMsg: createCommitMsg, - GitFileMap: gitMap, - CommitBranch: defaultBranch, - }, true) - return err -} - -func DeleteCIFiles(options configmanager.RawOptions) error { - opts, err := NewOptions(options) - if err != nil { - return err - } - // 1. get git content by config - gitMap, err := opts.CIFileConfig.getGitfileMap() - if err != nil { - return err - } - if len(gitMap) == 0 { - return errors.New("can't get valid Jenkinsfile, please check your config") - } - //2. init git client - gitClient, err := scm.NewClientWithAuth(opts.ProjectRepo) - if err != nil { - return err - } - //3. delete ci files from git repo - commitInfo := &git.CommitInfo{ - CommitMsg: deleteCommitMsg, - GitFileMap: gitMap, - CommitBranch: opts.ProjectRepo.Branch, - } - return gitClient.DeleteFiles(commitInfo) -} diff --git a/internal/pkg/plugin/installer/ci/cifile/option.go b/internal/pkg/plugin/installer/ci/cifile/option.go deleted file mode 100644 index 85d6f59c6..000000000 --- a/internal/pkg/plugin/installer/ci/cifile/option.go +++ /dev/null @@ -1,36 +0,0 @@ -package cifile - -import ( - "github.com/mitchellh/mapstructure" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/pkg/util/scm/git" - "github.com/devstream-io/devstream/pkg/util/types" -) - -type Options struct { - CIFileConfig *CIFileConfig `mapstructure:"ci" validate:"required"` - ProjectRepo *git.RepoInfo `mapstructure:"scm" validate:"required"` -} - -func NewOptions(options configmanager.RawOptions) (*Options, error) { - var opts Options - if err := mapstructure.Decode(options, &opts); err != nil { - return nil, err - } - return &opts, nil -} - -// FillDefaultValue config options default values by input defaultOptions -func (opts *Options) fillDefaultValue(defaultOptions *Options) { - if opts.CIFileConfig == nil { - opts.CIFileConfig = defaultOptions.CIFileConfig - } else { - types.FillStructDefaultValue(opts.CIFileConfig, defaultOptions.CIFileConfig) - } - if opts.ProjectRepo == nil { - opts.ProjectRepo = defaultOptions.ProjectRepo - } else { - types.FillStructDefaultValue(opts.ProjectRepo, defaultOptions.ProjectRepo) - } -} diff --git a/internal/pkg/plugin/installer/ci/cifile/option_test.go b/internal/pkg/plugin/installer/ci/cifile/option_test.go deleted file mode 100644 index 37da9ec34..000000000 --- a/internal/pkg/plugin/installer/ci/cifile/option_test.go +++ /dev/null @@ -1,82 +0,0 @@ -package cifile - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/cifile/server" - "github.com/devstream-io/devstream/pkg/util/scm/git" -) - -var _ = Describe("Options struct", func() { - var ( - rawOptions configmanager.RawOptions - defaultOpts, opts *Options - ) - BeforeEach(func() { - opts = &Options{} - defaultCIFileConfig := &CIFileConfig{ - Type: "github", - ConfigLocation: "http://www.test.com", - } - defaultRepo := &git.RepoInfo{ - Owner: "test", - Repo: "test_repo", - Branch: "test_branch", - RepoType: "gitlab", - } - defaultOpts = &Options{ - CIFileConfig: defaultCIFileConfig, - ProjectRepo: defaultRepo, - } - }) - - Context("NewOptions method", func() { - When("options is valid", func() { - BeforeEach(func() { - rawOptions = configmanager.RawOptions{ - "ci": map[string]interface{}{ - "type": "gitlab", - "content": "test", - }, - } - }) - It("should not raise error", func() { - _, err := NewOptions(rawOptions) - Expect(err).Error().ShouldNot(HaveOccurred()) - }) - }) - }) - - Context("fillDefaultValue method", func() { - When("ci config and repo are all empty", func() { - It("should set default value", func() { - opts.fillDefaultValue(defaultOpts) - Expect(opts.CIFileConfig).ShouldNot(BeNil()) - Expect(opts.ProjectRepo).ShouldNot(BeNil()) - Expect(string(opts.CIFileConfig.ConfigLocation)).Should(Equal("http://www.test.com")) - Expect(opts.ProjectRepo.Repo).Should(Equal("test_repo")) - }) - }) - When("ci config and repo has some value", func() { - BeforeEach(func() { - opts.CIFileConfig = &CIFileConfig{ - ConfigLocation: "http://exist.com", - } - opts.ProjectRepo = &git.RepoInfo{ - Branch: "new_branch", - } - }) - It("should update empty value", func() { - opts.fillDefaultValue(defaultOpts) - Expect(opts.CIFileConfig).ShouldNot(BeNil()) - Expect(opts.ProjectRepo).ShouldNot(BeNil()) - Expect(string(opts.CIFileConfig.ConfigLocation)).Should(Equal("http://exist.com")) - Expect(opts.CIFileConfig.Type).Should(Equal(server.CIServerType("github"))) - Expect(opts.ProjectRepo.Branch).Should(Equal("new_branch")) - Expect(opts.ProjectRepo.Repo).Should(Equal("test_repo")) - }) - }) - }) -}) diff --git a/internal/pkg/plugin/installer/ci/cifile/server/githubci.go b/internal/pkg/plugin/installer/ci/cifile/server/githubci.go deleted file mode 100644 index 230f85cf6..000000000 --- a/internal/pkg/plugin/installer/ci/cifile/server/githubci.go +++ /dev/null @@ -1,40 +0,0 @@ -package server - -import ( - "path" - "path/filepath" - "strings" - - "github.com/devstream-io/devstream/pkg/util/file" -) - -const ( - CIGithubType CIServerType = "github" - CiGitHubWorkConfigLocation string = ".github/workflows" - ciGithubTempName string = "app.yaml" -) - -type GitHubWorkflow struct { -} - -// CIFilePath return .github/workflows/app.yml -func (g *GitHubWorkflow) CIFilePath() string { - return filepath.Join(CiGitHubWorkConfigLocation, ciGithubTempName) -} - -func (g *GitHubWorkflow) FilterCIFilesFunc() file.DirFileFilterFunc { - return func(filePath string, isDir bool) bool { - // not process dir - if isDir { - return false - } - return strings.Contains(filePath, "yml") || strings.Contains(filePath, "yaml") - } -} - -func (g *GitHubWorkflow) GetGitNameFunc() file.DirFileNameFunc { - return func(filePath, _ string) string { - basePath := filepath.Base(filePath) - return path.Join(CiGitHubWorkConfigLocation, basePath) - } -} diff --git a/internal/pkg/plugin/installer/ci/cifile/server/gitlabci.go b/internal/pkg/plugin/installer/ci/cifile/server/gitlabci.go deleted file mode 100644 index ac1243d12..000000000 --- a/internal/pkg/plugin/installer/ci/cifile/server/gitlabci.go +++ /dev/null @@ -1,36 +0,0 @@ -package server - -import ( - "path/filepath" - - "github.com/devstream-io/devstream/pkg/util/file" -) - -const ( - CIGitLabType CIServerType = "gitlab" - ciGitLabConfigLocation string = ".gitlab-ci.yml" -) - -type GitLabCI struct { -} - -// CIFilePath return .gitlab-ci.yml -func (g *GitLabCI) CIFilePath() string { - return ciGitLabConfigLocation -} - -func (g *GitLabCI) FilterCIFilesFunc() file.DirFileFilterFunc { - return func(filePath string, isDir bool) bool { - // not process dir - if isDir { - return false - } - return filepath.Base(filePath) == ciGitLabConfigLocation - } -} - -func (g *GitLabCI) GetGitNameFunc() file.DirFileNameFunc { - return func(filePath, _ string) string { - return g.CIFilePath() - } -} diff --git a/internal/pkg/plugin/installer/ci/cifile/server/jenkinsci.go b/internal/pkg/plugin/installer/ci/cifile/server/jenkinsci.go deleted file mode 100644 index e6004a64b..000000000 --- a/internal/pkg/plugin/installer/ci/cifile/server/jenkinsci.go +++ /dev/null @@ -1,37 +0,0 @@ -package server - -import ( - "path/filepath" - - "github.com/devstream-io/devstream/pkg/util/file" -) - -const ( - CIJenkinsType CIServerType = "jenkins" - ciJenkinsConfigLocation string = "Jenkinsfile" -) - -type JenkinsPipeline struct { -} - -// CIFilePath return Jenkinsfile -func (j *JenkinsPipeline) CIFilePath() string { - return ciJenkinsConfigLocation -} - -// FilterCIFilesFunc only get file with name Jenkinsfile -func (j *JenkinsPipeline) FilterCIFilesFunc() file.DirFileFilterFunc { - return func(filePath string, isDir bool) bool { - // not process dir - if isDir { - return false - } - return filepath.Base(filePath) == ciJenkinsConfigLocation - } -} - -func (j *JenkinsPipeline) GetGitNameFunc() file.DirFileNameFunc { - return func(filePath, _ string) string { - return j.CIFilePath() - } -} diff --git a/internal/pkg/plugin/installer/ci/cifile/server/server.go b/internal/pkg/plugin/installer/ci/cifile/server/server.go deleted file mode 100644 index c53696787..000000000 --- a/internal/pkg/plugin/installer/ci/cifile/server/server.go +++ /dev/null @@ -1,36 +0,0 @@ -package server - -import ( - "github.com/devstream-io/devstream/pkg/util/file" -) - -type CIServerType string - -type CIServerOptions interface { - // CIFilePath returns the file path of ci config file - // gitlab and jenkins is just a file, so we can just use filename - // but GitHub use directory, we should process this situation - // for GitHub: return ".github/workflows" or ".github/workflows/subFilename" - // for gitlab, jenkins: will ignore subFilename param - CIFilePath() string - - // FilterCIFilesFunc returns a filter function to select ci config file - FilterCIFilesFunc() file.DirFileFilterFunc - // GetGitNameFunc returns a function to transform file path to git name of ci config file - GetGitNameFunc() file.DirFileNameFunc -} - -func NewCIServer(ciType CIServerType) CIServerOptions { - // there are no validation for ciType - // because we have already validated it by `validate` flag in CIFileConfig.Type - switch ciType { - case CIGitLabType: - return &GitLabCI{} - case CIGithubType: - return &GitHubWorkflow{} - case CIJenkinsType: - return &JenkinsPipeline{} - } - //TODO(jiafeng meng): if ciType is not exist, this will cause error - return nil -} diff --git a/internal/pkg/plugin/installer/ci/cifile/server/server_suite_test.go b/internal/pkg/plugin/installer/ci/cifile/server/server_suite_test.go deleted file mode 100644 index a91777004..000000000 --- a/internal/pkg/plugin/installer/ci/cifile/server/server_suite_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package server_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestCommon(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "common Ci Server Suite") -} diff --git a/internal/pkg/plugin/installer/ci/cifile/server/server_test.go b/internal/pkg/plugin/installer/ci/cifile/server/server_test.go deleted file mode 100644 index 206adf4b2..000000000 --- a/internal/pkg/plugin/installer/ci/cifile/server/server_test.go +++ /dev/null @@ -1,121 +0,0 @@ -package server - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -var _ = Describe("ci walkDir methods", func() { - type testCase struct { - filePath string - ciType CIServerType - isDir bool - } - var ( - testCases []*testCase - testCaseData *testCase - ) - Context("FilterCIFilesFunc func", func() { - When("input file not not right", func() { - BeforeEach(func() { - testCases = []*testCase{ - {"Jenkinsfile", CIServerType("gitlab"), false}, - {"directory", CIServerType("gitlab"), true}, - {"directory", CIServerType("github"), true}, - {".gitlab.ci", CIServerType("jenkins"), false}, - {"Jenkinsfile", CIServerType("jenkins"), true}, - {"workflows/pr.yaml", CIServerType("jenkins"), false}, - } - }) - It("should return false", func() { - for _, tt := range testCases { - Expect(NewCIServer(tt.ciType).FilterCIFilesFunc()(tt.filePath, tt.isDir)).Should(BeFalse()) - } - }) - }) - When("input file is valid", func() { - BeforeEach(func() { - testCases = []*testCase{ - {"Jenkinsfile", CIServerType("jenkins"), false}, - {".gitlab-ci.yml", CIServerType("gitlab"), false}, - {"workflows/pr.yaml", CIServerType("github"), false}, - {"workflows/pr2.yaml", CIServerType("github"), false}, - } - }) - It("should return true", func() { - for _, tt := range testCases { - Expect(NewCIServer(tt.ciType).FilterCIFilesFunc()(tt.filePath, tt.isDir)).Should(BeTrue()) - } - }) - }) - }) - - Context("GetGitNameFunc func", func() { - When("ciType is github", func() { - BeforeEach(func() { - testCaseData = &testCase{"workflows/pr.yaml", CIServerType("github"), false} - }) - It("should return github workflows path", func() { - result := NewCIServer(testCaseData.ciType).GetGitNameFunc()(testCaseData.filePath, "workflows") - Expect(result).Should(Equal(".github/workflows/pr.yaml")) - }) - }) - When("ciType is jenkins", func() { - BeforeEach(func() { - testCaseData = &testCase{"work/Jenkinsfile", CIServerType("jenkins"), false} - }) - It("should return github workflows path", func() { - result := NewCIServer(testCaseData.ciType).GetGitNameFunc()(testCaseData.filePath, "") - Expect(result).Should(Equal("Jenkinsfile")) - }) - }) - When("ciType is gitlab", func() { - BeforeEach(func() { - testCaseData = &testCase{"work/.gitlab-ci.yml", CIServerType("gitlab"), false} - }) - It("should return github workflows path", func() { - result := NewCIServer(testCaseData.ciType).GetGitNameFunc()(testCaseData.filePath, "") - Expect(result).Should(Equal(".gitlab-ci.yml")) - }) - }) - }) - - Context("CIFilePath func", func() { - When("ciType is github", func() { - BeforeEach(func() { - testCaseData = &testCase{"workflows/pr.yaml", CIServerType("github"), false} - }) - It("should return github workflows path", func() { - result := NewCIServer(testCaseData.ciType).CIFilePath() - Expect(result).Should(Equal(".github/workflows/app.yaml")) - }) - }) - When("ciType is gitlab", func() { - BeforeEach(func() { - testCaseData = &testCase{"", CIServerType("gitlab"), false} - }) - It("should return gitlab ci path", func() { - result := NewCIServer(testCaseData.ciType).CIFilePath() - Expect(result).Should(Equal(".gitlab-ci.yml")) - }) - }) - When("ciType is jenkins", func() { - BeforeEach(func() { - testCaseData = &testCase{"", CIServerType("jenkins"), false} - }) - It("should return jenkins ci path", func() { - result := NewCIServer(testCaseData.ciType).CIFilePath() - Expect(result).Should(Equal("Jenkinsfile")) - }) - }) - }) - - Context("NewCIServer func", func() { - When("ci type not exist", func() { - It("should return nil", func() { - server := NewCIServer(CIServerType("not_exist_type")) - Expect(server).Should(BeNil()) - }) - }) - }) -}) diff --git a/internal/pkg/plugin/installer/ci/cifile/state.go b/internal/pkg/plugin/installer/ci/cifile/state.go deleted file mode 100644 index b6737699f..000000000 --- a/internal/pkg/plugin/installer/ci/cifile/state.go +++ /dev/null @@ -1,34 +0,0 @@ -package cifile - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" - "github.com/devstream-io/devstream/pkg/util/scm" -) - -func GetCIFileStatus(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - opts, err := NewOptions(options) - if err != nil { - return nil, err - } - // init scm client - client, err := scm.NewClientWithAuth(opts.ProjectRepo) - if err != nil { - return nil, err - } - - // get local file info - gitMap, err := opts.CIFileConfig.getGitfileMap() - if err != nil { - log.Debugf("ci state get gitMap failed: %+v", err) - return nil, err - } - - gitFileStatus, err := scm.GetGitFileStats(client, gitMap) - if err != nil { - return nil, err - } - statusMap := statemanager.ResourceStatus(gitFileStatus) - return statusMap, nil -} diff --git a/internal/pkg/plugin/installer/ci/cifile/utils.go b/internal/pkg/plugin/installer/ci/cifile/utils.go deleted file mode 100644 index b3258110a..000000000 --- a/internal/pkg/plugin/installer/ci/cifile/utils.go +++ /dev/null @@ -1,28 +0,0 @@ -package cifile - -import ( - "fmt" - "os" - - "github.com/devstream-io/devstream/pkg/util/file" - "github.com/devstream-io/devstream/pkg/util/template" -) - -const ( - ciTemplateName = "ci_template" -) - -func processCIFilesFunc(vars map[string]interface{}) file.DirFileContentFunc { - return func(filePath string) ([]byte, error) { - if len(vars) == 0 { - return os.ReadFile(filePath) - } - renderContent, err := template.NewRenderClient(&template.TemplateOption{ - Name: fmt.Sprintf("ci-%s", ciTemplateName), - }, template.LocalFileGetter).Render(filePath, vars) - if err != nil { - return nil, err - } - return []byte(renderContent), nil - } -} diff --git a/internal/pkg/plugin/installer/ci/cifile/utils_test.go b/internal/pkg/plugin/installer/ci/cifile/utils_test.go deleted file mode 100644 index fe5381d52..000000000 --- a/internal/pkg/plugin/installer/ci/cifile/utils_test.go +++ /dev/null @@ -1,43 +0,0 @@ -package cifile - -import ( - "os" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -var _ = Describe("processCIFilesFunc func", func() { - var ( - tempFileLoc string - testContent []byte - ) - BeforeEach(func() { - tempDir := GinkgoT().TempDir() - tempFile, err := os.CreateTemp(tempDir, "testFile") - Expect(err).Error().ShouldNot(HaveOccurred()) - tempFileLoc = tempFile.Name() - }) - When("file is right", func() { - BeforeEach(func() { - testContent = []byte("[[ .Name ]]_test") - err := os.WriteFile(tempFileLoc, testContent, 0755) - Expect(err).Error().ShouldNot(HaveOccurred()) - }) - It("should work as expected", func() { - result, err := processCIFilesFunc(map[string]interface{}{ - "Name": "devstream", - })(tempFileLoc) - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(result).Should(Equal([]byte("devstream_test"))) - }) - }) - When("file is not exist", func() { - It("should return error", func() { - _, err := processCIFilesFunc(map[string]interface{}{ - "Name": "devstream", - })("not_exist_file") - Expect(err).Error().Should(HaveOccurred()) - }) - }) -}) diff --git a/internal/pkg/plugin/installer/ci/cifile/validate.go b/internal/pkg/plugin/installer/ci/cifile/validate.go deleted file mode 100644 index 2abcff1f5..000000000 --- a/internal/pkg/plugin/installer/ci/cifile/validate.go +++ /dev/null @@ -1,32 +0,0 @@ -package cifile - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - "github.com/devstream-io/devstream/pkg/util/types" - "github.com/devstream-io/devstream/pkg/util/validator" -) - -// Validate validates the options provided by the dtm-core. -func Validate(options configmanager.RawOptions) (configmanager.RawOptions, error) { - opts, err := NewOptions(options) - if err != nil { - return nil, err - } - if fieldErr := validator.CheckStructError(opts).Combine(); fieldErr != nil { - return nil, fieldErr - } - return options, nil -} - -// SetDefaultConfig will update options empty values base on import options -func SetDefaultConfig(defaultConfig *Options) installer.MutableOperation { - return func(options configmanager.RawOptions) (configmanager.RawOptions, error) { - opts, err := NewOptions(options) - if err != nil { - return nil, err - } - opts.fillDefaultValue(defaultConfig) - return types.EncodeStruct(opts) - } -} diff --git a/internal/pkg/plugin/installer/ci/cifile/validate_test.go b/internal/pkg/plugin/installer/ci/cifile/validate_test.go deleted file mode 100644 index d15132747..000000000 --- a/internal/pkg/plugin/installer/ci/cifile/validate_test.go +++ /dev/null @@ -1,98 +0,0 @@ -package cifile_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/cifile" - - "github.com/devstream-io/devstream/pkg/util/scm/git" -) - -var _ = Describe("validate func", func() { - var option configmanager.RawOptions - When("options is not valid", func() { - It("should return error", func() { - ciTypeNotExistOptions := map[string]any{ - "ci": map[string]any{ - "configLocation": "workflows/Jenkinsfile", - "type": "gg", - }, - "projectRepo": map[string]any{ - "baseURL": "http://127.0.0.1:30020", - "branch": "main", - "org": "", - "owner": "test_user", - "name": "test", - "scmType": "gitlab", - }, - } - _, err := cifile.Validate(ciTypeNotExistOptions) - Expect(err).Should(HaveOccurred()) - }) - }) - When("options is valid", func() { - BeforeEach(func() { - option = map[string]any{ - "ci": map[string]any{ - "configLocation": "workflows/Jenkinsfile", - "type": "jenkins", - }, - "projectRepo": map[string]any{ - "baseURL": "http://127.0.0.1:30020", - "branch": "main", - "org": "", - "owner": "test_user", - "name": "test", - "scmType": "gitlab", - }, - } - }) - It("should return nil error", func() { - option = map[string]any{ - "ci": map[string]any{ - "configLocation": "http://test.com", - "type": "gitlab", - }, - "scm": map[string]any{ - "baseURL": "http://127.0.0.1:30020", - "branch": "main", - "org": "", - "owner": "test_user", - "name": "test", - "scmType": "gitlab", - }, - } - _, err := cifile.Validate(option) - Expect(err).ShouldNot(HaveOccurred()) - }) - }) -}) - -var _ = Describe("SetDefaultConfig func", func() { - var defaultOpts *cifile.Options - BeforeEach(func() { - defaultCIFileConfig := &cifile.CIFileConfig{ - Type: "github", - ConfigLocation: "http://www.test.com", - } - defaultRepo := &git.RepoInfo{ - Owner: "test", - Repo: "test_repo", - Branch: "test_branch", - RepoType: "gitlab", - } - defaultOpts = &cifile.Options{ - CIFileConfig: defaultCIFileConfig, - ProjectRepo: defaultRepo, - } - }) - It("should work normal", func() { - defaultFunc := cifile.SetDefaultConfig(defaultOpts) - rawOptions := map[string]interface{}{} - opts, err := defaultFunc(rawOptions) - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(len(opts)).Should(Equal(2)) - }) -}) diff --git a/internal/pkg/plugin/installer/ci/config/config.go b/internal/pkg/plugin/installer/ci/config/config.go deleted file mode 100644 index 65e9c9654..000000000 --- a/internal/pkg/plugin/installer/ci/config/config.go +++ /dev/null @@ -1,6 +0,0 @@ -package config - -type GeneralDefaultOption struct { - Test *TestOption - defaultVersion string -} diff --git a/internal/pkg/plugin/installer/ci/config/config_suite_test.go b/internal/pkg/plugin/installer/ci/config/config_suite_test.go deleted file mode 100644 index 417b805b8..000000000 --- a/internal/pkg/plugin/installer/ci/config/config_suite_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package config_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestCommon(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "common CI Config Suite") -} diff --git a/internal/pkg/plugin/installer/ci/config/language.go b/internal/pkg/plugin/installer/ci/config/language.go deleted file mode 100644 index d97fd4634..000000000 --- a/internal/pkg/plugin/installer/ci/config/language.go +++ /dev/null @@ -1,119 +0,0 @@ -package config - -import ( - "strings" - - "github.com/devstream-io/devstream/pkg/util/types" -) - -type LanguageOption struct { - Name string `mapstructure:"name"` - Version string `mapstructure:"version"` - FrameWork string `mapstructure:"frameWork"` -} - -var ( - // aliasMap is used to map language/framework alias to standard name devstream used - aliasMap = map[string]string{ - "golang": "go", - "spring-boot": "springboot", - } - languageDefaultOptionMap = map[string]*GeneralDefaultOption{ - "java": { - Test: &TestOption{ - Command: []string{"mvn -B test"}, - ContainerName: "maven:3.8.1-jdk-8", - Enable: types.Bool(true), - }, - defaultVersion: "8", - }, - "go": { - Test: &TestOption{ - Enable: types.Bool(true), - ContainerName: "golang:1.18", - Command: []string{"go test ./..."}, - CoverageCommand: "go tool cover -func=coverage.out >> coverage.cov", - CoverageStatusCommand: `cat coverage.cov -body=$(cat coverage.cov) -body="${body//'%'/'%25'}" -body="${body//$'\n'/'%0A'}" -body="${body//$'\r'/'%0D'}" -echo ::set-output name=body::$body`, - }, - defaultVersion: "1.17", - }, - "python": { - Test: &TestOption{ - Command: []string{ - "python -m pip install --upgrade pip", - "pip install -r requirements.txt", - "python3 -m unittest", - }, - ContainerName: "python:3.10.9", - Enable: types.Bool(true), - }, - defaultVersion: "3.9", - }, - "nodejs": { - Test: &TestOption{ - Command: []string{ - "npm ci", - "npm run build --if-present", - "npm test", - }, - ContainerName: "nodejs:18", - Enable: types.Bool(true), - }, - defaultVersion: "18", - }, - } - frameworkDefaultOptionMap = map[string]*GeneralDefaultOption{ - "gin": languageDefaultOptionMap["go"], - "springboot": languageDefaultOptionMap["java"], - "django": languageDefaultOptionMap["python"], - } - framworkLanguageMap = map[string]string{ - "gin": "go", - "springboot": "java", - "django": "python", - } -) - -// GetGeneralDefaultOpt will return language/frameWork default ci/cd options -func (l *LanguageOption) GetGeneralDefaultOpt() *GeneralDefaultOption { - // if frameWork is configured, use frameWork first - // else use languange name for defalt options - var defaultOpt *GeneralDefaultOption - if l.FrameWork != "" { - frameWork := strings.TrimSpace(strings.ToLower(l.FrameWork)) - if frameWorkAlias, exist := aliasMap[frameWork]; exist { - frameWork = frameWorkAlias - } - if l.Name == "" { - l.Name = framworkLanguageMap[l.FrameWork] - } - defaultOpt = frameworkDefaultOptionMap[frameWork] - } - - if l.Name != "" { - lang := strings.TrimSpace(strings.ToLower(l.Name)) - if aliasLang, exist := aliasMap[lang]; exist { - lang = aliasLang - l.Name = aliasLang - } - if defaultOpt == nil { - defaultOpt = languageDefaultOptionMap[lang] - } - } - if defaultOpt != nil { - if l.Version == "" { - l.Version = defaultOpt.defaultVersion - } - } - return defaultOpt -} - -// IS -func (l LanguageOption) IsConfigured() bool { - return l.Name != "" || l.FrameWork != "" -} diff --git a/internal/pkg/plugin/installer/ci/config/language_test.go b/internal/pkg/plugin/installer/ci/config/language_test.go deleted file mode 100644 index f7728e2f7..000000000 --- a/internal/pkg/plugin/installer/ci/config/language_test.go +++ /dev/null @@ -1,69 +0,0 @@ -package config_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/config" -) - -var _ = Describe("Language struct", func() { - var ( - l config.LanguageOption - ) - Context("GetGeneralDefaultOpt func", func() { - When("lanuage is go", func() { - BeforeEach(func() { - l = config.LanguageOption{ - Name: "go", - } - }) - It("should return go default config", func() { - defaultConfig := l.GetGeneralDefaultOpt() - Expect(defaultConfig).ShouldNot(BeNil()) - Expect(defaultConfig.Test.Command).Should(Equal([]string{"go test ./..."})) - }) - }) - When("lanuage is java", func() { - BeforeEach(func() { - l = config.LanguageOption{ - Name: "java", - } - }) - It("should return java default config", func() { - defaultConfig := l.GetGeneralDefaultOpt() - Expect(defaultConfig).ShouldNot(BeNil()) - Expect(defaultConfig.Test.Command).Should(Equal([]string{"mvn -B test"})) - }) - }) - When("language is not exist", func() { - BeforeEach(func() { - l = config.LanguageOption{ - Name: "not_exist", - } - }) - It("should return go default config", func() { - defaultConfig := l.GetGeneralDefaultOpt() - Expect(defaultConfig).Should(BeNil()) - }) - }) - }) - Context("IsConfigured func", func() { - When("is empty", func() { - BeforeEach(func() { - l = config.LanguageOption{} - }) - It("should return false", func() { - Expect(l.IsConfigured()).Should(BeFalse()) - }) - }) - When("not empty", func() { - BeforeEach(func() { - l = config.LanguageOption{Name: "test"} - }) - It("should return true", func() { - Expect(l.IsConfigured()).Should(BeTrue()) - }) - }) - }) -}) diff --git a/internal/pkg/plugin/installer/ci/config/test.go b/internal/pkg/plugin/installer/ci/config/test.go deleted file mode 100644 index 4543d0cdb..000000000 --- a/internal/pkg/plugin/installer/ci/config/test.go +++ /dev/null @@ -1,9 +0,0 @@ -package config - -type TestOption struct { - Enable *bool `mapstructure:"enable"` - Command []string `mapstructure:"command"` - ContainerName string `mapstructure:"containerName"` - CoverageCommand string `mapstructure:"coverageCommand"` - CoverageStatusCommand string `mapstructure:"CoverageStatusCommand"` -} diff --git a/internal/pkg/plugin/installer/ci/step/dingtalk.go b/internal/pkg/plugin/installer/ci/step/dingtalk.go deleted file mode 100644 index 093d82c6c..000000000 --- a/internal/pkg/plugin/installer/ci/step/dingtalk.go +++ /dev/null @@ -1,79 +0,0 @@ -package step - -import ( - "fmt" - "strings" - - "github.com/devstream-io/devstream/pkg/util/jenkins" - "github.com/devstream-io/devstream/pkg/util/jenkins/dingtalk" - "github.com/devstream-io/devstream/pkg/util/log" - "github.com/devstream-io/devstream/pkg/util/scm" -) - -const ( - dingTalkSecretToken = "DINGTALK_SECURITY_TOKEN" - dingTalkSecretVal = "DINGTALK_SECURITY_VALUE" -) - -type DingtalkStepConfig struct { - Name string `mapstructure:"name"` - Webhook string `mapstructure:"webhook"` - SecurityValue string `mapstructure:"securityValue"` - SecurityType string `mapstructure:"securityType" validate:"required,oneof=KEY SECRET"` - AtUsers string `mapstructure:"atUsers"` -} - -// GetJenkinsPlugins return jenkins plugins info -func (g *DingtalkStepConfig) GetJenkinsPlugins() []*jenkins.JenkinsPlugin { - return []*jenkins.JenkinsPlugin{ - { - Name: "dingding-notifications", - Version: "2.4.10", - }, - } -} - -// JenkinsConfig config jenkins and return casc config -func (g *DingtalkStepConfig) ConfigJenkins(jenkinsClient jenkins.JenkinsAPI) (*jenkins.RepoCascConfig, error) { - robotConfig := dingtalk.BotInfoConfig{ - // use dingtalk robot name as id - ID: g.Name, - Name: g.Name, - Webhook: g.Webhook, - SecurityType: g.SecurityType, - } - if g.needSetSecurityValue() { - robotConfig.SecurityValue = g.SecurityValue - } - config := dingtalk.BotConfig{ - RobotConfigs: []dingtalk.BotInfoConfig{ - robotConfig, - }, - } - log.Info("jenkins plugin dingtalk start config...") - return nil, jenkinsClient.ApplyDingTalkBot(config) -} - -// ConfigSCM is used to config github and gitlab variables -func (g *DingtalkStepConfig) ConfigSCM(client scm.ClientOperation) error { - splitWebhook := strings.Split(g.Webhook, "=") - if len(splitWebhook) < 2 { - return fmt.Errorf("step scm dingTalk.webhook is not valid") - } - token := splitWebhook[len(splitWebhook)-1] - if g.needSetSecurityValue() { - err := client.AddRepoSecret(dingTalkSecretVal, g.SecurityValue) - if err != nil { - return err - } - } - return client.AddRepoSecret(dingTalkSecretToken, token) -} - -// if dingTalk use SECRET securityType, we need to set SecurityValue for use later -func (g *DingtalkStepConfig) needSetSecurityValue() bool { - if g.SecurityType == "SECRET" && g.SecurityValue != "" { - return true - } - return false -} diff --git a/internal/pkg/plugin/installer/ci/step/dingtalk_test.go b/internal/pkg/plugin/installer/ci/step/dingtalk_test.go deleted file mode 100644 index 3216ffe60..000000000 --- a/internal/pkg/plugin/installer/ci/step/dingtalk_test.go +++ /dev/null @@ -1,89 +0,0 @@ -package step_test - -import ( - "fmt" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/step" - "github.com/devstream-io/devstream/pkg/util/jenkins" - "github.com/devstream-io/devstream/pkg/util/scm" -) - -var _ = Describe("DingtalkStepConfig", func() { - var ( - c *step.DingtalkStepConfig - mockClient *jenkins.MockClient - name string - ) - BeforeEach(func() { - name = "test" - c = &step.DingtalkStepConfig{ - Name: name, - } - }) - Context("GetJenkinsPlugins method", func() { - It("should return dingding plugin", func() { - plugins := c.GetJenkinsPlugins() - Expect(len(plugins)).Should(Equal(1)) - plugin := plugins[0] - Expect(plugin.Name).Should(Equal("dingding-notifications")) - }) - }) - - Context("ConfigJenkins method", func() { - When("all config work noraml", func() { - BeforeEach(func() { - mockClient = &jenkins.MockClient{} - }) - It("should return nil", func() { - _, err := c.ConfigJenkins(mockClient) - Expect(err).Error().ShouldNot(HaveOccurred()) - }) - }) - }) - - Context("ConfigSCM method", func() { - var ( - scmClient *scm.MockScmClient - errMsg string - ) - When("webhook is not valid", func() { - BeforeEach(func() { - errMsg = "step scm dingTalk.webhook is not valid" - c.Webhook = "test.example.com/gg" - scmClient = &scm.MockScmClient{} - }) - It("should return error", func() { - err := c.ConfigSCM(scmClient) - Expect(err).Error().Should(HaveOccurred()) - Expect(err.Error()).Should(Equal(errMsg)) - }) - }) - When("add token failed", func() { - BeforeEach(func() { - errMsg = "add token failed" - c.Webhook = "test.example.com/gg?token=test" - scmClient = &scm.MockScmClient{ - AddRepoSecretError: fmt.Errorf(errMsg), - } - }) - It("should return error", func() { - err := c.ConfigSCM(scmClient) - Expect(err).Error().Should(HaveOccurred()) - Expect(err.Error()).Should(Equal(errMsg)) - }) - }) - When("all valid", func() { - BeforeEach(func() { - c.Webhook = "test.example.com/gg?token=test" - scmClient = &scm.MockScmClient{} - }) - It("should return nil", func() { - err := c.ConfigSCM(scmClient) - Expect(err).Error().ShouldNot(HaveOccurred()) - }) - }) - }) -}) diff --git a/internal/pkg/plugin/installer/ci/step/github.go b/internal/pkg/plugin/installer/ci/step/github.go deleted file mode 100644 index b6a16f624..000000000 --- a/internal/pkg/plugin/installer/ci/step/github.go +++ /dev/null @@ -1,55 +0,0 @@ -package step - -import ( - "github.com/devstream-io/devstream/pkg/util/jenkins" - "github.com/devstream-io/devstream/pkg/util/log" - "github.com/devstream-io/devstream/pkg/util/scm" -) - -const ( - githubCredentialName = "githubCredential" -) - -type GithubStepConfig struct { - RepoOwner string `mapstructure:"repoOwner"` - Token string `mapstructure:"token"` -} - -func (g *GithubStepConfig) GetJenkinsPlugins() []*jenkins.JenkinsPlugin { - return []*jenkins.JenkinsPlugin{ - { - Name: "github-branch-source", - Version: "1695.v88de84e9f6b_9", - }, - } -} - -func (g *GithubStepConfig) ConfigJenkins(jenkinsClient jenkins.JenkinsAPI) (*jenkins.RepoCascConfig, error) { - // 1. create github credentials by github token - err := jenkinsClient.CreatePasswordCredential( - githubCredentialName, - g.RepoOwner, - g.Token, - ) - if err != nil { - log.Debugf("jenkins preinstall github credentials failed: %s", err) - return nil, err - } - // 2. config github plugin casc - return &jenkins.RepoCascConfig{ - RepoType: "github", - CredentialID: githubCredentialName, - JenkinsURL: jenkinsClient.GetBasicInfo().URL, - }, nil -} - -func (g *GithubStepConfig) ConfigSCM(client scm.ClientOperation) error { - return nil -} - -func newGithubStep(config *StepGlobalOption) *GithubStepConfig { - return &GithubStepConfig{ - RepoOwner: config.RepoInfo.GetRepoOwner(), - Token: config.RepoInfo.Token, - } -} diff --git a/internal/pkg/plugin/installer/ci/step/github_test.go b/internal/pkg/plugin/installer/ci/step/github_test.go deleted file mode 100644 index 4874fd500..000000000 --- a/internal/pkg/plugin/installer/ci/step/github_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package step - -import ( - "fmt" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/pkg/util/jenkins" - "github.com/devstream-io/devstream/pkg/util/scm" - "github.com/devstream-io/devstream/pkg/util/scm/git" -) - -var _ = Describe("GithubStepConfig", func() { - var ( - c *GithubStepConfig - mockClient *jenkins.MockClient - repoOwner string - ) - BeforeEach(func() { - repoOwner = "test_user" - c = &GithubStepConfig{ - RepoOwner: repoOwner, - } - }) - - Context("GetJenkinsPlugins method", func() { - It("should return github plugin", func() { - plugins := c.GetJenkinsPlugins() - Expect(len(plugins)).Should(Equal(1)) - plugin := plugins[0] - Expect(plugin.Name).Should(Equal("github-branch-source")) - }) - }) - - Context("ConfigJenkins method", func() { - When("create password failed", func() { - var ( - createErrMsg string - ) - BeforeEach(func() { - createErrMsg = "create password failed" - mockClient = &jenkins.MockClient{ - CreatePasswordCredentialError: fmt.Errorf(createErrMsg), - } - }) - It("should return error", func() { - _, err := c.ConfigJenkins(mockClient) - Expect(err).Error().Should(HaveOccurred()) - Expect(err.Error()).Should(Equal(createErrMsg)) - }) - }) - When("create password success", func() { - BeforeEach(func() { - mockClient = &jenkins.MockClient{} - }) - It("should return repoCascConfig", func() { - cascConfig, err := c.ConfigJenkins(mockClient) - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(cascConfig.RepoType).Should(Equal("github")) - }) - }) - }) - Context("ConfigSCM method", func() { - It("should return nil", func() { - scmClient := &scm.MockScmClient{} - err := c.ConfigSCM(scmClient) - Expect(err).Error().ShouldNot(HaveOccurred()) - }) - }) -}) - -var _ = Describe("newGithubStep func", func() { - var ( - pluginConfig *StepGlobalOption - ) - BeforeEach(func() { - pluginConfig = &StepGlobalOption{ - RepoInfo: &git.RepoInfo{ - Owner: "test", - }, - } - }) - It("should return github plugin", func() { - githubPlugin := newGithubStep(pluginConfig) - Expect(githubPlugin.RepoOwner).Should(Equal("test")) - }) -}) diff --git a/internal/pkg/plugin/installer/ci/step/gitlab.go b/internal/pkg/plugin/installer/ci/step/gitlab.go deleted file mode 100644 index 3b6cc2438..000000000 --- a/internal/pkg/plugin/installer/ci/step/gitlab.go +++ /dev/null @@ -1,69 +0,0 @@ -package step - -import ( - "github.com/devstream-io/devstream/pkg/util/jenkins" - "github.com/devstream-io/devstream/pkg/util/log" - "github.com/devstream-io/devstream/pkg/util/scm" -) - -const ( - sshKeyCredentialName = "gitlabSSHKeyCredential" - gitlabCredentialName = "gitlabCredential" - gitlabConnectionName = "gitlabConnection" -) - -type GitlabStepConfig struct { - SSHPrivateKey string `mapstructure:"sshPrivateKey"` - RepoOwner string `mapstructure:"repoOwner"` - BaseURL string `mapstructure:"baseURL"` - Token string `mapstructure:"token"` -} - -func (g *GitlabStepConfig) GetJenkinsPlugins() []*jenkins.JenkinsPlugin { - return []*jenkins.JenkinsPlugin{ - { - Name: "gitlab-plugin", - Version: "1.5.35", - }, - } -} - -func (g *GitlabStepConfig) ConfigJenkins(jenkinsClient jenkins.JenkinsAPI) (*jenkins.RepoCascConfig, error) { - // 1. create ssh credentials - if g.SSHPrivateKey == "" { - log.Warnf("jenkins gitlab ssh key not config, private repo can't be clone") - } else { - err := jenkinsClient.CreateSSHKeyCredential( - sshKeyCredentialName, g.RepoOwner, g.SSHPrivateKey, - ) - if err != nil { - return nil, err - } - } - // 2. create gitlab connection by casc - err := jenkinsClient.CreateGiltabCredential(gitlabCredentialName, g.Token) - if err != nil { - log.Debugf("jenkins preinstall gitlab credentials failed: %s", err) - return nil, err - } - // 3. config gitlab casc - return &jenkins.RepoCascConfig{ - RepoType: "gitlab", - CredentialID: gitlabCredentialName, - GitLabConnectionName: gitlabConnectionName, - GitlabURL: g.BaseURL, - }, nil -} - -func (g *GitlabStepConfig) ConfigSCM(client scm.ClientOperation) error { - return nil -} - -func newGitlabStep(config *StepGlobalOption) *GitlabStepConfig { - return &GitlabStepConfig{ - SSHPrivateKey: config.RepoInfo.SSHPrivateKey, - RepoOwner: config.RepoInfo.GetRepoOwner(), - BaseURL: config.RepoInfo.BaseURL, - Token: config.RepoInfo.Token, - } -} diff --git a/internal/pkg/plugin/installer/ci/step/gitlab_test.go b/internal/pkg/plugin/installer/ci/step/gitlab_test.go deleted file mode 100644 index b76705011..000000000 --- a/internal/pkg/plugin/installer/ci/step/gitlab_test.go +++ /dev/null @@ -1,119 +0,0 @@ -package step - -import ( - "fmt" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/pkg/util/jenkins" - "github.com/devstream-io/devstream/pkg/util/scm" - "github.com/devstream-io/devstream/pkg/util/scm/git" -) - -var _ = Describe("GitlabStepConfig", func() { - var ( - c *GitlabStepConfig - mockClient *jenkins.MockClient - sshKey, repoOwner, baseURL string - ) - BeforeEach(func() { - baseURL = "jenkins_test" - repoOwner = "test_user" - sshKey = "test_key" - c = &GitlabStepConfig{ - BaseURL: baseURL, - RepoOwner: repoOwner, - } - }) - - Context("GetJenkinsPlugins method", func() { - It("should return gitlab plugin", func() { - plugins := c.GetJenkinsPlugins() - Expect(len(plugins)).Should(Equal(1)) - plugin := plugins[0] - Expect(plugin.Name).Should(Equal("gitlab-plugin")) - }) - }) - - Context("ConfigJenkins method", func() { - When("create sshKey failed", func() { - var ( - createErrMsg string - ) - BeforeEach(func() { - createErrMsg = "create ssh key failed" - mockClient = &jenkins.MockClient{ - CreateSSHKeyCredentialError: fmt.Errorf(createErrMsg), - } - c.SSHPrivateKey = sshKey - }) - It("should return error", func() { - _, err := c.ConfigJenkins(mockClient) - Expect(err).Error().Should(HaveOccurred()) - Expect(err.Error()).Should(Equal(createErrMsg)) - }) - }) - When("not use ssh key", func() { - BeforeEach(func() { - mockClient = &jenkins.MockClient{} - }) - When("create gitlabCredential failed", func() { - var ( - createErrMsg string - ) - BeforeEach(func() { - createErrMsg = "create gitlabCredential failed" - mockClient.CreateGiltabCredentialError = fmt.Errorf(createErrMsg) - }) - It("should return error", func() { - _, err := c.ConfigJenkins(mockClient) - Expect(err).Error().Should(HaveOccurred()) - Expect(err.Error()).Should(Equal(createErrMsg)) - }) - }) - When("create gitlabCredential success", func() { - It("should return repoCascConfig", func() { - cascConfig, err := c.ConfigJenkins(mockClient) - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(cascConfig.RepoType).Should(Equal("gitlab")) - Expect(cascConfig.CredentialID).Should(Equal("gitlabCredential")) - Expect(cascConfig.GitLabConnectionName).Should(Equal("gitlabConnection")) - Expect(cascConfig.GitlabURL).Should(Equal(baseURL)) - }) - }) - }) - }) - Context("ConfigSCM method", func() { - It("should return nil", func() { - scmClient := &scm.MockScmClient{} - err := c.ConfigSCM(scmClient) - Expect(err).Error().ShouldNot(HaveOccurred()) - }) - }) -}) - -var _ = Describe("newGitlabStep func", func() { - var ( - pluginConfig *StepGlobalOption - sshKey, owner, baseURL string - ) - BeforeEach(func() { - owner = "test_owner" - sshKey = "test_key" - baseURL = "http://base.com" - pluginConfig = &StepGlobalOption{ - RepoInfo: &git.RepoInfo{ - Owner: owner, - SSHPrivateKey: sshKey, - BaseURL: baseURL, - }, - } - }) - It("should return gitlab plugin", func() { - githubPlugin := newGitlabStep(pluginConfig) - Expect(githubPlugin.RepoOwner).Should(Equal(owner)) - Expect(githubPlugin.SSHPrivateKey).Should(Equal(sshKey)) - Expect(githubPlugin.BaseURL).Should(Equal(baseURL)) - }) -}) diff --git a/internal/pkg/plugin/installer/ci/step/imagerepo.go b/internal/pkg/plugin/installer/ci/step/imagerepo.go deleted file mode 100644 index 952e20b6b..000000000 --- a/internal/pkg/plugin/installer/ci/step/imagerepo.go +++ /dev/null @@ -1,92 +0,0 @@ -package step - -import ( - "encoding/base64" - "fmt" - - "github.com/devstream-io/devstream/pkg/util/jenkins" - "github.com/devstream-io/devstream/pkg/util/k8s" - "github.com/devstream-io/devstream/pkg/util/log" - "github.com/devstream-io/devstream/pkg/util/scm" -) - -const ( - // imageRepoDockerSecretName is used for creating k8s secret - // and it will be used by jenkins for mount - imageRepoDockerSecretName = "image-repo-auth" - // imageRepoSecretName is used for github action secret - imageRepoSecretName = "IMAGE_REPO_SECRET" - imageRepoUserName = "IMAGE_REPO_USER" -) - -type ImageRepoStepConfig struct { - URL string `mapstructure:"url"` - User string `mapstructure:"user"` - Password string `mapstructure:"password"` -} - -func (g *ImageRepoStepConfig) GetJenkinsPlugins() []*jenkins.JenkinsPlugin { - return []*jenkins.JenkinsPlugin{} -} - -// imageRepo config will create kubernetes secret for docker auth -// pipeline in jenkins will mount this secret to login image repo -func (g *ImageRepoStepConfig) ConfigJenkins(jenkinsClient jenkins.JenkinsAPI) (*jenkins.RepoCascConfig, error) { - log.Info("jenkins plugin imageRepo start config...") - secretData, err := g.generateDockerAuthSecretData() - if err != nil { - return nil, err - } - // use k8s client to create secret - client, err := k8s.NewClient() - if err != nil { - return nil, err - } - namespace := jenkinsClient.GetBasicInfo().Namespace - _, err = client.ApplySecret(imageRepoDockerSecretName, namespace, secretData, nil) - log.Debugf("jenkins imageRepo secret %s/%s create status: %+v", namespace, imageRepoSecretName, err) - return nil, err -} - -func (g *ImageRepoStepConfig) ConfigSCM(client scm.ClientOperation) error { - if g.Password == "" { - return fmt.Errorf("config field password is not set") - } - - if err := client.AddRepoSecret(imageRepoUserName, g.User); err != nil { - return err - } - return client.AddRepoSecret(imageRepoSecretName, g.Password) -} - -func (g *ImageRepoStepConfig) generateDockerAuthSecretData() (map[string][]byte, error) { - if g.Password == "" { - return nil, fmt.Errorf("config field password is not set") - } - tmpStr := fmt.Sprintf("%s:%s", g.User, g.Password) - authStr := base64.StdEncoding.EncodeToString([]byte(tmpStr)) - authURL := g.GetImageRepoURL() - log.Debugf("jenkins plugin imageRepo auth string: %s.", authStr) - - configJsonStrTpl := `{ - "auths": { - "%s": { - "auth": "%s" - } - } -}` - configJsonStr := fmt.Sprintf(configJsonStrTpl, authURL, authStr) - log.Debugf("config.json: %s.", configJsonStr) - - return map[string][]byte{ - "config.json": []byte(configJsonStr), - }, nil -} - -func (g *ImageRepoStepConfig) GetImageRepoURL() string { - if g.URL == "" { - // default use docker image repo - return "https://index.docker.io/v1/" - } - return g.URL -} diff --git a/internal/pkg/plugin/installer/ci/step/imagerepo_test.go b/internal/pkg/plugin/installer/ci/step/imagerepo_test.go deleted file mode 100644 index 962ead96f..000000000 --- a/internal/pkg/plugin/installer/ci/step/imagerepo_test.go +++ /dev/null @@ -1,107 +0,0 @@ -package step - -import ( - "fmt" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/pkg/util/scm" -) - -var _ = Describe("ImageRepoStepConfig", func() { - var ( - c *ImageRepoStepConfig - url, user string - ) - BeforeEach(func() { - url = "jenkins_test" - user = "test_user" - c = &ImageRepoStepConfig{ - URL: url, - User: user, - } - }) - - Context("GetJenkinsPlugins method", func() { - It("should be empty", func() { - plugins := c.GetJenkinsPlugins() - Expect(len(plugins)).Should(BeZero()) - }) - }) - - Context("generateDockerAuthSecretData method", func() { - When("image env is not set", func() { - BeforeEach(func() { - c = &ImageRepoStepConfig{ - URL: url, - User: user, - } - }) - It("should return error", func() { - _, err := c.generateDockerAuthSecretData() - Expect(err).Error().Should(HaveOccurred()) - Expect(err.Error()).Should(Equal("config field password is not set")) - }) - }) - When("image env is set", func() { - BeforeEach(func() { - c = &ImageRepoStepConfig{ - URL: url, - User: user, - Password: "test", - } - }) - It("should return image auth data", func() { - d, err := c.generateDockerAuthSecretData() - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(len(d)).Should(Equal(1)) - configJson, ok := d["config.json"] - Expect(ok).Should(BeTrue()) - - expectStr := fmt.Sprintf(`{ - "auths": { - "%s": { - "auth": "%s" - } - } -}`, c.URL, "dGVzdF91c2VyOnRlc3Q=") - Expect(string(configJson)).Should(Equal(expectStr)) - }) - }) - }) - - Context("ConfigSCM method", func() { - var ( - scmClient *scm.MockScmClient - ) - When("imageRepoPassword is not valid", func() { - BeforeEach(func() { - c = &ImageRepoStepConfig{ - URL: url, - User: user, - } - scmClient = &scm.MockScmClient{} - }) - It("should return error", func() { - err := c.ConfigSCM(scmClient) - Expect(err).Error().Should(HaveOccurred()) - Expect(err.Error()).Should(Equal("config field password is not set")) - }) - }) - When("all valid", func() { - BeforeEach(func() { - scmClient = &scm.MockScmClient{} - c = &ImageRepoStepConfig{ - URL: url, - User: user, - Password: "test", - } - }) - It("should return nil", func() { - err := c.ConfigSCM(scmClient) - Expect(err).Error().ShouldNot(HaveOccurred()) - }) - }) - }) -}) diff --git a/internal/pkg/plugin/installer/ci/step/plugins_mock.go b/internal/pkg/plugin/installer/ci/step/plugins_mock.go deleted file mode 100644 index 1133f32df..000000000 --- a/internal/pkg/plugin/installer/ci/step/plugins_mock.go +++ /dev/null @@ -1,24 +0,0 @@ -package step - -import ( - "github.com/devstream-io/devstream/pkg/util/jenkins" - "github.com/devstream-io/devstream/pkg/util/scm" -) - -type MockPluginsConfig struct { - ConfigVar *jenkins.RepoCascConfig - ConfigErr error -} - -func (m *MockPluginsConfig) GetJenkinsPlugins() (plugins []*jenkins.JenkinsPlugin) { - return -} -func (m *MockPluginsConfig) ConfigJenkins(jenkins.JenkinsAPI) (*jenkins.RepoCascConfig, error) { - if m.ConfigErr != nil { - return nil, m.ConfigErr - } - return m.ConfigVar, nil -} -func (m *MockPluginsConfig) ConfigSCM(scm.ClientOperation) error { - return nil -} diff --git a/internal/pkg/plugin/installer/ci/step/sonarqube.go b/internal/pkg/plugin/installer/ci/step/sonarqube.go deleted file mode 100644 index ce1f99ddb..000000000 --- a/internal/pkg/plugin/installer/ci/step/sonarqube.go +++ /dev/null @@ -1,49 +0,0 @@ -package step - -import ( - "github.com/devstream-io/devstream/pkg/util/jenkins" - "github.com/devstream-io/devstream/pkg/util/log" - "github.com/devstream-io/devstream/pkg/util/scm" -) - -const ( - sonarSecretKey = "SONAR_SECRET_TOKEN" -) - -type SonarQubeStepConfig struct { - Name string `mapstructure:"name" validate:"required"` - Token string `mapstructure:"token"` - URL string `mapstructure:"url" validate:"url"` -} - -func (g *SonarQubeStepConfig) GetJenkinsPlugins() []*jenkins.JenkinsPlugin { - return []*jenkins.JenkinsPlugin{ - { - Name: "sonar", - Version: "2.14", - }, - } -} - -func (g *SonarQubeStepConfig) ConfigJenkins(jenkinsClient jenkins.JenkinsAPI) (*jenkins.RepoCascConfig, error) { - log.Info("jenkins plugin sonarqube start config...") - // 1. install token credential - err := jenkinsClient.CreateSecretCredential( - g.Name, - g.Token, - ) - if err != nil { - log.Debugf("jenkins preinstall github credentials failed: %s", err) - return nil, err - } - // 2. config sonarqube casc - return &jenkins.RepoCascConfig{ - SonarqubeURL: g.URL, - SonarqubeName: g.Name, - SonarTokenCredentialID: sonarSecretKey, - }, nil -} - -func (s *SonarQubeStepConfig) ConfigSCM(client scm.ClientOperation) error { - return client.AddRepoSecret(sonarSecretKey, s.Token) -} diff --git a/internal/pkg/plugin/installer/ci/step/sonarqube_test.go b/internal/pkg/plugin/installer/ci/step/sonarqube_test.go deleted file mode 100644 index 5e1ff5908..000000000 --- a/internal/pkg/plugin/installer/ci/step/sonarqube_test.go +++ /dev/null @@ -1,76 +0,0 @@ -package step_test - -import ( - "fmt" - - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/step" - "github.com/devstream-io/devstream/pkg/util/jenkins" - "github.com/devstream-io/devstream/pkg/util/scm" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -var _ = Describe("SonarQubeStepConfig", func() { - var ( - c *step.SonarQubeStepConfig - mockClient *jenkins.MockClient - name, url, token string - ) - BeforeEach(func() { - name = "test" - token = "test_token" - url = "test_url" - c = &step.SonarQubeStepConfig{ - Name: name, - Token: token, - URL: url, - } - }) - Context("GetJenkinsPlugins method", func() { - It("should return sonar plugin", func() { - plugins := c.GetJenkinsPlugins() - Expect(len(plugins)).Should(Equal(1)) - plugin := plugins[0] - Expect(plugin.Name).Should(Equal("sonar")) - }) - }) - - Context("ConfigJenkins method", func() { - When("Create secret failed", func() { - var ( - errMsg string - ) - BeforeEach(func() { - errMsg = "create secret failed" - mockClient = &jenkins.MockClient{ - CreateSecretCredentialError: fmt.Errorf(errMsg), - } - }) - It("should return error", func() { - _, err := c.ConfigJenkins(mockClient) - Expect(err).Error().Should(HaveOccurred()) - Expect(err).Error().Should(HaveOccurred()) - }) - }) - When("all config work noraml", func() { - BeforeEach(func() { - mockClient = &jenkins.MockClient{} - }) - It("should return nil", func() { - cascConfig, err := c.ConfigJenkins(mockClient) - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(cascConfig.SonarqubeURL).Should(Equal(url)) - Expect(cascConfig.SonarqubeName).Should(Equal(name)) - Expect(cascConfig.SonarTokenCredentialID).Should(Equal("SONAR_SECRET_TOKEN")) - }) - }) - }) - Context("ConfigSCM method", func() { - It("should return nil", func() { - scmClient := &scm.MockScmClient{} - err := c.ConfigSCM(scmClient) - Expect(err).Error().ShouldNot(HaveOccurred()) - }) - }) -}) diff --git a/internal/pkg/plugin/installer/ci/step/step.go b/internal/pkg/plugin/installer/ci/step/step.go deleted file mode 100644 index d29723772..000000000 --- a/internal/pkg/plugin/installer/ci/step/step.go +++ /dev/null @@ -1,89 +0,0 @@ -package step - -import ( - "reflect" - - "github.com/devstream-io/devstream/pkg/util/jenkins" - "github.com/devstream-io/devstream/pkg/util/log" - "github.com/devstream-io/devstream/pkg/util/scm" - "github.com/devstream-io/devstream/pkg/util/scm/git" -) - -type StepConfigAPI interface { - GetJenkinsPlugins() []*jenkins.JenkinsPlugin - ConfigJenkins(jenkinsClient jenkins.JenkinsAPI) (*jenkins.RepoCascConfig, error) - ConfigSCM(client scm.ClientOperation) error -} - -type StepGlobalOption struct { - RepoInfo *git.RepoInfo -} - -type StepGlobalVars struct { - DingTalkSecretKey string `mapstructure:"DingTalkSecretKey"` - DingTalkSecretToken string `mapstructure:"DingTalkSecretToken"` - ImageRepoSecret string `mapstructure:"ImageRepoSecret"` - ImageRepoDockerSecret string `mapstructure:"ImageRepoDockerSecret"` - CredentialID string `mapstructure:"StepGlobalVars"` - SonarqubeSecretKey string `mapstructure:"SonarqubeSecretKey"` - GitlabConnectionID string `mapstructure:"GitlabConnectionID"` - RepoType string `mapstructure:"RepoType"` -} - -// GetStepGlobalVars get global config vars for step -func GetStepGlobalVars(repoInfo *git.RepoInfo) *StepGlobalVars { - v := &StepGlobalVars{ - ImageRepoSecret: imageRepoSecretName, - ImageRepoDockerSecret: imageRepoDockerSecretName, - DingTalkSecretKey: dingTalkSecretVal, - DingTalkSecretToken: dingTalkSecretToken, - SonarqubeSecretKey: sonarSecretKey, - GitlabConnectionID: gitlabConnectionName, - RepoType: repoInfo.RepoType, - } - // config credentialID for jenkins if SSHPrivateKey is configured - switch repoInfo.RepoType { - case "github": - v.CredentialID = githubCredentialName - case "gitlab": - if repoInfo.SSHPrivateKey != "" { - v.CredentialID = gitlabCredentialName - } - } - return v -} - -func ExtractValidStepConfig(p any) []StepConfigAPI { - var stepConfigs []StepConfigAPI - // 1. add pipeline plugin - v := reflect.ValueOf(p) - if v.Kind() == reflect.Pointer { - v = v.Elem() - } - for i := 0; i < v.NumField(); i++ { - valueField := v.Field(i) - if valueField.Kind() == reflect.Ptr && !valueField.IsNil() { - fieldVal, ok := valueField.Interface().(StepConfigAPI) - if ok { - stepConfigs = append(stepConfigs, fieldVal) - } else { - log.Warnf("jenkins extract pipeline plugins failed: %+v", valueField) - } - } - } - return stepConfigs -} - -func GetRepoStepConfig(repoInfo *git.RepoInfo) []StepConfigAPI { - var stepConfigs []StepConfigAPI - plugGlobalConfig := &StepGlobalOption{ - RepoInfo: repoInfo, - } - switch repoInfo.RepoType { - case "gitlab": - stepConfigs = append(stepConfigs, newGitlabStep(plugGlobalConfig)) - case "github": - stepConfigs = append(stepConfigs, newGithubStep(plugGlobalConfig)) - } - return stepConfigs -} diff --git a/internal/pkg/plugin/installer/ci/step/step_suite_test.go b/internal/pkg/plugin/installer/ci/step/step_suite_test.go deleted file mode 100644 index b6e13b0b8..000000000 --- a/internal/pkg/plugin/installer/ci/step/step_suite_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package step_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestCommon(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "common CI Base Suite") -} diff --git a/internal/pkg/plugin/installer/ci/step/step_test.go b/internal/pkg/plugin/installer/ci/step/step_test.go deleted file mode 100644 index 911fe1919..000000000 --- a/internal/pkg/plugin/installer/ci/step/step_test.go +++ /dev/null @@ -1,113 +0,0 @@ -package step_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/step" - "github.com/devstream-io/devstream/pkg/util/scm/git" -) - -var _ = Describe("GetStepGlobalVars func", func() { - var ( - repoInfo *git.RepoInfo - ) - BeforeEach(func() { - repoInfo = &git.RepoInfo{} - }) - When("repo type is gitlab and ssh key is not empty", func() { - BeforeEach(func() { - repoInfo.RepoType = "gitlab" - repoInfo.SSHPrivateKey = "test" - }) - It("should return gitlab ssh key", func() { - v := step.GetStepGlobalVars(repoInfo) - Expect(v.CredentialID).Should(Equal("gitlabCredential")) - }) - }) - When("repo type is github", func() { - BeforeEach(func() { - repoInfo.RepoType = "github" - }) - It("should return github ssh key", func() { - v := step.GetStepGlobalVars(repoInfo) - Expect(v.CredentialID).Should(Equal("githubCredential")) - }) - }) - When("repo type is not valid", func() { - BeforeEach(func() { - repoInfo.RepoType = "not exist" - }) - It("should return empty", func() { - v := step.GetStepGlobalVars(repoInfo) - Expect(v.ImageRepoSecret).Should(Equal("IMAGE_REPO_SECRET")) - }) - }) -}) - -var _ = Describe("ExtractValidStepConfig func", func() { - type mockPlugin struct { - ImageRepo *step.ImageRepoStepConfig - } - var ( - p mockPlugin - imageURL string - ) - BeforeEach(func() { - imageURL = "test" - p = mockPlugin{ - ImageRepo: &step.ImageRepoStepConfig{ - URL: imageURL, - }, - } - }) - When("input type is pointer", func() { - It("should return field", func() { - stepAPI := step.ExtractValidStepConfig(&p) - Expect(len(stepAPI)).Should(Equal(1)) - }) - }) - When("input type is struct", func() { - It("should return field", func() { - stepAPI := step.ExtractValidStepConfig(p) - Expect(len(stepAPI)).Should(Equal(1)) - }) - }) -}) - -var _ = Describe("GetRepoStepConfig func", func() { - var r *git.RepoInfo - When("repo type is github", func() { - BeforeEach(func() { - r = &git.RepoInfo{ - RepoType: "github", - } - }) - It("should return github stepConfig", func() { - s := step.GetRepoStepConfig(r) - Expect(len(s)).Should(Equal(1)) - }) - }) - When("repo type is gitlab", func() { - BeforeEach(func() { - r = &git.RepoInfo{ - RepoType: "gitlab", - } - }) - It("should return gitlab stepConfig", func() { - s := step.GetRepoStepConfig(r) - Expect(len(s)).Should(Equal(1)) - }) - }) - When("repo type is not valid", func() { - BeforeEach(func() { - r = &git.RepoInfo{ - RepoType: "not_exist", - } - }) - It("should return empty stepConfig", func() { - s := step.GetRepoStepConfig(r) - Expect(len(s)).Should(Equal(0)) - }) - }) -}) diff --git a/internal/pkg/plugin/installer/docker/compose.go b/internal/pkg/plugin/installer/docker/compose.go deleted file mode 100644 index 497651e9e..000000000 --- a/internal/pkg/plugin/installer/docker/compose.go +++ /dev/null @@ -1,33 +0,0 @@ -package docker - -import ( - "fmt" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/statemanager" -) - -func ComposeUp(options configmanager.RawOptions) error { - if err := op.ComposeUp(); err != nil { - return fmt.Errorf("failed to run containers: %s", err) - } - - return nil -} - -func ComposeDown(options configmanager.RawOptions) error { - if err := op.ComposeDown(); err != nil { - return fmt.Errorf("failed to down containers: %s", err) - } - - return nil -} - -func ComposeStatus(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - state, err := op.ComposeState() - if err != nil { - return nil, fmt.Errorf("failed to get containers state: %s", err) - } - - return state, nil -} diff --git a/internal/pkg/plugin/installer/docker/docker_suite_test.go b/internal/pkg/plugin/installer/docker/docker_suite_test.go deleted file mode 100644 index 4001151b1..000000000 --- a/internal/pkg/plugin/installer/docker/docker_suite_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package docker_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestDocker(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Docker Suite") -} diff --git a/internal/pkg/plugin/installer/docker/installer.go b/internal/pkg/plugin/installer/docker/installer.go deleted file mode 100644 index 3bf03bf7b..000000000 --- a/internal/pkg/plugin/installer/docker/installer.go +++ /dev/null @@ -1,138 +0,0 @@ -package docker - -import ( - "fmt" - - "go.uber.org/multierr" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/pkg/util/docker" - "github.com/devstream-io/devstream/pkg/util/docker/dockersh" - "github.com/devstream-io/devstream/pkg/util/log" -) - -var op docker.Operator = &dockersh.ShellOperator{} - -// Install runs the docker container -func Install(options configmanager.RawOptions) error { - opts, err := NewOptions(options) - if err != nil { - return err - } - - // 1. pull the image if it not exists - if !op.ImageIfExist(opts.GetImageNameWithTag()) { - if err := op.ImagePull(opts.GetImageNameWithTag()); err != nil { - log.Debugf("Failed to pull the image: %s.", err) - return err - } - } - - // 2. try to run the container - log.Infof("Running container as the name <%s>", opts.ContainerName) - if err := op.ContainerRun(opts.GetRunOpts()); err != nil { - return fmt.Errorf("failed to run container: %v", err) - } - - // 3. check if the volume is created successfully - mounts, err := op.ContainerListMounts(opts.ContainerName) - if err != nil { - return fmt.Errorf("failed to get container mounts: %v", err) - } - volumes := mounts.ExtractSources() - if docker.IfVolumesDiffer(volumes, opts.Volumes.ExtractHostPaths()) { - return fmt.Errorf("failed to create volumes") - } - - return nil -} - -// ClearWhenInterruption will delete the container if the container fails to run -func ClearWhenInterruption(options configmanager.RawOptions) error { - opts, err := NewOptions(options) - if err != nil { - return err - } - - // 1. stop the container if it is running - if ok := op.ContainerIfRunning(opts.ContainerName); ok { - if err := op.ContainerStop(opts.ContainerName); err != nil { - log.Errorf("Failed to stop container %s: %v", opts.ContainerName, err) - } - } - - // 2. remove the container if it exists - if ok := op.ContainerIfExist(opts.ContainerName); ok { - if err := op.ContainerRemove(opts.ContainerName); err != nil { - log.Errorf("failed to remove container %v: %v", opts.ContainerName, err) - } - } - - var errs []error - - // 3. check if the container is stopped - if ok := op.ContainerIfRunning(opts.ContainerName); ok { - errs = append(errs, fmt.Errorf("failed to stop container %s", opts.ContainerName)) - } - - // 4. check if the container is removed - if ok := op.ContainerIfExist(opts.ContainerName); ok { - errs = append(errs, fmt.Errorf("failed to delete container %s", opts.ContainerName)) - } - - return multierr.Combine(errs...) -} - -// DeleteAll will delete the container/volumes -func DeleteAll(options configmanager.RawOptions) error { - opts, err := NewOptions(options) - if err != nil { - return err - } - - // 1. stop the container if it is running - if ok := op.ContainerIfRunning(opts.ContainerName); ok { - if err := op.ContainerStop(opts.ContainerName); err != nil { - log.Errorf("Failed to stop container %s: %v", opts.ContainerName, err) - } - } - - // 2. remove the container if it exists - if ok := op.ContainerIfExist(opts.ContainerName); ok { - if err := op.ContainerRemove(opts.ContainerName); err != nil { - log.Errorf("failed to remove container %v: %v", opts.ContainerName, err) - } - } - - // 3. remove the volume if it exists - if *opts.RmDataAfterDelete { - volumesDirFromOptions := opts.Volumes.ExtractHostPaths() - for _, err := range RemoveDirs(volumesDirFromOptions) { - log.Error(err) - } - } - - var errs []error - - // 4. check if the container is stopped and deleted - if ok := op.ContainerIfRunning(opts.ContainerName); ok { - errs = append(errs, fmt.Errorf("failed to stop container %s", opts.ContainerName)) - } - - // 5. check if the container is removed - if ok := op.ContainerIfExist(opts.ContainerName); ok { - errs = append(errs, fmt.Errorf("failed to delete container %s", opts.ContainerName)) - } - - // 6. check if the volume is removed - if *opts.RmDataAfterDelete { - volumesDirFromOptions := opts.Volumes.ExtractHostPaths() - for _, volume := range volumesDirFromOptions { - if exist := PathExist(volume); exist { - errs = append(errs, fmt.Errorf("failed to delete volume %s", volume)) - } - } - } - - return multierr.Combine(errs...) -} diff --git a/internal/pkg/plugin/installer/docker/option.go b/internal/pkg/plugin/installer/docker/option.go deleted file mode 100644 index b7b4db9f7..000000000 --- a/internal/pkg/plugin/installer/docker/option.go +++ /dev/null @@ -1,61 +0,0 @@ -package docker - -import ( - "fmt" - - "github.com/mitchellh/mapstructure" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/pkg/util/docker" - "github.com/devstream-io/devstream/pkg/util/log" - "github.com/devstream-io/devstream/pkg/util/validator" -) - -type Options struct { - ImageName string `validate:"required"` - ImageTag string `validate:"required"` - ContainerName string `validate:"required"` - RmDataAfterDelete *bool - - RunParams []string - Hostname string - PortPublishes []docker.PortPublish - Volumes docker.Volumes - RestartAlways bool -} - -// NewOptions create options by raw options -func NewOptions(options configmanager.RawOptions) (Options, error) { - var opts Options - if err := mapstructure.Decode(options, &opts); err != nil { - return opts, err - } - return opts, nil -} - -func (opts *Options) GetImageNameWithTag() string { - return fmt.Sprintf("%s:%s", opts.ImageName, opts.ImageTag) -} - -func (opts *Options) GetRunOpts() *docker.RunOptions { - return &docker.RunOptions{ - ImageName: opts.ImageName, - ImageTag: opts.ImageTag, - ContainerName: opts.ContainerName, - Hostname: opts.Hostname, - PortPublishes: opts.PortPublishes, - Volumes: opts.Volumes, - RestartAlways: opts.RestartAlways, - } -} -func Validate(options configmanager.RawOptions) (configmanager.RawOptions, error) { - opts, err := NewOptions(options) - if err != nil { - return nil, err - } - log.Debugf("Options: %v.", opts) - if err := validator.CheckStructError(opts).Combine(); err != nil { - return nil, err - } - return options, nil -} diff --git a/internal/pkg/plugin/installer/docker/state.go b/internal/pkg/plugin/installer/docker/state.go deleted file mode 100644 index 4b0da75f2..000000000 --- a/internal/pkg/plugin/installer/docker/state.go +++ /dev/null @@ -1,92 +0,0 @@ -package docker - -import ( - "fmt" - - "github.com/mitchellh/mapstructure" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/docker" - "github.com/devstream-io/devstream/pkg/util/log" -) - -type State struct { - ContainerRunning bool - Volumes []string - Hostname string - PortPublishes []docker.PortPublish -} - -func (s *State) ToMap() (map[string]interface{}, error) { - m := make(map[string]interface{}) - err := mapstructure.Decode(s, &m) - if err != nil { - return nil, fmt.Errorf("failed to convert state to map: %v", err) - } - - return m, nil -} - -func GetStaticStatus(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - opts, err := NewOptions(options) - if err != nil { - return nil, err - } - - staticState := &State{ - ContainerRunning: true, - Volumes: opts.Volumes.ExtractHostPaths(), - Hostname: opts.Hostname, - PortPublishes: opts.PortPublishes, - } - - return staticState.ToMap() -} - -func GetRunningStatus(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - opts, err := NewOptions(options) - if err != nil { - return nil, err - } - - // 1. get running status - running := op.ContainerIfRunning(opts.ContainerName) - if !running { - return map[string]interface{}{}, nil - } - - // 2. get volumes - mounts, err := op.ContainerListMounts(opts.ContainerName) - if err != nil { - // `Read` shouldn't return errors even if failed to read ports, volumes, hostname. - // because: - // 1. when the docker is stopped it could cause these errors. - // 2. if Read failed, the following steps contain the docker's restart will be aborted. - log.Errorf("failed to get container mounts: %v", err) - } - volumes := mounts.ExtractSources() - - // 3. get hostname - hostname, err := op.ContainerGetHostname(opts.ContainerName) - if err != nil { - log.Errorf("failed to get container hostname: %v", err) - } - - // 4. get port bindings - portPublishes, err := op.ContainerListPortPublishes(opts.ContainerName) - if err != nil { - log.Errorf("failed to get container port publishes: %v", err) - } - - // if the previous steps failed, the parameters will be empty - // so dtm will find the resource is drifted and restart docker - resource := &State{ - ContainerRunning: running, - Volumes: volumes, - Hostname: hostname, - PortPublishes: portPublishes, - } - - return resource.ToMap() -} diff --git a/internal/pkg/plugin/installer/docker/util.go b/internal/pkg/plugin/installer/docker/util.go deleted file mode 100644 index c310d8a1a..000000000 --- a/internal/pkg/plugin/installer/docker/util.go +++ /dev/null @@ -1,26 +0,0 @@ -package docker - -import ( - "fmt" - "os" -) - -// RemoveDirs removes the all the directories in the given list recursively -func RemoveDirs(dirs []string) []error { - var errs []error - for _, dir := range dirs { - if err := os.RemoveAll(dir); err != nil { - errs = append(errs, fmt.Errorf("failed to remove data %v: %v", dir, err)) - } - } - - return errs -} - -func PathExist(path string) bool { - _, err := os.Stat(path) - if err == nil { - return true - } - return os.IsExist(err) -} diff --git a/internal/pkg/plugin/installer/docker/util_test.go b/internal/pkg/plugin/installer/docker/util_test.go deleted file mode 100644 index 2c227218b..000000000 --- a/internal/pkg/plugin/installer/docker/util_test.go +++ /dev/null @@ -1,68 +0,0 @@ -package docker - -import ( - "os" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -var _ = Describe("RemoveDirs func", func() { - - var ( - errs []error - dirs []string - ) - - AfterEach(func() { - // check directories are removed - for _, dir := range dirs { - _, err := os.Stat(dir) - Expect(os.IsNotExist(err)).To(BeTrue()) - } - }) - - When("the directories are not exist", func() { - - BeforeEach(func() { - dirs = []string{"dir/not/exist/1", "dir/not/exist/2"} - }) - - It("should return no error", func() { - // Remove the directories - errs = RemoveDirs(dirs) - for _, e := range errs { - Expect(e).ToNot(HaveOccurred()) - } - }) - }) - - When("the directories are exist", func() { - - BeforeEach(func() { - // create temp dir, it will be removed automatically after the test - parentDir := GinkgoT().TempDir() - dirs = []string{ - parentDir + "dir1", - parentDir + "dir2", - parentDir + "dir3/dir3-1", - } - // create directories - for _, dir := range dirs { - err := os.MkdirAll(dir, 0755) - Expect(err).ToNot(HaveOccurred()) - } - // create files - _, err := os.CreateTemp(dirs[0], "file1*") - Expect(err).ToNot(HaveOccurred()) - }) - - It("should remove all directories successfully", func() { - // Remove the directories - errs = RemoveDirs(dirs) - for _, e := range errs { - Expect(e).ToNot(HaveOccurred()) - } - }) - }) -}) diff --git a/internal/pkg/plugin/installer/goclient/installer.go b/internal/pkg/plugin/installer/goclient/installer.go deleted file mode 100644 index fc8df5267..000000000 --- a/internal/pkg/plugin/installer/goclient/installer.go +++ /dev/null @@ -1,394 +0,0 @@ -package goclient - -import ( - "fmt" - "strings" - - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - kerr "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/utils/pointer" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/k8s" - "github.com/devstream-io/devstream/pkg/util/log" -) - -// Create namespace by goclient -func DealWithNsWhenInstall(options configmanager.RawOptions) error { - opts, err := NewOptions(options) - if err != nil { - return err - } - - // Namespace should not be empty - if opts.Namespace == "" { - return fmt.Errorf("config options should set namespace config") - } - log.Debugf("Prepare to create the namespace: %s.", opts.Namespace) - - kubeClient, err := k8s.NewClient() - if err != nil { - return err - } - - // Check whether the given namespace already exists. - exist, err := kubeClient.IsNamespaceExists(opts.Namespace) - if err != nil { - log.Debugf("Failed to check whether namespace: %s exists.", opts.Namespace) - return err - } - if exist { - log.Debugf("Namespace: %s already exists.", opts.Namespace) - return nil - } - - // Create new namespace - if err = kubeClient.CreateNamespace(opts.Namespace); err != nil { - log.Debugf("Failed to create the namespace: %s.", opts.Namespace) - return err - } - - log.Debugf("The namespace %s has been created.", opts.Namespace) - return nil -} - -// Deal with resource when errors occur during creation -func DealWithErrWhenInstall(options configmanager.RawOptions) error { - opts, err := NewOptions(options) - if err != nil { - return err - } - - kubeClient, err := k8s.NewClient() - if err != nil { - return err - } - - exist, err := kubeClient.IsDevstreamNS(opts.Namespace) - if err != nil { - log.Debugf("Failed to check whether namespace: %s exists.", opts.Namespace) - return err - } - - // namespace is controlled by dtm, just delete this namespace - if exist { - log.Debugf("Prepare to delete the namespace: %s.", opts.Namespace) - - err := kubeClient.DeleteNamespace(opts.Namespace) - if err != nil { - log.Debugf("Failed to delete the namespace: %s.", opts.Namespace) - return err - } - - log.Debugf("The namespace %s has been deleted.", opts.Namespace) - } - - return nil -} - -// Create persistent volume with hostpath -func CreatePersistentVolumeWrapper(pvPath map[string]string) installer.BaseOperation { - return func(options configmanager.RawOptions) error { - opts, err := NewOptions(options) - if err != nil { - return err - } - - kubeClient, err := k8s.NewClient() - if err != nil { - return err - } - - for _, pv := range opts.PersistentVolumes { - newPVOpt := &k8s.PVOption{ - Name: pv.PVName, - StorageClassName: opts.StorageClassName, - AccessMode: []corev1.PersistentVolumeAccessMode{ - corev1.ReadWriteOnce, - }, - Capacity: pv.PVCapacity, - PersistentVolumeReclaimPolicy: corev1.PersistentVolumeReclaimRetain, - } - - if path, ok := pvPath[newPVOpt.Name]; ok { - newPVOpt.HostPath = path - } - - log.Debugf("Prepare to create persistentVolume: %s.", newPVOpt.Name) - - if err := kubeClient.CreatePersistentVolume(newPVOpt); err != nil { - if !kerr.IsAlreadyExists(err) { - return err - } - log.Infof("The PersistentVolume %s is already exists.", newPVOpt.Name) - } - log.Debugf("The PersistentVolume %s has been created.", newPVOpt.Name) - } - - return nil - } -} - -// Create persistent volume claim -func CreatePersistentVolumeClaim(options configmanager.RawOptions) error { - - opts, err := NewOptions(options) - if err != nil { - return err - } - - kubeClient, err := k8s.NewClient() - if err != nil { - return err - } - - for _, pvc := range opts.PersistentVolumeClaims { - newPVOpt := &k8s.PVCOption{ - Name: pvc.PVCName, - NameSpace: opts.Namespace, - StorageClassName: opts.StorageClassName, - AccessMode: []corev1.PersistentVolumeAccessMode{ - corev1.ReadWriteOnce, - }, - Requirement: corev1.ResourceRequirements{ - Requests: corev1.ResourceList{ - corev1.ResourceName(corev1.ResourceStorage): resource.MustParse(pvc.PVCCapacity), - }, - }, - } - - log.Debugf("Prepare to create persistentVolumeClaim: %s.", newPVOpt.Name) - if err := kubeClient.CreatePersistentVolumeClaim(newPVOpt); err != nil { - if !kerr.IsAlreadyExists(err) { - return err - } - log.Infof("The PersistentVolumeClaim %s is already exists.", newPVOpt.Name) - } - log.Debugf("The PersistentVolumeClaim %s has been created.", newPVOpt.Name) - - } - - return nil -} - -// Create deployment by goclient with label, containerPorts and name -func CreateDeploymentWrapperLabelAndContainerPorts(label map[string]string, containerPorts *[]corev1.ContainerPort, name string) installer.BaseOperation { - return func(options configmanager.RawOptions) error { - - opts, err := NewOptions(options) - if err != nil { - return err - } - - kubeClient, err := k8s.NewClient() - if err != nil { - return err - } - - volumes := opts.genVolumesForDeployment() - envs := opts.genEnvsForDeployment() - - deployment := &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: opts.Deployment.Name, - Labels: label, - }, - Spec: appsv1.DeploymentSpec{ - Replicas: pointer.Int32Ptr(int32(opts.Deployment.Replicas)), - Selector: &metav1.LabelSelector{MatchLabels: label}, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: label, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: name, - Image: opts.Deployment.Image, - Env: envs, - Ports: *containerPorts, - }, - }, - Volumes: volumes, - }, - }, - }, - } - - log.Debugf("The Deployment %s has been created.", deployment.Name) - if err := kubeClient.CreateDeployment(opts.Namespace, deployment); err != nil { - if !kerr.IsAlreadyExists(err) { - return err - } - log.Infof("The Deployment %s is already exists.", deployment.Name) - } - log.Debugf("The Deployment %s has been created.", deployment.Name) - - return nil - } -} - -// Create service by goclient with label and servicePort -func CreateServiceWrapperLabelAndPorts(label map[string]string, svcPort *corev1.ServicePort) installer.BaseOperation { - return func(options configmanager.RawOptions) error { - - opts, err := NewOptions(options) - if err != nil { - return err - } - - kubeClient, err := k8s.NewClient() - if err != nil { - return err - } - - svcPort.NodePort = int32(opts.Service.NodePort) - svc := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: opts.Service.Name, - Labels: label, - }, - Spec: corev1.ServiceSpec{ - Ports: []corev1.ServicePort{*svcPort}, - Selector: label, - Type: corev1.ServiceTypeNodePort, - }, - } - - log.Debugf("The Service %s has been created.", svc.Name) - return kubeClient.CreateService(opts.Namespace, svc) - } -} - -// Check application status by goclient with retry times -func WaitForReady(retry int) installer.BaseOperation { - return func(options configmanager.RawOptions) error { - - opts, err := NewOptions(options) - if err != nil { - return err - } - - kubeClient, err := k8s.NewClient() - if err != nil { - return err - } - - err = kubeClient.WaitForDeploymentReady(retry, opts.Namespace, opts.Deployment.Name) - if err != nil { - return err - } - return nil - } -} - -// Delete plugin by goclient -func Delete(options configmanager.RawOptions) error { - opts, err := NewOptions(options) - if err != nil { - return err - } - - // 1. Create k8s clientset - kubeClient, err := k8s.NewClient() - if err != nil { - return err - } - - // 2. Delete application - if err = deleteApp(kubeClient, &opts); err != nil { - return err - } - - // 3. Delete PVC - log.Debug("Prepare to delete PVC.") - for _, pvc := range opts.PersistentVolumeClaims { - if err = kubeClient.DeletePersistentVolumeClaim(opts.Namespace, pvc.PVCName); err != nil { - if !strings.Contains(err.Error(), "not found") { - return err - } - } - } - - // 4. Delete PV - log.Debug("Prepare to delete PV.") - for _, pv := range opts.PersistentVolumes { - if err = kubeClient.DeletePersistentVolume(pv.PVName); err != nil { - if !strings.Contains(err.Error(), "not found") { - return err - } - } - } - - // 5. Delete namespace only when namespace is controlled by dtm - exist, err := kubeClient.IsDevstreamNS(opts.Namespace) - if err != nil { - log.Debugf("Failed to check whether namespace: %s exists.", opts.Namespace) - return err - } - - if exist { - log.Debug("Prepare to delete namespace.") - if err = kubeClient.DeleteNamespace(opts.Namespace); err != nil { - if !strings.Contains(err.Error(), "not found") { - return err - } - } - } - - return nil -} - -// Delete application by goclient -func DeleteApp(options configmanager.RawOptions) error { - opts, err := NewOptions(options) - if err != nil { - return err - } - - // 1. Create k8s clientset - kubeClient, err := k8s.NewClient() - if err != nil { - return err - } - - // 2. Delete application - if err = deleteApp(kubeClient, &opts); err != nil { - return err - } - - return nil -} - -// GetStatus checks plugin status by goclient -func GetStatus(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - opts, err := NewOptions(options) - if err != nil { - return nil, err - } - - kubeClient, err := k8s.NewClient() - if err != nil { - return nil, err - } - - ready, err := checkDeploymentsAndServicesReady(kubeClient, &opts) - if err != nil { - return nil, err - } - - if !ready { - return statemanager.ResourceStatus{ - "stopped": true, - }, nil - } - - return statemanager.ResourceStatus{ - "running": true, - }, nil -} diff --git a/internal/pkg/plugin/installer/goclient/option.go b/internal/pkg/plugin/installer/goclient/option.go deleted file mode 100644 index 2ad4904da..000000000 --- a/internal/pkg/plugin/installer/goclient/option.go +++ /dev/null @@ -1,54 +0,0 @@ -package goclient - -import ( - "github.com/mitchellh/mapstructure" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" -) - -// Options is the struct for parameters used by the goclient install config. -type Options struct { - Namespace string `validate:"required"` - StorageClassName string `validate:"required"` - PersistentVolumes []*PersistentVolume `validate:"required"` - PersistentVolumeClaims []*PersistentVolumeClaim `validate:"required"` - Deployment *Deployment `validate:"required"` - Service *Service `validate:"required"` -} - -type PersistentVolume struct { - PVName string `validate:"required"` - PVCapacity string `validate:"required"` - HostPath string -} - -type PersistentVolumeClaim struct { - PVCName string `validate:"required"` - PVCCapacity string `validate:"required"` -} - -type Deployment struct { - Name string `validate:"required"` - Replicas int `validate:"required"` - Image string `validate:"required"` - Envs []*Env `validate:"required"` -} - -type Service struct { - Name string `validate:"required"` - NodePort int `validate:"required"` -} - -type Env struct { - Key string `validate:"required"` - Value string `validate:"required"` -} - -// NewOptions create options by raw options -func NewOptions(options configmanager.RawOptions) (Options, error) { - var opts Options - if err := mapstructure.Decode(options, &opts); err != nil { - return opts, err - } - return opts, nil -} diff --git a/internal/pkg/plugin/installer/goclient/utils.go b/internal/pkg/plugin/installer/goclient/utils.go deleted file mode 100644 index a5c7fe135..000000000 --- a/internal/pkg/plugin/installer/goclient/utils.go +++ /dev/null @@ -1,94 +0,0 @@ -package goclient - -import ( - corev1 "k8s.io/api/core/v1" - kerr "k8s.io/apimachinery/pkg/api/errors" - - "github.com/devstream-io/devstream/pkg/util/k8s" - "github.com/devstream-io/devstream/pkg/util/log" -) - -var checkRetryTime = 5 - -// Check whether deployment is ready and service exists -func checkDeploymentsAndServicesReady(kubeClient k8s.K8sAPI, opts *Options) (bool, error) { - namespace := opts.Namespace - deploy := opts.Deployment.Name - svc := opts.Service.Name - - dp, err := kubeClient.GetDeployment(namespace, deploy) - if err != nil { - log.Debugf("Get deployment err: %s", err.Error()) - if kerr.IsNotFound(err) { - return false, nil - } - return false, err - } - - err = kubeClient.WaitForDeploymentReady(checkRetryTime, opts.Namespace, opts.Deployment.Name) - if err != nil { - log.Debugf("The deployment %s is not ready yet.", dp.Name) - return false, nil - } - log.Debugf("The deployment %s is ready.", dp.Name) - - _, err = kubeClient.GetService(namespace, svc) - if err != nil { - log.Debugf("Get service err: %s", err.Error()) - if kerr.IsNotFound(err) { - return false, nil - } - return false, err - } - log.Debugf("The service %s is ready.", svc) - - return true, nil -} - -// Delete application by goclient -func deleteApp(kubeClient k8s.K8sAPI, opts *Options) error { - // 1. Delete service - if err := kubeClient.DeleteService(opts.Namespace, opts.Service.Name); err != nil { - if !kerr.IsNotFound(err) { - return err - } - } - - // 2. Delete deployment - if err := kubeClient.DeleteDeployment(opts.Namespace, opts.Deployment.Name); err != nil { - if !kerr.IsNotFound(err) { - return err - } - } - - return nil -} - -// Generate []corev1.Volume for deployment from Options.PersistentVolumeClaims -func (opts *Options) genVolumesForDeployment() []corev1.Volume { - var v []corev1.Volume - for _, pvc := range opts.PersistentVolumeClaims { - v = append(v, corev1.Volume{ - Name: pvc.PVCName, - VolumeSource: corev1.VolumeSource{ - PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ - ClaimName: pvc.PVCName, - }, - }, - }) - } - - return v -} - -// Generate []corev1.EnvVar for deployment from Options.PersistentVolumeClaims -func (opts *Options) genEnvsForDeployment() []corev1.EnvVar { - var e []corev1.EnvVar - for _, env := range opts.Deployment.Envs { - e = append(e, corev1.EnvVar{ - Name: env.Key, - Value: env.Value, - }) - } - return e -} diff --git a/internal/pkg/plugin/installer/goclient/validate.go b/internal/pkg/plugin/installer/goclient/validate.go deleted file mode 100644 index 20a6b170c..000000000 --- a/internal/pkg/plugin/installer/goclient/validate.go +++ /dev/null @@ -1,18 +0,0 @@ -package goclient - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/pkg/util/validator" -) - -// validate validates the options provided by the core. -func Validate(options configmanager.RawOptions) (configmanager.RawOptions, error) { - opts, err := NewOptions(options) - if err != nil { - return nil, err - } - if err := validator.CheckStructError(opts).Combine(); err != nil { - return nil, err - } - return options, nil -} diff --git a/internal/pkg/plugin/installer/helm/helm_suit_test.go b/internal/pkg/plugin/installer/helm/helm_suit_test.go deleted file mode 100644 index 96ef37b2b..000000000 --- a/internal/pkg/plugin/installer/helm/helm_suit_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package helm_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestPlanmanager(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "common Helm Suite") -} diff --git a/internal/pkg/plugin/installer/helm/installer.go b/internal/pkg/plugin/installer/helm/installer.go deleted file mode 100644 index f2776de15..000000000 --- a/internal/pkg/plugin/installer/helm/installer.go +++ /dev/null @@ -1,77 +0,0 @@ -package helm - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - "github.com/devstream-io/devstream/pkg/util/helm" - "github.com/devstream-io/devstream/pkg/util/k8s" - "github.com/devstream-io/devstream/pkg/util/log" -) - -var ( - DefaultCreateOperations = installer.ExecuteOperations{ - DealWithNsWhenInstall, - InstallOrUpdate, - } - DefaultUpdateOperations = installer.ExecuteOperations{ - InstallOrUpdate, - } - DefaultDeleteOperations = installer.ExecuteOperations{ - Delete, - } - DefaultTerminateOperations = installer.TerminateOperations{ - Delete, - } -) - -// InstallOrUpdate will install or update service by input options -func InstallOrUpdate(options configmanager.RawOptions) error { - opts, err := NewOptions(options) - if err != nil { - return err - } - h, err := helm.NewHelm(opts.GetHelmParam()) - if err != nil { - return err - } - - log.Info("Creating or updating helm chart ...") - if err := h.InstallOrUpgradeChart(); err != nil { - log.Errorf("Failed to install or upgrade the chart: %s.", err) - return err - } - return err -} - -// DealWithNsWhenInstall will create namespace by input options -func DealWithNsWhenInstall(options configmanager.RawOptions) error { - opts, err := NewOptions(options) - if err != nil { - return err - } - log.Debugf("Prepare to create the namespace: %s.", opts.GetNamespace()) - - kubeClient, err := k8s.NewClient() - if err != nil { - return err - } - return kubeClient.UpsertNameSpace(opts.Chart.Namespace) -} - -// Delete will delete service base on input options -func Delete(options configmanager.RawOptions) error { - opts, err := NewOptions(options) - if err != nil { - return err - } - h, err := helm.NewHelm(opts.GetHelmParam()) - if err != nil { - return err - } - - log.Infof("Uninstalling %s helm chart.", opts.GetReleaseName()) - if err = h.UninstallHelmChartRelease(); err != nil { - return err - } - return nil -} diff --git a/internal/pkg/plugin/installer/helm/option.go b/internal/pkg/plugin/installer/helm/option.go deleted file mode 100644 index 5c1093e28..000000000 --- a/internal/pkg/plugin/installer/helm/option.go +++ /dev/null @@ -1,47 +0,0 @@ -package helm - -import ( - "github.com/mitchellh/mapstructure" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/pkg/util/helm" -) - -// Options is the struct for parameters used by the helm install config. -type Options struct { - InstanceID string `mapstructure:"instanceID"` - Repo helm.Repo `mapstructure:"repo"` - Chart helm.Chart `mapstructure:"chart"` - ValuesYaml string `mapstructure:"valuesYaml" validate:"yaml"` -} - -func (opts *Options) GetHelmParam() *helm.HelmParam { - return &helm.HelmParam{ - Repo: opts.Repo, - Chart: opts.Chart, - } -} - -func (opts *Options) GetNamespace() string { - return opts.Chart.Namespace -} - -func (opts *Options) GetReleaseName() string { - return opts.Chart.ReleaseName -} - -func (opts *Options) FillDefaultValue(defaultOpts *Options) { - chart := &opts.Chart - chart.FillDefaultValue(&defaultOpts.Chart) - repo := &opts.Repo - repo.FillDefaultValue(&defaultOpts.Repo) -} - -// NewOptions create options by raw options -func NewOptions(options configmanager.RawOptions) (*Options, error) { - var opts Options - if err := mapstructure.Decode(options, &opts); err != nil { - return nil, err - } - return &opts, nil -} diff --git a/internal/pkg/plugin/installer/helm/option_test.go b/internal/pkg/plugin/installer/helm/option_test.go deleted file mode 100644 index a55f06972..000000000 --- a/internal/pkg/plugin/installer/helm/option_test.go +++ /dev/null @@ -1,83 +0,0 @@ -package helm_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/helm" - helmCommon "github.com/devstream-io/devstream/pkg/util/helm" -) - -var _ = Describe("Options struct", func() { - var ( - testOpts helm.Options - testChartName string - testRepoName string - testNameSpace string - ) - - BeforeEach(func() { - testChartName = "test_chart" - testRepoName = "test_repo" - testNameSpace = "test_nameSpace" - testOpts = helm.Options{ - Chart: helmCommon.Chart{ - ChartName: testChartName, - Namespace: testNameSpace, - }, - Repo: helmCommon.Repo{ - Name: testRepoName, - }, - } - }) - - Context("GetHelmParam method", func() { - It("should pass chart and repo field", func() { - helmParam := testOpts.GetHelmParam() - Expect(helmParam.Chart).Should(Equal(testOpts.Chart)) - Expect(helmParam.Repo).Should(Equal(testOpts.Repo)) - }) - }) - - Context("GetNamespace method", func() { - It("should return chart's nameSpace", func() { - Expect(testOpts.GetNamespace()).Should(Equal(testOpts.Chart.Namespace)) - }) - }) - - Context("GetReleaseName method", func() { - It("should return chart's ReleaseName", func() { - Expect(testOpts.GetReleaseName()).Should(Equal(testOpts.Chart.ReleaseName)) - }) - }) -}) - -var _ = Describe("NewOptions func", func() { - - var ( - inputOptions configmanager.RawOptions - testRepoName string - testChartName string - ) - - BeforeEach(func() { - testRepoName = "test_repo" - testChartName = "test_chart" - inputOptions = map[string]interface{}{ - "repo": map[string]interface{}{ - "name": testRepoName, - }, - "chart": map[string]interface{}{ - "chartName": testChartName, - }, - } - }) - - It("should work normal", func() { - opts, err := helm.NewOptions(inputOptions) - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(opts.Chart.ChartName).Should(Equal(testChartName)) - Expect(opts.Repo.Name).Should(Equal(testRepoName)) - }) -}) diff --git a/internal/pkg/plugin/installer/helm/state.go b/internal/pkg/plugin/installer/helm/state.go deleted file mode 100644 index 579ef2a39..000000000 --- a/internal/pkg/plugin/installer/helm/state.go +++ /dev/null @@ -1,34 +0,0 @@ -package helm - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/helm" - "github.com/devstream-io/devstream/pkg/util/k8s" - "github.com/devstream-io/devstream/pkg/util/log" -) - -// GetAllResourcesStatus will get the State of k8s Deployment, DaemonSet and StatefulSet resources -func GetAllResourcesStatus(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - opts, err := NewOptions(options) - if err != nil { - return nil, err - } - - kubeClient, err := k8s.NewClient() - if err != nil { - log.Debugf("helm init k8s client to get state failed: %+v", err) - return nil, err - } - - anFilter := map[string]string{ - helm.GetAnnotationName(): opts.GetReleaseName(), - } - labelFilter := map[string]string{} - allResourceState, err := kubeClient.GetResourceStatus(opts.GetNamespace(), anFilter, labelFilter) - if err != nil { - log.Debugf("helm get resource state failed: %+v", err) - return nil, err - } - return allResourceState.ToStringInterfaceMap() -} diff --git a/internal/pkg/plugin/installer/helm/validate.go b/internal/pkg/plugin/installer/helm/validate.go deleted file mode 100644 index 60bfee403..000000000 --- a/internal/pkg/plugin/installer/helm/validate.go +++ /dev/null @@ -1,41 +0,0 @@ -package helm - -import ( - "fmt" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - "github.com/devstream-io/devstream/pkg/util/log" - "github.com/devstream-io/devstream/pkg/util/types" - "github.com/devstream-io/devstream/pkg/util/validator" -) - -// Validate validates the options provided by the dtm-core. -func Validate(options configmanager.RawOptions) (configmanager.RawOptions, error) { - opts, err := NewOptions(options) - if err != nil { - return nil, err - } - - var structErrs = validator.CheckStructError(opts) - - if opts.Chart.ChartPath == "" && (opts.Repo.Name == "" || opts.Repo.URL == "" || opts.Chart.ChartName == "") { - log.Debugf("Repo.Name: %s, Repo.URL: %s, Chart.ChartName: %s", opts.Repo.Name, opts.Repo.URL, opts.Chart.ChartName) - err := fmt.Errorf("if chartPath == \"\", then the repo.Name & repo.URL & chart.chartName must be set") - structErrs = append(structErrs, err) - } - - return options, structErrs.Combine() -} - -// SetDefaultConfig will update options empty values base on import options -func SetDefaultConfig(defaultConfig *Options) installer.MutableOperation { - return func(options configmanager.RawOptions) (configmanager.RawOptions, error) { - opts, err := NewOptions(options) - if err != nil { - return nil, err - } - opts.FillDefaultValue(defaultConfig) - return types.EncodeStruct(opts) - } -} diff --git a/internal/pkg/plugin/installer/helm/validate_test.go b/internal/pkg/plugin/installer/helm/validate_test.go deleted file mode 100644 index fd07d71d8..000000000 --- a/internal/pkg/plugin/installer/helm/validate_test.go +++ /dev/null @@ -1,107 +0,0 @@ -package helm_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/helm" - helmCommon "github.com/devstream-io/devstream/pkg/util/helm" - "github.com/devstream-io/devstream/pkg/util/types" -) - -var _ = Describe("Validate func", func() { - var testOption configmanager.RawOptions - - When("options is not valid", func() { - BeforeEach(func() { - testOption = map[string]interface{}{ - "chart": map[string]string{}, - "repo": map[string]string{}, - } - }) - It("should return error", func() { - _, err := helm.Validate(testOption) - Expect(err).Error().Should(HaveOccurred()) - }) - }) - - When("options is valid", func() { - BeforeEach(func() { - testOption = map[string]interface{}{ - "chart": map[string]string{ - "chartName": "test", - }, - "repo": map[string]string{ - "url": "http://test.com", - "name": "test", - }, - } - }) - It("should return success", func() { - opt, err := helm.Validate(testOption) - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(opt).ShouldNot(BeEmpty()) - }) - }) -}) - -var _ = Describe("SetDefaultConfig func", func() { - var ( - testChartName string - testRepoURL string - testRepoName string - testBool *bool - defaultConfig helm.Options - testOptions configmanager.RawOptions - expectChart map[string]interface{} - expectRepo map[string]interface{} - ) - BeforeEach(func() { - testChartName = "test_chart" - testRepoName = "test_repo" - testRepoURL = "http://test.com" - testBool = types.Bool(true) - testOptions = map[string]interface{}{ - "chart": map[string]string{}, - "repo": map[string]string{}, - } - defaultConfig = helm.Options{ - Chart: helmCommon.Chart{ - ChartName: testChartName, - Wait: testBool, - UpgradeCRDs: testBool, - }, - Repo: helmCommon.Repo{ - URL: testRepoURL, - Name: testRepoName, - }, - } - expectChart = map[string]interface{}{ - "chartPath": "", - "chartName": testChartName, - "wait": testBool, - "namespace": "", - "version": "", - "releaseName": "", - "valuesYaml": "", - "timeout": "", - "upgradeCRDs": testBool, - } - expectRepo = map[string]interface{}{ - "url": testRepoURL, - "name": testRepoName, - } - }) - It("should update default value", func() { - updateFunc := helm.SetDefaultConfig(&defaultConfig) - o, err := updateFunc(testOptions) - Expect(err).Error().ShouldNot(HaveOccurred()) - oRepo, exist := o["repo"] - Expect(exist).Should(BeTrue()) - oChart, exist := o["chart"] - Expect(exist).Should(BeTrue()) - Expect(oRepo).Should(Equal(expectRepo)) - Expect(oChart).Should(Equal(expectChart)) - }) -}) diff --git a/internal/pkg/plugin/installer/installer.go b/internal/pkg/plugin/installer/installer.go deleted file mode 100644 index 47e3fc9db..000000000 --- a/internal/pkg/plugin/installer/installer.go +++ /dev/null @@ -1,82 +0,0 @@ -package installer - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" -) - -type ( - // MutableOperation can be used to change options if it is needed - MutableOperation func(options configmanager.RawOptions) (configmanager.RawOptions, error) - // BaseOperation reads options and executes operation - BaseOperation func(options configmanager.RawOptions) error - // StatusGetterOperation reads options and executes operation, then returns the status map - StatusGetterOperation func(options configmanager.RawOptions) (statemanager.ResourceStatus, error) -) - -type ( - PreExecuteOperations []MutableOperation - ExecuteOperations []BaseOperation - TerminateOperations []BaseOperation -) - -type Installer interface { - Execute(options configmanager.RawOptions) (statemanager.ResourceStatus, error) -} - -// Operator knows all the operations and can execute them in order -type Operator struct { - PreExecuteOperations PreExecuteOperations - ExecuteOperations ExecuteOperations - TerminateOperations TerminateOperations - GetStatusOperation StatusGetterOperation -} - -// Execute will sequentially execute all operations in Operator -func (o *Operator) Execute(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - var err error - // 1. Execute PreExecuteOperations. It may changes the options. - log.Debugf("Start to execute PreExecuteOperations...") - for _, preOps := range o.PreExecuteOperations { - options, err = preOps(options) - if err != nil { - return nil, err - } - } - - // 2. Register defer func so that in case ExecuteOperations fails, it can execute TerminateOperations - var execErr error - defer func() { - if execErr == nil { - return - } - log.Debugf("Start to execute TerminateOperations...") - for _, terminateOperation := range o.TerminateOperations { - err := terminateOperation(options) - if err != nil { - log.Errorf("Failed to execute TerminateOperations: %s.", err) - } - } - }() - - // 3. Execute ExecuteOperations in order. It won't change the options. - log.Debugf("Start to execute ExecuteOperations...") - for _, execOps := range o.ExecuteOperations { - execErr = execOps(options) - if execErr != nil { - return nil, execErr - } - } - - // 4. Execute StatusGetterOperation. - var state map[string]interface{} - if o.GetStatusOperation != nil { - log.Debugf("Start to execute StatusGetterOperation...") - state, err = o.GetStatusOperation(options) - if err != nil { - return nil, err - } - } - return state, err -} diff --git a/internal/pkg/plugin/installer/kubectl/installer.go b/internal/pkg/plugin/installer/kubectl/installer.go deleted file mode 100644 index b9943a2d4..000000000 --- a/internal/pkg/plugin/installer/kubectl/installer.go +++ /dev/null @@ -1,82 +0,0 @@ -package kubectl - -import ( - "fmt" - "io" - "strings" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - "github.com/devstream-io/devstream/pkg/util/kubectl" - "github.com/devstream-io/devstream/pkg/util/log" - "github.com/devstream-io/devstream/pkg/util/pkgerror" - "github.com/devstream-io/devstream/pkg/util/template" -) - -func ProcessByContent(action, content string) installer.BaseOperation { - return func(options configmanager.RawOptions) error { - reader, err := renderKubectlContent(content, options) - if err != nil { - return err - } - - return processByIOReader(action, reader) - } -} - -func renderKubectlContent(content string, options configmanager.RawOptions) (io.Reader, error) { - content, err := template.NewRenderClient(&template.TemplateOption{ - Name: "kubectl", - }, template.ContentGetter).Render(content, options) - if err != nil { - return nil, err - } - if content == "" { - return nil, fmt.Errorf("kubectl content is empty") - } - - return strings.NewReader(content), nil -} - -func ProcessByURL(action, url string) installer.BaseOperation { - return func(options configmanager.RawOptions) error { - content, err := template.NewRenderClient(&template.TemplateOption{ - Name: "kubectl", - }, template.URLGetter).Render(url, options) - if err != nil { - return err - } - if content == "" { - return fmt.Errorf("kubectl content is empty") - } - - reader := strings.NewReader(content) - - return processByIOReader(action, reader) - } -} - -func processByIOReader(action string, reader io.Reader) error { - // generate k8s config file for apply - var err error - // kubectl apply -f - switch action { - case kubectl.Create: - err = kubectl.KubeApplyFromIOReader(reader) - case kubectl.Apply: - err = kubectl.KubeApplyFromIOReader(reader) - case kubectl.Delete: - err = kubectl.KubeDeleteFromIOReader(reader) - // ignore resource not exist error - if err != nil && pkgerror.CheckErrorMatchByMessage(err, kubectl.ArgocdApplicationNotExist) { - log.Warnf("kubectl config is already deleted") - err = nil - } - default: - err = fmt.Errorf("kubectl not support this kind of action: %v", action) - } - if err != nil { - return err - } - return nil -} diff --git a/internal/pkg/plugin/installer/kubectl/installer_test.go b/internal/pkg/plugin/installer/kubectl/installer_test.go deleted file mode 100644 index 2f411d4ae..000000000 --- a/internal/pkg/plugin/installer/kubectl/installer_test.go +++ /dev/null @@ -1,89 +0,0 @@ -package kubectl - -import ( - "fmt" - "io" - "net/http" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "github.com/onsi/gomega/ghttp" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - utilKubectl "github.com/devstream-io/devstream/pkg/util/kubectl" -) - -var _ = Describe("renderKubectlContent", func() { - var ( - content string - options configmanager.RawOptions - ) - - BeforeEach(func() { - content = `metadata: - name: "[[ .app.name ]]" - namespace: "[[ .app.namespace ]]" - finalizers: - - resources-finalizer.argocd.argoproj.io -` - options = map[string]interface{}{ - "app": map[string]interface{}{ - "name": "app-name", - "namespace": "app-namespace", - }, - } - }) - - It("should render kubectl content", func() { - - contentExpected := `metadata: - name: "app-name" - namespace: "app-namespace" - finalizers: - - resources-finalizer.argocd.argoproj.io -` - reader, err := renderKubectlContent(content, options) - Expect(err).To(Succeed()) - - bytes, err := io.ReadAll(reader) - - Expect(err).To(Succeed()) - Expect(string(bytes)).To(Equal(contentExpected)) - }) - -}) - -var _ = Describe("ProcessByContent", Ordered, func() { - var options configmanager.RawOptions - var s *ghttp.Server - - BeforeAll(func() { - s = ghttp.NewServer() - s.RouteToHandler("GET", "/", func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, "ok") - }) - }) - AfterAll(func() { - s.Close() - }) - It("should return error if content is empty", func() { - op := ProcessByContent(utilKubectl.Apply, "") - err := op(options) - Expect(err).To(HaveOccurred()) - }) - It("action is kubectl apply", func() { - op := ProcessByURL(utilKubectl.Apply, s.URL()) - err := op(options) - Expect(err).To(HaveOccurred()) - }) - It("action is kubectl create", func() { - op := ProcessByURL(utilKubectl.Create, s.URL()) - err := op(options) - Expect(err).To(HaveOccurred()) - }) - It("action is not support", func() { - op := ProcessByURL("", s.URL()) - err := op(options) - Expect(err).To(HaveOccurred()) - }) -}) diff --git a/internal/pkg/plugin/installer/kubectl/kubectl_suite_test.go b/internal/pkg/plugin/installer/kubectl/kubectl_suite_test.go deleted file mode 100644 index 3af59740e..000000000 --- a/internal/pkg/plugin/installer/kubectl/kubectl_suite_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package kubectl_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestKubectl(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Kubectl Suite") -} diff --git a/internal/pkg/plugin/installer/util/decode.go b/internal/pkg/plugin/installer/util/decode.go deleted file mode 100644 index ba8cf0028..000000000 --- a/internal/pkg/plugin/installer/util/decode.go +++ /dev/null @@ -1,44 +0,0 @@ -package util - -import ( - "fmt" - "reflect" - - "github.com/mitchellh/mapstructure" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/pkg/util/mapz" - "github.com/devstream-io/devstream/pkg/util/scm/git" -) - -func DecodePlugin(rawOptions configmanager.RawOptions, pluginData any) error { - // 1. create a new decode with pluginDecoder config - decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ - DecodeHook: pluginDecoder, - Result: pluginData, - }) - if err != nil { - return fmt.Errorf("create plugin decoder failed: %w", err) - } - // 2. decode rawOptions to structData - if err := decoder.Decode(rawOptions); err != nil { - return fmt.Errorf("decode plugin option failed: %w", err) - } - return nil -} - -func pluginDecoder(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { - switch t { - // set git.RepoInfo default value - case reflect.TypeOf(&git.RepoInfo{}): - repoData := new(git.RepoInfo) - if err := mapstructure.Decode(data, repoData); err != nil { - return nil, err - } - if err := repoData.SetDefault(); err != nil { - return nil, err - } - return mapz.DecodeStructToMap(repoData) - } - return data, nil -} diff --git a/internal/pkg/plugin/installer/util/decode_test.go b/internal/pkg/plugin/installer/util/decode_test.go deleted file mode 100644 index f59385c8c..000000000 --- a/internal/pkg/plugin/installer/util/decode_test.go +++ /dev/null @@ -1,66 +0,0 @@ -package util_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/util" - "github.com/devstream-io/devstream/pkg/util/scm/git" -) - -var _ = Describe("DecodePlugin func", func() { - var ( - plugData *mockStruct - rawData configmanager.RawOptions - ) - When("decoder is not valid", func() { - BeforeEach(func() { - plugData = nil - }) - It("should return error", func() { - err := util.DecodePlugin(rawData, plugData) - Expect(err).Should(HaveOccurred()) - Expect(err.Error()).Should(ContainSubstring("create plugin decoder failed")) - }) - }) - When("options is not valid", func() { - BeforeEach(func() { - plugData = new(mockStruct) - rawData = map[string]any{ - "scm": map[string]any{"key": "not_exist"}, - } - }) - It("should return error", func() { - err := util.DecodePlugin(rawData, plugData) - Expect(err).Should(HaveOccurred()) - Expect(err.Error()).Should(ContainSubstring("decode plugin option failed")) - }) - }) - - When("all params are valid", func() { - BeforeEach(func() { - plugData = new(mockStruct) - rawData = map[string]any{ - "scm": map[string]any{ - "url": "github.com/test/test_repo", - }, - } - }) - - It("should set default value", func() { - err := util.DecodePlugin(rawData, plugData) - Expect(err).ShouldNot(HaveOccurred()) - Expect(plugData).Should(Equal(&mockStruct{ - Scm: &git.RepoInfo{ - Owner: "test", - Repo: "test_repo", - Branch: "main", - RepoType: "github", - CloneURL: "github.com/test/test_repo", - NeedAuth: false, - }, - })) - }) - }) -}) diff --git a/internal/pkg/plugin/installer/util/util_suite_test.go b/internal/pkg/plugin/installer/util/util_suite_test.go deleted file mode 100644 index f889d081d..000000000 --- a/internal/pkg/plugin/installer/util/util_suite_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package util_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/pkg/util/scm/git" -) - -func TestCommon(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Plugin Installer Util Suite") -} - -type mockStruct struct { - Scm *git.RepoInfo `mapstructure:"scm"` - DeepStruct deepStruct `mapstructure:"deepStruct"` -} - -type deepStruct struct { - DeepStr string `mapstructure:"deepStr" validate:"required"` -} diff --git a/internal/pkg/plugin/jenkinspipeline/create.go b/internal/pkg/plugin/jenkinspipeline/create.go deleted file mode 100644 index 620bfd6bb..000000000 --- a/internal/pkg/plugin/jenkinspipeline/create.go +++ /dev/null @@ -1,32 +0,0 @@ -package jenkinspipeline - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/cifile" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" -) - -func Create(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - // Initialize Operator with Operations - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - setJenkinsDefault, - validateJenkins, - }, - ExecuteOperations: installer.ExecuteOperations{ - installPipeline, - cifile.PushCIFiles, - }, - GetStatusOperation: getStatus, - } - - // Execute all Operations in Operator - status, err := operator.Execute(options) - if err != nil { - return nil, err - } - log.Debugf("Return map: %v", status) - return status, nil -} diff --git a/internal/pkg/plugin/jenkinspipeline/delete.go b/internal/pkg/plugin/jenkinspipeline/delete.go deleted file mode 100644 index 26f70c877..000000000 --- a/internal/pkg/plugin/jenkinspipeline/delete.go +++ /dev/null @@ -1,29 +0,0 @@ -package jenkinspipeline - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/cifile" -) - -func Delete(options configmanager.RawOptions) (bool, error) { - // Initialize Operator with Operations - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - setJenkinsDefault, - validateJenkins, - }, - ExecuteOperations: installer.ExecuteOperations{ - // TODO(daniel-hutao): delete secret: docker-config - cifile.DeleteCIFiles, - deletePipeline, - }, - } - _, err := operator.Execute(options) - if err != nil { - return false, err - } - - // 2. return ture if all process success - return true, nil -} diff --git a/internal/pkg/plugin/jenkinspipeline/jenkins.go b/internal/pkg/plugin/jenkinspipeline/jenkins.go deleted file mode 100644 index ca7efd873..000000000 --- a/internal/pkg/plugin/jenkinspipeline/jenkins.go +++ /dev/null @@ -1,81 +0,0 @@ -package jenkinspipeline - -import ( - "errors" - - "github.com/devstream-io/devstream/pkg/util/jenkins" - "github.com/devstream-io/devstream/pkg/util/k8s" - "github.com/devstream-io/devstream/pkg/util/log" -) - -const ( - defaultAdminSecretName = "jenkins" - defaultAdminSecretUserName = "jenkins-admin-user" - defaultAdminSecretUserPassword = "jenkins-admin-password" - jenkinsPasswordEnvKey = "JENKINS_PASSWORD" -) - -type jenkinsOption struct { - URL string `mapstructure:"url" validate:"required,url"` - User string `mapstructure:"user" validate:"required"` - Password string `mapstructure:"password" validate:"required"` - Namespace string `mapstructure:"namespace"` - EnableRestart bool `mapstructure:"enableRestart"` - Offline bool `mapstructure:"offline"` -} - -func (j *jenkinsOption) newClient() (jenkins.JenkinsAPI, error) { - auth, err := j.getBasicAuth() - if err != nil { - return nil, errors.New("jenkins uesrname and password is required") - } - jenkinsConfig := &jenkins.JenkinsConfigOption{ - BasicAuth: auth, - URL: j.URL, - Namespace: j.Namespace, - EnableRestart: j.EnableRestart, - Offline: j.Offline, - } - return jenkins.NewClient(jenkinsConfig) -} - -func (j *jenkinsOption) getBasicAuth() (*jenkins.BasicAuth, error) { - // 1. check username is set and has env password - if j.User != "" && j.Password != "" { - log.Debugf("jenkins get auth token from env") - return &jenkins.BasicAuth{ - Username: j.User, - Password: j.Password, - }, nil - } - // 2. if not set, get user and password from secret - k8sClient, err := k8s.NewClient() - if err == nil { - secretAuth := getAuthFromSecret(k8sClient, j.Namespace) - if secretAuth != nil && secretAuth.CheckNameMatch(j.User) { - log.Debugf("jenkins get auth token from secret") - return secretAuth, nil - } - } - return nil, errors.New("jenkins uesrname and password is required") -} - -func getAuthFromSecret(k8sClient k8s.K8sAPI, namespace string) *jenkins.BasicAuth { - secret, err := k8sClient.GetSecret(namespace, defaultAdminSecretName) - if err != nil { - log.Warnf("jenkins get auth from k8s failed: %+v", err) - return nil - } - user, ok := secret[defaultAdminSecretUserName] - if !ok { - return nil - } - password, ok := secret[defaultAdminSecretUserPassword] - if !ok { - return nil - } - return &jenkins.BasicAuth{ - Username: user, - Password: password, - } -} diff --git a/internal/pkg/plugin/jenkinspipeline/jenkins_test.go b/internal/pkg/plugin/jenkinspipeline/jenkins_test.go deleted file mode 100644 index ec8f849cc..000000000 --- a/internal/pkg/plugin/jenkinspipeline/jenkins_test.go +++ /dev/null @@ -1,130 +0,0 @@ -package jenkinspipeline - -import ( - "fmt" - "net/http" - "os" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "github.com/onsi/gomega/ghttp" - - "github.com/devstream-io/devstream/pkg/util/k8s" -) - -var _ = Describe("jenkinsOption struct", func() { - var ( - url, user, namespace string - opt *jenkinsOption - ) - BeforeEach(func() { - url = "http://test.exmaple.com" - user = "test_user" - namespace = "test_namespace" - opt = &jenkinsOption{ - URL: url, - User: user, - Namespace: namespace, - Password: "changeme", - } - }) - var ( - testPassword string - ) - - Context("getBasicAuth method", func() { - When("env password is setted", func() { - It("should work normal", func() { - auth, err := opt.getBasicAuth() - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(auth).ShouldNot(BeNil()) - Expect(auth.Username).Should(Equal(user)) - Expect(auth.Password).Should(Equal("changeme")) - }) - }) - }) - Context("newClient method", func() { - var ( - s *ghttp.Server - ) - BeforeEach(func() { - s = ghttp.NewServer() - s.RouteToHandler("GET", "/api/json", func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, "ok") - }) - s.RouteToHandler("GET", "/crumbIssuer/api/json/api/json", func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, "ok") - }) - opt.URL = s.URL() - os.Setenv(jenkinsPasswordEnvKey, testPassword) - }) - - It("should work normal", func() { - _, err := opt.newClient() - Expect(err).Error().ShouldNot(HaveOccurred()) - }) - AfterEach(func() { - s.Close() - }) - }) - Context("getAuthFromSecret method", func() { - var ( - m *k8s.MockClient - nameSpace string - ) - BeforeEach(func() { - nameSpace = "test_namespace" - }) - When("GetSecret return error", func() { - BeforeEach(func() { - m = &k8s.MockClient{ - GetSecretError: fmt.Errorf("test error"), - } - }) - It("should return nil", func() { - Expect(getAuthFromSecret(m, nameSpace)).Should(BeNil()) - }) - }) - When("defaultUsername not exist in secret", func() { - BeforeEach(func() { - m = &k8s.MockClient{ - GetSecretValue: map[string]string{ - defaultAdminSecretUserPassword: "test", - }, - } - }) - It("should return nil", func() { - Expect(getAuthFromSecret(m, nameSpace)).Should(BeNil()) - }) - }) - When("defaultPassword not exist in secret", func() { - BeforeEach(func() { - m = &k8s.MockClient{ - GetSecretValue: map[string]string{ - defaultAdminSecretUserName: "test", - }, - } - }) - It("should return nil", func() { - Expect(getAuthFromSecret(m, nameSpace)).Should(BeNil()) - }) - }) - When("GetSecret return error", func() { - nameSpace = "test_namespace" - BeforeEach(func() { - m = &k8s.MockClient{ - GetSecretValue: map[string]string{ - defaultAdminSecretUserName: "test_user", - defaultAdminSecretUserPassword: "test_pass", - }, - } - }) - It("should return nil", func() { - auth := getAuthFromSecret(m, nameSpace) - Expect(auth).ShouldNot(BeNil()) - Expect(auth.Username).Should(Equal("test_user")) - Expect(auth.Password).Should(Equal("test_pass")) - }) - }) - }) -}) diff --git a/internal/pkg/plugin/jenkinspipeline/jenkinspipeline.go b/internal/pkg/plugin/jenkinspipeline/jenkinspipeline.go deleted file mode 100644 index c3a14860d..000000000 --- a/internal/pkg/plugin/jenkinspipeline/jenkinspipeline.go +++ /dev/null @@ -1,68 +0,0 @@ -package jenkinspipeline - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/util" - "github.com/devstream-io/devstream/pkg/util/log" - "github.com/devstream-io/devstream/pkg/util/random" - "github.com/devstream-io/devstream/pkg/util/scm" - "github.com/devstream-io/devstream/pkg/util/scm/git" -) - -func installPipeline(options configmanager.RawOptions) error { - opts := new(jobOptions) - if err := util.DecodePlugin(options, opts); err != nil { - return err - } - - // 1. init jenkins Client - jenkinsClient, err := opts.Jenkins.newClient() - if err != nil { - log.Debugf("jenkins init client failed: %s", err) - return err - } - // 2. generate secretToken for webhook auth - secretToken := random.GenerateRandomSecretToken() - if err := opts.install(jenkinsClient, secretToken); err != nil { - log.Debugf("jenkins install pipeline failed: %s", err) - return err - } - // 3. create or update scm webhook - scmClient, err := scm.NewClientWithAuth(opts.ProjectRepo) - if err != nil { - return err - } - - return scmClient.AddWebhook(&git.WebhookConfig{ - Address: opts.getScmWebhookAddress(), - SecretToken: secretToken, - }) -} - -func deletePipeline(options configmanager.RawOptions) error { - opts := new(jobOptions) - if err := util.DecodePlugin(options, opts); err != nil { - return err - } - - // 1. delete jenkins job - client, err := opts.Jenkins.newClient() - if err != nil { - log.Debugf("jenkins init client failed: %s", err) - return err - } - err = opts.remove(client) - if err != nil { - return err - } - // 2. delete repo webhook - scmClient, err := scm.NewClientWithAuth(opts.ProjectRepo) - if err != nil { - return err - } - - return scmClient.DeleteWebhook(&git.WebhookConfig{ - Address: opts.getScmWebhookAddress(), - SecretToken: "", - }) -} diff --git a/internal/pkg/plugin/jenkinspipeline/jenkinspipeline_suite_test.go b/internal/pkg/plugin/jenkinspipeline/jenkinspipeline_suite_test.go deleted file mode 100644 index 449396a12..000000000 --- a/internal/pkg/plugin/jenkinspipeline/jenkinspipeline_suite_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package jenkinspipeline_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestCommon(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Plugin JenkinsPipeline Suite") -} diff --git a/internal/pkg/plugin/jenkinspipeline/option.go b/internal/pkg/plugin/jenkinspipeline/option.go deleted file mode 100644 index 3be178a11..000000000 --- a/internal/pkg/plugin/jenkinspipeline/option.go +++ /dev/null @@ -1,134 +0,0 @@ -package jenkinspipeline - -import ( - "context" - "fmt" - "strings" - - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/step" - "github.com/devstream-io/devstream/pkg/util/jenkins" - "github.com/devstream-io/devstream/pkg/util/log" -) - -type jobOptions struct { - Jenkins jenkinsOption `mapstructure:"jenkins"` - ci.CIConfig `mapstructure:",squash"` - JobName jenkinsJobName `mapstructure:"jobName"` -} - -func (j *jobOptions) install(jenkinsClient jenkins.JenkinsAPI, secretToken string) error { - // 1. install jenkins plugins - pipelinePlugins := j.extractPlugins() - if err := ensurePluginInstalled(jenkinsClient, pipelinePlugins); err != nil { - return err - } - // 2. config plugins by casc - if err := configPlugins(jenkinsClient, pipelinePlugins); err != nil { - return err - } - // 3. create or update jenkins job - return j.createOrUpdateJob(jenkinsClient, secretToken) -} - -func (j *jobOptions) remove(jenkinsClient jenkins.JenkinsAPI) error { - job, err := jenkinsClient.GetFolderJob(j.JobName.getJobName(), j.JobName.getJobFolder()) - if err != nil { - // check job is already been deleted - if jenkins.IsNotFoundError(err) { - return nil - } - return err - } - _, err = job.Delete(context.Background()) - log.Debugf("jenkins delete job %s status: %v", j.JobName, err) - return nil -} - -func (j *jobOptions) createOrUpdateJob(jenkinsClient jenkins.JenkinsAPI, secretToken string) error { - repoInfo := j.ProjectRepo - globalConfig := step.GetStepGlobalVars(repoInfo) - // 1. render groovy script - jobRenderInfo := &jenkins.JobScriptRenderInfo{ - RepoType: repoInfo.RepoType, - JobName: j.JobName.getJobName(), - RepositoryURL: string(repoInfo.CloneURL), - Branch: repoInfo.Branch, - SecretToken: secretToken, - FolderName: j.JobName.getJobFolder(), - GitlabConnection: globalConfig.GitlabConnectionID, - RepoURL: string(repoInfo.GetCloneURL()), - RepoOwner: repoInfo.GetRepoOwner(), - RepoName: repoInfo.GetRepoName(), - RepoCredentialsId: globalConfig.CredentialID, - } - jobScript, err := jenkins.BuildRenderedScript(jobRenderInfo) - if err != nil { - log.Debugf("jenkins redner template failed: %s", err) - return err - } - // 2. execute script to create jenkins job - _, err = jenkinsClient.ExecuteScript(jobScript) - if err != nil { - log.Debugf("jenkins execute script failed: %s", err) - return err - } - return nil -} - -func (j *jobOptions) extractPlugins() []step.StepConfigAPI { - stepConfigs := step.ExtractValidStepConfig(j.Pipeline) - // add repo plugin for repoInfo - stepConfigs = append(stepConfigs, step.GetRepoStepConfig(j.ProjectRepo)...) - return stepConfigs -} - -// check config need offline config -func (j *jobOptions) needOfflineConfig() bool { - // since we use github as default config location - // we use this to check whether this pipeline need default offline Jenkinsfile - const githubContentHost = "raw.githubusercontent.com" - return j.Jenkins.Offline && strings.Contains(string(j.Pipeline.ConfigLocation), githubContentHost) -} - -func (j *jobOptions) getScmWebhookAddress() string { - var webHookURL string - switch j.ProjectRepo.RepoType { - case "gitlab": - webHookURL = fmt.Sprintf("%s/project/%s", j.Jenkins.URL, j.JobName) - case "github": - webHookURL = fmt.Sprintf("%s/github-webhook/", j.Jenkins.URL) - } - log.Debugf("jenkins config webhook is %s", webHookURL) - return webHookURL -} - -// jenkins jobName, can be like folder/jobName or jobName -type jenkinsJobName string - -func (n jenkinsJobName) getJobName() string { - jobNameStr := string(n) - if strings.Contains(jobNameStr, "/") { - return strings.Split(jobNameStr, "/")[1] - } - return jobNameStr -} - -func (n jenkinsJobName) getJobFolder() string { - jobNameStr := string(n) - if strings.Contains(jobNameStr, "/") { - return strings.Split(jobNameStr, "/")[0] - } - return "" -} - -func (n jenkinsJobName) checkValid() error { - jobNameStr := string(n) - if strings.Contains(jobNameStr, "/") { - strs := strings.Split(jobNameStr, "/") - if len(strs) != 2 || len(strs[0]) == 0 || len(strs[1]) == 0 { - return fmt.Errorf("jenkins jobName illegal: %s", n) - } - } - return nil -} diff --git a/internal/pkg/plugin/jenkinspipeline/option_test.go b/internal/pkg/plugin/jenkinspipeline/option_test.go deleted file mode 100644 index f8b9907ff..000000000 --- a/internal/pkg/plugin/jenkinspipeline/option_test.go +++ /dev/null @@ -1,273 +0,0 @@ -package jenkinspipeline - -import ( - "fmt" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/step" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/util" - "github.com/devstream-io/devstream/pkg/util/downloader" - "github.com/devstream-io/devstream/pkg/util/jenkins" - "github.com/devstream-io/devstream/pkg/util/scm/git" -) - -var _ = Describe("newJobOptions func", func() { - var ( - jenkinsURL, jobName, projectURL, userName string - jenkinsFilePath downloader.ResourceLocation - rawOptions configmanager.RawOptions - ) - BeforeEach(func() { - jenkinsURL = "http://test.com" - userName = "test_user" - jobName = "test_folder/test_job" - projectURL = "http://127.0.0.1:300/test/project" - jenkinsFilePath = "http://raw.content.com/Jenkinsfile" - rawOptions = configmanager.RawOptions{ - "jobName": jobName, - "jenkins": map[string]interface{}{ - "url": jenkinsURL, - "user": userName, - }, - "scm": map[string]interface{}{ - "url": projectURL, - }, - "pipeline": map[string]interface{}{ - "configLocation": jenkinsFilePath, - }, - } - - }) - It("should work normal", func() { - job := new(jobOptions) - err := util.DecodePlugin(rawOptions, job) - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(string(job.JobName)).Should(Equal(jobName)) - Expect(job.Pipeline.ConfigLocation).Should(Equal(jenkinsFilePath)) - Expect(string(job.ProjectRepo.CloneURL)).Should(Equal(projectURL)) - Expect(job.Jenkins.URL).Should(Equal(jenkinsURL)) - Expect(job.Jenkins.User).Should(Equal(userName)) - }) -}) - -var _ = Describe("options struct", func() { - var ( - jobName, repoOwner, repoName, secretToken, errMsg string - jenkinsFilePath downloader.ResourceLocation - repoInfo *git.RepoInfo - j *jenkins.MockClient - opts *jobOptions - ) - BeforeEach(func() { - repoOwner = "owner" - repoName = "repo" - jobName = "test_pipeline" - jenkinsFilePath = "test_path" - repoInfo = &git.RepoInfo{ - Owner: repoOwner, - Repo: repoName, - Branch: "test", - BaseURL: "http://127.0.0.1:300", - RepoType: "gitlab", - } - secretToken = "test_secret" - opts = &jobOptions{ - JobName: jenkinsJobName(jobName), - CIConfig: ci.CIConfig{ - ProjectRepo: repoInfo, - Pipeline: &ci.PipelineConfig{ - ConfigLocation: jenkinsFilePath, - }, - }, - } - j = &jenkins.MockClient{} - }) - - Context("getJobName method", func() { - When("jobName has slash", func() { - BeforeEach(func() { - jobName = "testFolderJob" - opts.JobName = jenkinsJobName(fmt.Sprintf("folder/%s", jobName)) - }) - It("should return later item", func() { - Expect(opts.JobName.getJobName()).Should(Equal(jobName)) - }) - }) - When("jobName does'nt have slash", func() { - BeforeEach(func() { - opts.JobName = jenkinsJobName("testJob") - }) - It("should return jobName", func() { - Expect(opts.JobName.getJobName()).Should(Equal("testJob")) - }) - }) - }) - - Context("getJobFolder method", func() { - When("folder name exist", func() { - BeforeEach(func() { - opts.JobName = jenkinsJobName(fmt.Sprintf("folder/%s", jobName)) - }) - It("should return later item", func() { - Expect(opts.JobName.getJobFolder()).Should(Equal("folder")) - }) - }) - When("folder name not exist", func() { - It("should return empty string", func() { - Expect(opts.JobName.getJobFolder()).Should(Equal("")) - }) - }) - }) - - Context("extractpipelinePlugins method", func() { - When("repo type is github", func() { - BeforeEach(func() { - opts.CIConfig.Pipeline.ImageRepo = &step.ImageRepoStepConfig{ - URL: "http://test.com", - User: "test", - } - opts.CIConfig.ProjectRepo.RepoType = "github" - }) - It("should return plugin config", func() { - plugins := opts.extractPlugins() - Expect(len(plugins)).Should(Equal(2)) - }) - }) - When("repo type is gitlab", func() { - BeforeEach(func() { - opts.CIConfig.Pipeline.ImageRepo = &step.ImageRepoStepConfig{ - URL: "http://test.com", - User: "test", - } - opts.CIConfig.ProjectRepo.RepoType = "gitlab" - }) - It("should return plugin config", func() { - plugins := opts.extractPlugins() - Expect(len(plugins)).Should(Equal(2)) - }) - }) - }) - - Context("createOrUpdateJob method", func() { - When("jenkins client return normal", func() { - BeforeEach(func() { - opts.CIConfig.ProjectRepo.SSHPrivateKey = "test" - }) - It("should work noraml", func() { - err := opts.createOrUpdateJob(j, secretToken) - Expect(err).ShouldNot(HaveOccurred()) - }) - }) - When("jenkins client script error", func() { - BeforeEach(func() { - errMsg = "script err" - j.ExecuteScriptError = fmt.Errorf(errMsg) - }) - It("should return error", func() { - err := opts.createOrUpdateJob(j, secretToken) - Expect(err).Should(HaveOccurred()) - Expect(err.Error()).Should(Equal(errMsg)) - }) - }) - }) - - Context("remove method", func() { - When("jenkins job get error", func() { - BeforeEach(func() { - errMsg = "get job error" - j.GetFolderJobError = fmt.Errorf(errMsg) - }) - It("should return error", func() { - err := opts.remove(j) - Expect(err).Error().Should(HaveOccurred()) - Expect(err.Error()).Should(Equal(errMsg)) - }) - }) - When("jenkins job is not exist", func() { - BeforeEach(func() { - errMsg = "404" - j.GetFolderJobError = fmt.Errorf(errMsg) - }) - It("should return delete error", func() { - err := opts.remove(j) - Expect(err).Error().ShouldNot(HaveOccurred()) - }) - }) - }) - - Context("install method", func() { - When("install plugin failed", func() { - BeforeEach(func() { - errMsg = "install plugin failed" - j.InstallPluginsIfNotExistsError = fmt.Errorf(errMsg) - }) - It("should return error", func() { - err := opts.install(j, secretToken) - Expect(err).Should(HaveOccurred()) - Expect(err.Error()).Should(Equal(errMsg)) - }) - }) - When("config plugin failed", func() { - BeforeEach(func() { - errMsg = "config plugin failed" - j = &jenkins.MockClient{} - j.ConfigCascForRepoError = fmt.Errorf(errMsg) - }) - It("should return error", func() { - err := opts.install(j, secretToken) - Expect(err).Should(HaveOccurred()) - Expect(err.Error()).Should(Equal(errMsg)) - }) - }) - When("all config valid", func() { - BeforeEach(func() { - j = &jenkins.MockClient{} - }) - It("should work normal", func() { - err := opts.install(j, secretToken) - Expect(err).ShouldNot(HaveOccurred()) - }) - }) - }) - Context("getScmWebhookAddress method", func() { - var ( - testJob *jobOptions - baseURL, appName string - ) - BeforeEach(func() { - baseURL = "test.jenkins.com" - appName = "testApp" - testJob = &jobOptions{ - CIConfig: ci.CIConfig{ - ProjectRepo: &git.RepoInfo{}, - }, - Jenkins: jenkinsOption{ - URL: baseURL, - }, - JobName: jenkinsJobName(appName), - } - }) - When("repo type is gitlab", func() { - BeforeEach(func() { - testJob.ProjectRepo.RepoType = "gitlab" - }) - It("should return gitlab webhook address", func() { - address := testJob.getScmWebhookAddress() - Expect(address).Should(Equal(fmt.Sprintf("%s/project/%s", baseURL, appName))) - }) - }) - When("repo type is github", func() { - BeforeEach(func() { - testJob.ProjectRepo.RepoType = "github" - }) - It("should return github webhook address", func() { - address := testJob.getScmWebhookAddress() - Expect(address).Should(Equal(fmt.Sprintf("%s/github-webhook/", baseURL))) - }) - }) - }) -}) diff --git a/internal/pkg/plugin/jenkinspipeline/plugins.go b/internal/pkg/plugin/jenkinspipeline/plugins.go deleted file mode 100644 index 6512a3e9c..000000000 --- a/internal/pkg/plugin/jenkinspipeline/plugins.go +++ /dev/null @@ -1,42 +0,0 @@ -package jenkinspipeline - -import ( - "github.com/imdario/mergo" - - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/step" - "github.com/devstream-io/devstream/pkg/util/jenkins" - "github.com/devstream-io/devstream/pkg/util/log" -) - -// ensurePluginInstalled will ensure jenkins plugins are installed -func ensurePluginInstalled(jenkinsClient jenkins.JenkinsAPI, pluginConfigs []step.StepConfigAPI) error { - if jenkinsClient.GetBasicInfo().IsOffline() { - return nil - } - var plugins []*jenkins.JenkinsPlugin - for _, pluginConfig := range pluginConfigs { - plugins = append(plugins, pluginConfig.GetJenkinsPlugins()...) - } - return jenkinsClient.InstallPluginsIfNotExists(plugins) -} - -func configPlugins(jenkinsClient jenkins.JenkinsAPI, pluginConfigs []step.StepConfigAPI) error { - globalCascConfig := &jenkins.RepoCascConfig{ - Offline: jenkinsClient.GetBasicInfo().IsOffline(), - } - for _, pluginConfig := range pluginConfigs { - cascConfig, err := pluginConfig.ConfigJenkins(jenkinsClient) - if err != nil { - log.Debugf("jenkins plugin %+v config error", pluginConfig) - return err - } - if cascConfig != nil { - err := mergo.Merge(globalCascConfig, cascConfig, mergo.WithOverride) - if err != nil { - log.Debugf("jenins merge casc config failed: %+v", err) - return err - } - } - } - return jenkinsClient.ConfigCascForRepo(globalCascConfig) -} diff --git a/internal/pkg/plugin/jenkinspipeline/plugins_test.go b/internal/pkg/plugin/jenkinspipeline/plugins_test.go deleted file mode 100644 index 5f3969339..000000000 --- a/internal/pkg/plugin/jenkinspipeline/plugins_test.go +++ /dev/null @@ -1,56 +0,0 @@ -package jenkinspipeline - -import ( - "fmt" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/step" - "github.com/devstream-io/devstream/pkg/util/jenkins" -) - -var _ = Describe("ensurePluginInstalled func", func() { - var ( - mockClient *jenkins.MockClient - s *step.MockPluginsConfig - ) - BeforeEach(func() { - mockClient = &jenkins.MockClient{} - s = &step.MockPluginsConfig{} - }) - It("should work normal", func() { - err := ensurePluginInstalled(mockClient, []step.StepConfigAPI{s}) - Expect(err).Error().ShouldNot(HaveOccurred()) - }) -}) - -var _ = Describe("configPlugins func", func() { - var ( - mockClient *jenkins.MockClient - s *step.MockPluginsConfig - f *step.MockPluginsConfig - ) - When("configPlugins is valid", func() { - BeforeEach(func() { - mockClient = &jenkins.MockClient{} - s = &step.MockPluginsConfig{} - }) - It("should work normal", func() { - err := configPlugins(mockClient, []step.StepConfigAPI{s}) - Expect(err).Error().ShouldNot(HaveOccurred()) - }) - }) - When("configPlugins is not valid", func() { - BeforeEach(func() { - mockClient = &jenkins.MockClient{} - f = &step.MockPluginsConfig{ - ConfigErr: fmt.Errorf("test error"), - } - }) - It("should return error", func() { - err := configPlugins(mockClient, []step.StepConfigAPI{f}) - Expect(err).Error().Should(HaveOccurred()) - }) - }) -}) diff --git a/internal/pkg/plugin/jenkinspipeline/read.go b/internal/pkg/plugin/jenkinspipeline/read.go deleted file mode 100644 index f6962510a..000000000 --- a/internal/pkg/plugin/jenkinspipeline/read.go +++ /dev/null @@ -1,26 +0,0 @@ -package jenkinspipeline - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" -) - -func Read(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - setJenkinsDefault, - validateJenkins, - }, - GetStatusOperation: getStatus, - } - - status, err := operator.Execute(options) - if err != nil { - return nil, err - } - - log.Debugf("Return map: %v", status) - return status, nil -} diff --git a/internal/pkg/plugin/jenkinspipeline/state.go b/internal/pkg/plugin/jenkinspipeline/state.go deleted file mode 100644 index db1c45ac7..000000000 --- a/internal/pkg/plugin/jenkinspipeline/state.go +++ /dev/null @@ -1,48 +0,0 @@ -package jenkinspipeline - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/cifile" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/util" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/jenkins" -) - -func getStatus(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - opts := new(jobOptions) - if err := util.DecodePlugin(options, opts); err != nil { - return nil, err - } - - client, err := opts.Jenkins.newClient() - if err != nil { - return nil, err - } - - res := make(statemanager.ResourceStatus) - jobRes, err := getJobState(client, opts.JobName.getJobName(), opts.JobName.getJobFolder()) - if err != nil { - return nil, err - } - res["JobCreated"] = true - res["Job"] = jobRes - generalState, err := cifile.GetCIFileStatus(options) - if err != nil { - return nil, err - } - res["General"] = generalState - return res, nil -} - -func getJobState(jenkinsClient jenkins.JenkinsAPI, jobName, jobFolder string) (map[string]interface{}, error) { - job, err := jenkinsClient.GetFolderJob(jobName, jobFolder) - if err != nil { - return nil, err - } - rawJob := job.Raw - return map[string]interface{}{ - "Created": true, - "Class": rawJob.Class, - "URL": rawJob.URL, - }, nil -} diff --git a/internal/pkg/plugin/jenkinspipeline/state_test.go b/internal/pkg/plugin/jenkinspipeline/state_test.go deleted file mode 100644 index b1a8f6152..000000000 --- a/internal/pkg/plugin/jenkinspipeline/state_test.go +++ /dev/null @@ -1,56 +0,0 @@ -package jenkinspipeline - -import ( - "fmt" - - "github.com/bndr/gojenkins" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/pkg/util/jenkins" -) - -var _ = Describe("getJobState func", func() { - var ( - c jenkins.JenkinsAPI - ) - When("GetFolderJob return error", func() { - BeforeEach(func() { - c = &jenkins.MockClient{ - GetFolderJobError: fmt.Errorf("test error"), - } - }) - It("should return err", func() { - _, err := getJobState(c, "test_job", "test_folder") - Expect(err).Error().Should(HaveOccurred()) - }) - }) - When("GetFolderJob return job", func() { - var ( - jobClass, jobURL string - ) - BeforeEach(func() { - jobClass = "testClass" - jobURL = "testURL" - c = &jenkins.MockClient{ - GetFolderJobValue: &gojenkins.Job{ - Raw: &gojenkins.JobResponse{ - Class: jobClass, - URL: jobURL, - }, - }, - } - }) - It("should return job res", func() { - job, err := getJobState(c, "test_job", "test_folder") - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(len(job)).ShouldNot(BeZero()) - class, ok := job["Class"] - Expect(ok).Should(BeTrue()) - Expect(class).Should(Equal(jobClass)) - url, ok := job["URL"] - Expect(ok).Should(BeTrue()) - Expect(url).Should(Equal(jobURL)) - }) - }) -}) diff --git a/internal/pkg/plugin/jenkinspipeline/tpl/Jenkinsfile_offline.tpl b/internal/pkg/plugin/jenkinspipeline/tpl/Jenkinsfile_offline.tpl deleted file mode 100644 index fa498808c..000000000 --- a/internal/pkg/plugin/jenkinspipeline/tpl/Jenkinsfile_offline.tpl +++ /dev/null @@ -1,51 +0,0 @@ -podTemplate(containers: [ - containerTemplate(name: 'maven', image: 'maven:3.8.6-openjdk-18', command: 'sleep', args: '99d'), - containerTemplate(name: 'buildkit', image: 'moby/buildkit:master', ttyEnabled: true, privileged: true), - ], volumes: [ - secretVolume(secretName: '[[ .ImageRepoDockerSecret ]]', mountPath: '/root/.docker') - ]) { - node(POD_LABEL) { - stage("Get Project") { - checkout scm - } - stage('Run Maven test') { - gitlabCommitStatus("test") { - container('maven') { - stage('run mvn test') { - sh 'mvn -B test' - } - } - } - } - stage("Build Docker image") { - gitlabCommitStatus("build image") { - container('buildkit') { - stage('build a Maven project') { - String opts = "" - String imageRepo = "[[ .imageRepo.user ]]/[[ .AppName ]]" - String imageURL = "[[ .imageRepo.url ]]" - if (imageURL) { - imageRepo = "${imageURL}/${imageRepo}" - } - if (imageRepo.contains("http://")) { - opts = ",registry.insecure=true" - imageRepo = imageRepo.replace("http://", "") - } - String version - if (env.GIT_COMMIT) { - version = env.GIT_COMMIT.substring(0, 8) - } else { - sh "git config --global --add safe.directory '*'" - String gitCommitLang = sh (script: "git log -n 1 --pretty=format:'%H'", returnStdout: true) - version = gitCommitLang.substring(0, 8) - } - sh """ - buildctl build --frontend dockerfile.v0 --local context=. --local dockerfile=. --output type=image,name=${imageRepo}:latest,push=true${opts} - buildctl build --frontend dockerfile.v0 --local context=. --local dockerfile=. --output type=image,name=${imageRepo}:${version},push=true${opts} - """ - } - } - } - } - } -} diff --git a/internal/pkg/plugin/jenkinspipeline/update.go b/internal/pkg/plugin/jenkinspipeline/update.go deleted file mode 100644 index 024dbbe24..000000000 --- a/internal/pkg/plugin/jenkinspipeline/update.go +++ /dev/null @@ -1,10 +0,0 @@ -package jenkinspipeline - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/statemanager" -) - -func Update(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return Create(options) -} diff --git a/internal/pkg/plugin/jenkinspipeline/validate.go b/internal/pkg/plugin/jenkinspipeline/validate.go deleted file mode 100644 index 37d30e3bc..000000000 --- a/internal/pkg/plugin/jenkinspipeline/validate.go +++ /dev/null @@ -1,69 +0,0 @@ -package jenkinspipeline - -import ( - _ "embed" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/cifile" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/cifile/server" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/util" - "github.com/devstream-io/devstream/pkg/util/log" - "github.com/devstream-io/devstream/pkg/util/types" - "github.com/devstream-io/devstream/pkg/util/validator" -) - -//go:embed tpl/Jenkinsfile_offline.tpl -var offlineJenkinsScript string - -// setJenkinsDefault config default fields for usage -func setJenkinsDefault(options configmanager.RawOptions) (configmanager.RawOptions, error) { - const ciType = server.CIJenkinsType - opts := new(jobOptions) - if err := util.DecodePlugin(options, opts); err != nil { - return nil, err - } - - // if jenkins is offline, just use offline Jenkinsfile - if opts.needOfflineConfig() { - opts.CIFileConfig = &cifile.CIFileConfig{ - Type: ciType, - ConfigContentMap: map[string]string{ - "Jenkinsfile": offlineJenkinsScript, - }, - Vars: opts.Pipeline.GenerateCIFileVars(opts.ProjectRepo), - } - } else { - if opts.Pipeline.ConfigLocation == "" { - opts.Pipeline.ConfigLocation = "https://raw.githubusercontent.com/devstream-io/dtm-pipeline-templates/main/jenkins-pipeline/general/Jenkinsfile" - } - opts.CIFileConfig = opts.Pipeline.BuildCIFileConfig(ciType, opts.ProjectRepo) - } - // set field value if empty - if opts.Jenkins.Namespace == "" { - opts.Jenkins.Namespace = "jenkins" - } - if opts.JobName == "" { - opts.JobName = jenkinsJobName(opts.ProjectRepo.GetRepoName()) - } - return types.EncodeStruct(opts) -} - -// validateJenkins will validate jenkins jobName field and jenkins field -func validateJenkins(options configmanager.RawOptions) (configmanager.RawOptions, error) { - opts := new(jobOptions) - if err := util.DecodePlugin(options, opts); err != nil { - return nil, err - } - - if err := validator.CheckStructError(opts).Combine(); err != nil { - return nil, err - } - - // check jenkins job name - if err := opts.JobName.checkValid(); err != nil { - log.Debugf("jenkins validate pipeline invalid: %+v", err) - return nil, err - } - - return options, nil -} diff --git a/internal/pkg/plugin/jenkinspipeline/validate_test.go b/internal/pkg/plugin/jenkinspipeline/validate_test.go deleted file mode 100644 index 546397b98..000000000 --- a/internal/pkg/plugin/jenkinspipeline/validate_test.go +++ /dev/null @@ -1,153 +0,0 @@ -package jenkinspipeline - -import ( - "fmt" - "os" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/util" -) - -var _ = Describe("setDefault func", func() { - var ( - jenkinsUser, jenkinsPassword, jenkinsURL, jenkinsFilePath, projectURL string - options map[string]interface{} - ) - BeforeEach(func() { - jenkinsUser = "test" - jenkinsPassword = "testPassword" - jenkinsURL = "http://test.jenkins.com/" - projectURL = "https://test.gitlab.com/test/test_project" - jenkinsFilePath = "http://raw.content.com/Jenkinsfile" - err := os.Setenv("JENKINS_PASSWORD", jenkinsPassword) - Expect(err).NotTo(HaveOccurred()) - options = map[string]interface{}{ - "jenkins": map[string]interface{}{ - "url": jenkinsURL, - "user": jenkinsUser, - }, - "scm": map[string]interface{}{ - "url": projectURL, - }, - "pipeline": map[string]interface{}{ - "configLocation": jenkinsFilePath, - }, - } - }) - When("all input is valid", func() { - BeforeEach(func() { - options["scm"] = map[string]interface{}{ - "url": "git@44.33.22.11:30022:root/spring-demo.git", - "apiURL": "http://www.app.com", - } - options["projectRepo"] = map[string]interface{}{ - "repo": "testRepo", - } - }) - It("should set default value", func() { - newOptions, err := setJenkinsDefault(options) - Expect(err).Error().ShouldNot(HaveOccurred()) - opts := new(jobOptions) - err = util.DecodePlugin(newOptions, opts) - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(string(opts.JobName)).Should(Equal("spring-demo")) - }) - }) - AfterEach(func() { - os.Unsetenv("JENKINS_PASSWORD") - }) -}) - -var _ = Describe("validate func", func() { - var ( - jenkinsUser, jenkinsURL, jenkinsFilePath, projectURL, repoType, githubToken string - options, projectRepo, pipeline map[string]interface{} - ) - BeforeEach(func() { - jenkinsUser = "test" - jenkinsURL = "http://test.jenkins.com/" - projectURL = "https://test.gitlab.com/test/test_project" - jenkinsFilePath = "http://raw.content.com/Jenkinsfile" - githubToken = "github_token" - pipeline = map[string]interface{}{ - "configLocation": jenkinsFilePath, - } - projectRepo = map[string]interface{}{ - "owner": "test_owner", - "org": "test_org", - "repo": "test_repo", - } - options = map[string]interface{}{ - "jobName": "test", - "jenkins": map[string]interface{}{ - "url": jenkinsURL, - "user": jenkinsUser, - "password": "changeme", - }, - "scm": map[string]interface{}{ - "url": projectURL, - "token": githubToken, - }, - "pipeline": pipeline, - "projectRepo": projectRepo, - } - }) - When("Input field miss", func() { - BeforeEach(func() { - options = map[string]interface{}{ - "jenkins": map[string]interface{}{ - "url": jenkinsURL, - "user": jenkinsUser, - }, - "scm": map[string]interface{}{ - "url": projectURL, - }, - } - }) - It("should return error", func() { - _, err := validateJenkins(options) - Expect(err).Error().Should(HaveOccurred()) - }) - }) - - When("jobName is not valid", func() { - BeforeEach(func() { - options["jobName"] = "folder/not_exist/jobName" - options["pipeline"] = pipeline - repoType = "github" - projectRepo = map[string]any{ - "scmType": repoType, - "name": "test", - "owner": "test_user", - "branch": "main", - "token": githubToken, - } - options["scm"] = projectRepo - }) - It("should return error", func() { - _, err := validateJenkins(options) - Expect(err).Error().Should(HaveOccurred()) - Expect(err.Error()).Should(Equal(fmt.Sprintf("jenkins jobName illegal: %s", options["jobName"]))) - }) - }) - When("all params is right", func() { - BeforeEach(func() { - options["pipeline"] = pipeline - repoType = "github" - projectRepo = map[string]any{ - "scmType": repoType, - "name": "test", - "owner": "test_user", - "branch": "main", - "token": githubToken, - } - options["scm"] = projectRepo - }) - It("should return nil error", func() { - _, err := validateJenkins(options) - Expect(err).Error().ShouldNot(HaveOccurred()) - }) - }) -}) diff --git a/internal/pkg/plugin/jira/create.go b/internal/pkg/plugin/jira/create.go deleted file mode 100644 index 4b86c379c..000000000 --- a/internal/pkg/plugin/jira/create.go +++ /dev/null @@ -1,33 +0,0 @@ -package jira - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/cifile" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" -) - -// Create sets up jira workflows. -func Create(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - // Initialize Operator with Operations - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - setDefault, - validate, - }, - ExecuteOperations: installer.ExecuteOperations{ - addJiraSecret, - cifile.PushCIFiles, - }, - GetStatusOperation: cifile.GetCIFileStatus, - } - - // Execute all Operations in Operator - status, err := operator.Execute(configmanager.RawOptions(options)) - if err != nil { - return nil, err - } - log.Debugf("Return map: %v", status) - return status, nil -} diff --git a/internal/pkg/plugin/jira/delete.go b/internal/pkg/plugin/jira/delete.go deleted file mode 100644 index 04c767431..000000000 --- a/internal/pkg/plugin/jira/delete.go +++ /dev/null @@ -1,28 +0,0 @@ -package jira - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/cifile" -) - -// Delete remove jira workflows. -func Delete(options configmanager.RawOptions) (bool, error) { - // Initialize Operator with Operations - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - setDefault, - validate, - }, - ExecuteOperations: installer.ExecuteOperations{ - cifile.DeleteCIFiles, - }, - } - - // Execute all Operations in Operator - _, err := operator.Execute(configmanager.RawOptions(options)) - if err != nil { - return false, err - } - return true, nil -} diff --git a/internal/pkg/plugin/jira/jira.go b/internal/pkg/plugin/jira/jira.go deleted file mode 100644 index ae1feaac3..000000000 --- a/internal/pkg/plugin/jira/jira.go +++ /dev/null @@ -1,26 +0,0 @@ -package jira - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/pkg/util/scm" -) - -// addJiraSecret will add trello secret in github -func addJiraSecret(rawOptions configmanager.RawOptions) error { - opts, err := newOptions(rawOptions) - if err != nil { - return err - } - - scmClient, err := scm.NewClientWithAuth(opts.Scm) - if err != nil { - return err - } - if err := scmClient.AddRepoSecret("GH_TOKEN", opts.Scm.Token); err != nil { - return err - } - if err := scmClient.AddRepoSecret("JIRA_API_TOKEN", opts.Jira.Token); err != nil { - return err - } - return nil -} diff --git a/internal/pkg/plugin/jira/jira_suit_test.go b/internal/pkg/plugin/jira/jira_suit_test.go deleted file mode 100644 index 88f4328f7..000000000 --- a/internal/pkg/plugin/jira/jira_suit_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package jira_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestPlanmanager(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Plugin Jira Test Suite") -} diff --git a/internal/pkg/plugin/jira/options.go b/internal/pkg/plugin/jira/options.go deleted file mode 100644 index 6367ca1ec..000000000 --- a/internal/pkg/plugin/jira/options.go +++ /dev/null @@ -1,31 +0,0 @@ -package jira - -import ( - "github.com/mitchellh/mapstructure" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/cifile" - "github.com/devstream-io/devstream/pkg/util/scm/git" -) - -type options struct { - Scm *git.RepoInfo `mapstructure:"scm" validate:"required"` - Jira *jiraInfo `mapstructure:"jira" validate:"required"` - // used in package - CIFileConfig *cifile.CIFileConfig `mapstructure:"ci"` -} - -type jiraInfo struct { - BaseUrl string `validate:"required" mapstructure:"baseURL"` - UserEmail string `validate:"required" mapstructure:"userEmail"` - ProjectKey string `validate:"required" mapstructure:"projectKey"` - Token string `validate:"required" mapstructure:"token"` -} - -func newOptions(rawOptions configmanager.RawOptions) (*options, error) { - var opts options - if err := mapstructure.Decode(rawOptions, &opts); err != nil { - return nil, err - } - return &opts, nil -} diff --git a/internal/pkg/plugin/jira/read.go b/internal/pkg/plugin/jira/read.go deleted file mode 100644 index b10c0621e..000000000 --- a/internal/pkg/plugin/jira/read.go +++ /dev/null @@ -1,28 +0,0 @@ -package jira - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/cifile" - "github.com/devstream-io/devstream/pkg/util/log" -) - -// Read get jira workflows. -func Read(options map[string]interface{}) (map[string]interface{}, error) { - // Initialize Operator with Operations - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - setDefault, - validate, - }, - GetStatusOperation: cifile.GetCIFileStatus, - } - - // Execute all Operations in Operator - status, err := operator.Execute(configmanager.RawOptions(options)) - if err != nil { - return nil, err - } - log.Debugf("Return map: %v", status) - return status, nil -} diff --git a/internal/pkg/plugin/jira/tpl/issuebuilder.tpl.yml b/internal/pkg/plugin/jira/tpl/issuebuilder.tpl.yml deleted file mode 100644 index 767d87436..000000000 --- a/internal/pkg/plugin/jira/tpl/issuebuilder.tpl.yml +++ /dev/null @@ -1,111 +0,0 @@ -name: jira-integ Builder -on: - issues: - types: [opened, reopened, edited, closed] - issue_comment: - types: [created, edited, deleted] - -jobs: - github-jira-issue: - name: Transition Issue - runs-on: ubuntu-latest - steps: - - name: Login - uses: atlassian/gajira-login@master - env: - JIRA_BASE_URL: [[.jira.baseURL]] - JIRA_USER_EMAIL: [[.jira.userEmail]] - JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} - - - name: Create new Jira issue - id: create - if: ${{ github.event.action == 'opened' }} - uses: atlassian/gajira-create@master - with: - project: [[.jira.projectKey]] - issuetype: Task - summary: ${{ github.event.issue.title }} - description: ${{ github.event.issue.body }} - - - name: Rename github issue - if: ${{ github.event.action == 'opened' }} - uses: actions-cool/issues-helper@v3 - with: - actions: 'update-issue' - token: ${{ secrets.GH_TOKEN }} - issue-number: ${{ github.event.issue.number }} - state: 'open' - title: "[${{ steps.create.outputs.issue }}] ${{ github.event.issue.title }}" - update-mode: 'replace' - emoji: '+1' - - - name: Find Jira Issue Key - id: find - if: ${{ github.event.action != 'opened' }} - uses: atlassian/gajira-find-issue-key@master - with: - string: "${{ github.event.issue.title }}" - - - name: Transition issue to In Progress - id: transition_inprogress - if: ${{ github.event.action == 'edited' }} - uses: atlassian/gajira-transition@master - with: - issue: ${{ steps.find.outputs.issue }} - transition: "Start progress" - - - name: Transition issue to Done - id: transition_done - if: ${{ github.event.action == 'closed' }} - uses: atlassian/gajira-transition@master - with: - issue: ${{ steps.find.outputs.issue }} - transition: "Done" - - - name: Transition issue to TO DO - id: transition_todo - if: ${{ github.event.action == 'reopened' }} - uses: atlassian/gajira-transition@master - with: - issue: ${{ steps.find.outputs.issue }} - transition: "To Do" - - issue_comment_integration: - if: ${{ github.event_name == 'issue_comment' }} - runs-on: ubuntu-latest - name: Integrate Issue Comment - steps: - - name: Login - uses: atlassian/gajira-login@master - env: - JIRA_BASE_URL: [[.jira.baseURL]] - JIRA_USER_EMAIL: [[.jira.userEmail]] - JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} - - name: Find Issue Key - id: find - uses: atlassian/gajira-find-issue-key@master - with: - string: "${{ github.event.issue.title }}" - - name: Create issue - id: create_issue - if: ${{ github.event.action == 'created' }} - uses: atlassian/gajira-comment@master - with: - issue: ${{ steps.find.outputs.issue }} - comment: ${{ github.event.comment.body }} - - - name: Edit issue - id: edit_issue - if: ${{ github.event.action == 'edited' }} - uses: atlassian/gajira-comment@master - with: - issue: ${{ steps.find.outputs.issue }} - comment: "updated: ${{ github.event.comment.body }} from: ${{ github.event.changes.body.from }}" - - - name: Delete issue - id: del_issue - if: ${{ github.event.action == 'deleted' }} - uses: atlassian/gajira-comment@master - with: - issue: ${{ steps.find.outputs.issue }} - comment: "deleted: ${{ github.event.comment.body }}" diff --git a/internal/pkg/plugin/jira/update.go b/internal/pkg/plugin/jira/update.go deleted file mode 100644 index c51d70413..000000000 --- a/internal/pkg/plugin/jira/update.go +++ /dev/null @@ -1,34 +0,0 @@ -package jira - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/cifile" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" -) - -// Update remove and set up jira workflows. -func Update(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - // Initialize Operator with Operations - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - setDefault, - validate, - }, - ExecuteOperations: installer.ExecuteOperations{ - cifile.DeleteCIFiles, - addJiraSecret, - cifile.PushCIFiles, - }, - GetStatusOperation: cifile.GetCIFileStatus, - } - - // Execute all Operations in Operator - status, err := operator.Execute(configmanager.RawOptions(options)) - if err != nil { - return nil, err - } - log.Debugf("Return map: %v", status) - return status, nil -} diff --git a/internal/pkg/plugin/jira/validate.go b/internal/pkg/plugin/jira/validate.go deleted file mode 100644 index ffe8a4260..000000000 --- a/internal/pkg/plugin/jira/validate.go +++ /dev/null @@ -1,42 +0,0 @@ -package jira - -import ( - _ "embed" - "fmt" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/cifile" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/cifile/server" - "github.com/devstream-io/devstream/pkg/util/mapz" - "github.com/devstream-io/devstream/pkg/util/validator" -) - -//go:embed tpl/issuebuilder.tpl.yml -var issueBuilderWorkflow string - -func validate(rawOptions configmanager.RawOptions) (configmanager.RawOptions, error) { - opts, err := newOptions(rawOptions) - if err != nil { - return nil, err - } - if err := validator.CheckStructError(opts).Combine(); err != nil { - return nil, err - } - return rawOptions, nil -} - -func setDefault(rawOptions configmanager.RawOptions) (configmanager.RawOptions, error) { - const workflowFileName = "jiraIssueBuilder.yml" - opts, err := newOptions(rawOptions) - if err != nil { - return nil, err - } - opts.CIFileConfig = &cifile.CIFileConfig{ - Type: server.CIGithubType, - ConfigContentMap: cifile.CIFileConfigMap{ - fmt.Sprintf("%s/%s", server.CiGitHubWorkConfigLocation, workflowFileName): issueBuilderWorkflow, - }, - Vars: cifile.CIFileVarsMap(rawOptions), - } - return mapz.DecodeStructToMap(opts) -} diff --git a/internal/pkg/plugin/reposcaffolding/create.go b/internal/pkg/plugin/reposcaffolding/create.go deleted file mode 100644 index 6b2f6e195..000000000 --- a/internal/pkg/plugin/reposcaffolding/create.go +++ /dev/null @@ -1,29 +0,0 @@ -package reposcaffolding - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" -) - -func Create(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - // Initialize Operator with Operations - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - validate, - }, - ExecuteOperations: installer.ExecuteOperations{ - installRepo, - }, - GetStatusOperation: getDynamicStatus, - } - - // Execute all Operations in Operator - status, err := operator.Execute(options) - if err != nil { - return nil, err - } - log.Debugf("Return map: %v", status) - return status, nil -} diff --git a/internal/pkg/plugin/reposcaffolding/delete.go b/internal/pkg/plugin/reposcaffolding/delete.go deleted file mode 100644 index 7cd0cc693..000000000 --- a/internal/pkg/plugin/reposcaffolding/delete.go +++ /dev/null @@ -1,25 +0,0 @@ -package reposcaffolding - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" -) - -func Delete(options configmanager.RawOptions) (bool, error) { - // Initialize Operator with Operations - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - validate, - }, - ExecuteOperations: installer.ExecuteOperations{ - deleteRepo, - }, - } - _, err := operator.Execute(options) - if err != nil { - return false, err - } - - // 2. return ture if all process success - return true, nil -} diff --git a/internal/pkg/plugin/reposcaffolding/option.go b/internal/pkg/plugin/reposcaffolding/option.go deleted file mode 100644 index d1d0a6095..000000000 --- a/internal/pkg/plugin/reposcaffolding/option.go +++ /dev/null @@ -1,26 +0,0 @@ -package reposcaffolding - -import ( - "github.com/devstream-io/devstream/pkg/util/scm/git" -) - -type options struct { - SourceRepo *git.RepoInfo `validate:"required" mapstructure:"sourceRepo"` - DestinationRepo *git.RepoInfo `validate:"required" mapstructure:"destinationRepo"` - Vars map[string]interface{} -} - -func (opts *options) renderTplConfig() map[string]interface{} { - // default render value from repo - renderConfig := map[string]any{ - "AppName": opts.DestinationRepo.GetRepoName(), - "Repo": map[string]string{ - "Name": opts.DestinationRepo.GetRepoName(), - "Owner": opts.DestinationRepo.GetRepoOwner(), - }, - } - for k, v := range opts.Vars { - renderConfig[k] = v - } - return renderConfig -} diff --git a/internal/pkg/plugin/reposcaffolding/option_test.go b/internal/pkg/plugin/reposcaffolding/option_test.go deleted file mode 100644 index 60bea82a9..000000000 --- a/internal/pkg/plugin/reposcaffolding/option_test.go +++ /dev/null @@ -1,50 +0,0 @@ -package reposcaffolding - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/pkg/util/scm/git" -) - -var _ = Describe("Options struct", func() { - var ( - opts *options - ) - BeforeEach(func() { - opts = &options{ - SourceRepo: &git.RepoInfo{ - Repo: "source_repo", - Owner: "source_owner", - RepoType: "github", - }, - DestinationRepo: &git.RepoInfo{ - Repo: "dst_repo", - Owner: "dst_owner", - RepoType: "github", - }, - } - }) - - Context("renderTplConfig method", func() { - var ( - coverAppName string - ) - BeforeEach(func() { - coverAppName = "cover app" - opts.Vars = map[string]interface{}{ - "AppName": coverAppName, - } - }) - It("should return with vars", func() { - renderConfig := opts.renderTplConfig() - Expect(len(renderConfig)).ShouldNot(BeZero()) - appName, ok := renderConfig["AppName"] - Expect(ok).Should(BeTrue()) - Expect(appName).Should(Equal(coverAppName)) - repo, ok := renderConfig["Repo"] - Expect(ok).Should(BeTrue()) - Expect(repo).Should(Equal(map[string]string{"Owner": "dst_owner", "Name": "dst_repo"})) - }) - }) -}) diff --git a/internal/pkg/plugin/reposcaffolding/read.go b/internal/pkg/plugin/reposcaffolding/read.go deleted file mode 100644 index 6a3806414..000000000 --- a/internal/pkg/plugin/reposcaffolding/read.go +++ /dev/null @@ -1,24 +0,0 @@ -package reposcaffolding - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" -) - -func Read(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - validate, - }, - GetStatusOperation: getDynamicStatus, - } - - status, err := operator.Execute(options) - if err != nil { - return nil, err - } - log.Debugf("Return map: %v", status) - return status, nil -} diff --git a/internal/pkg/plugin/reposcaffolding/reposcaffolding.go b/internal/pkg/plugin/reposcaffolding/reposcaffolding.go deleted file mode 100644 index 22dc5720b..000000000 --- a/internal/pkg/plugin/reposcaffolding/reposcaffolding.go +++ /dev/null @@ -1,71 +0,0 @@ -package reposcaffolding - -import ( - "fmt" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/util" - "github.com/devstream-io/devstream/pkg/util/file" - "github.com/devstream-io/devstream/pkg/util/log" - "github.com/devstream-io/devstream/pkg/util/scm" - "github.com/devstream-io/devstream/pkg/util/scm/git" -) - -// installRepo will install repo by opts config -func installRepo(rawOptions configmanager.RawOptions) error { - const ( - defaultCommitMsg = "init by devstream" - defaultCommitBranch = "init-by-devstream" - ) - opts := new(options) - if err := util.DecodePlugin(rawOptions, opts); err != nil { - return err - } - // 1. Download repo by SourceRepo - sourceClient, err := scm.NewClient(opts.SourceRepo) - if err != nil { - return err - } - repoDir, err := sourceClient.DownloadRepo() - if err != nil { - log.Debugf("reposcaffolding process files error: %s", err) - return err - } - - // 2. render repo with variables - appName := opts.DestinationRepo.Repo - repoNameWithBranch := fmt.Sprintf("%s-%s", opts.SourceRepo.Repo, opts.SourceRepo.Branch) - gitMap, err := file.GetFileMapByWalkDir( - repoDir, filterGitFiles, - getRepoFileNameFunc(appName, repoNameWithBranch), - processRepoFileFunc(appName, opts.renderTplConfig()), - ) - if err != nil { - return fmt.Errorf("render RepoTemplate files failed with error: %w", err) - } - - // 3. push repo to DestinationRepo - dstClient, err := scm.NewClientWithAuth(opts.DestinationRepo) - if err != nil { - return err - } - return scm.PushInitRepo(dstClient, &git.CommitInfo{ - CommitMsg: defaultCommitMsg, - CommitBranch: defaultCommitBranch, - GitFileMap: gitMap, - }) -} - -// deleteRepo will delete repo by options -func deleteRepo(rawOptions configmanager.RawOptions) error { - opts := new(options) - if err := util.DecodePlugin(rawOptions, opts); err != nil { - return err - } - - client, err := scm.NewClientWithAuth(opts.DestinationRepo) - if err != nil { - return err - } - return client.DeleteRepo() -} diff --git a/internal/pkg/plugin/reposcaffolding/reposcaffolding_suite_test.go b/internal/pkg/plugin/reposcaffolding/reposcaffolding_suite_test.go deleted file mode 100644 index 49f75095c..000000000 --- a/internal/pkg/plugin/reposcaffolding/reposcaffolding_suite_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package reposcaffolding_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestCommon(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "common Reposcaffolding Suite") -} diff --git a/internal/pkg/plugin/reposcaffolding/state.go b/internal/pkg/plugin/reposcaffolding/state.go deleted file mode 100644 index bd831fce4..000000000 --- a/internal/pkg/plugin/reposcaffolding/state.go +++ /dev/null @@ -1,45 +0,0 @@ -package reposcaffolding - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/util" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" - "github.com/devstream-io/devstream/pkg/util/scm" -) - -func getDynamicStatus(rawOptions configmanager.RawOptions) (statemanager.ResourceStatus, error) { - opts := new(options) - if err := util.DecodePlugin(rawOptions, opts); err != nil { - return nil, err - } - - scmClient, err := scm.NewClientWithAuth(opts.DestinationRepo) - if err != nil { - log.Debugf("reposcaffolding status init repo failed: %+v", err) - return nil, err - } - - repoInfo, err := scmClient.DescribeRepo() - if err != nil { - log.Debugf("reposcaffolding status describe repo failed: %+v", err) - return nil, err - } - - resStatus := statemanager.ResourceStatus{ - "repo": repoInfo.Repo, - "owner": repoInfo.Owner, - "org": repoInfo.Org, - "repoURL": repoInfo.CloneURL, - "repoType": repoInfo.RepoType, - "source": opts.SourceRepo.CloneURL, - } - - resStatus.SetOutputs(statemanager.ResourceOutputs{ - "repo": repoInfo.Repo, - "org": repoInfo.Org, - "owner": repoInfo.Owner, - "repoURL": string(repoInfo.CloneURL), - }) - return resStatus, nil -} diff --git a/internal/pkg/plugin/reposcaffolding/update.go b/internal/pkg/plugin/reposcaffolding/update.go deleted file mode 100644 index 859847649..000000000 --- a/internal/pkg/plugin/reposcaffolding/update.go +++ /dev/null @@ -1,29 +0,0 @@ -package reposcaffolding - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" -) - -func Update(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - validate, - }, - ExecuteOperations: installer.ExecuteOperations{ - deleteRepo, - installRepo, - }, - GetStatusOperation: getDynamicStatus, - } - - // Execute all Operations in Operator - status, err := operator.Execute(options) - if err != nil { - return nil, err - } - log.Debugf("Return map: %v", status) - return status, nil -} diff --git a/internal/pkg/plugin/reposcaffolding/utils.go b/internal/pkg/plugin/reposcaffolding/utils.go deleted file mode 100644 index aeaf48b2c..000000000 --- a/internal/pkg/plugin/reposcaffolding/utils.go +++ /dev/null @@ -1,63 +0,0 @@ -package reposcaffolding - -import ( - "fmt" - "os" - "path/filepath" - "strings" - - "github.com/devstream-io/devstream/pkg/util/file" - "github.com/devstream-io/devstream/pkg/util/log" - "github.com/devstream-io/devstream/pkg/util/template" -) - -const ( - appNamePlaceHolder = "_app_name_" -) - -func filterGitFiles(filePath string, isDir bool) bool { - if isDir { - return false - } - if strings.Contains(filePath, ".git/") { - log.Debugf("Walk: ignore file %s.", "./git/xxx") - return false - } - - if strings.HasSuffix(filePath, "README.md") { - log.Debugf("Walk: ignore file %s.", "README.md") - return false - } - return true -} - -func getRepoFileNameFunc(appName, repoName string) file.DirFileNameFunc { - return func(filePath, srcPath string) string { - relativePath, _ := filepath.Rel(srcPath, filePath) - if strings.HasPrefix(relativePath, repoName) { - repoNamePath := fmt.Sprintf("%s/", repoName) - relativePath = strings.Replace(relativePath, repoNamePath, "", 1) - } - replacedFileName := file.ReplaceAppNameInPathStr(relativePath, appNamePlaceHolder, appName) - return strings.TrimSuffix(replacedFileName, ".tpl") - } -} - -func processRepoFileFunc(appName string, vars map[string]interface{}) file.DirFileContentFunc { - return func(filePath string) ([]byte, error) { - var fileContent []byte - log.Debugf("Walk: found file: %s.", filePath) - // if file endswith tpl, render this file, else copy this file directly - if strings.Contains(filePath, "tpl") { - fileContentStr, err := template.NewRenderClient(&template.TemplateOption{ - Name: "repo-scaffolding", - }, template.LocalFileGetter).Render(filePath, vars) - if err != nil { - log.Warnf("repo render file failed: %s", err) - return fileContent, err - } - return []byte(fileContentStr), nil - } - return os.ReadFile(filePath) - } -} diff --git a/internal/pkg/plugin/reposcaffolding/utils_test.go b/internal/pkg/plugin/reposcaffolding/utils_test.go deleted file mode 100644 index 03b02d65a..000000000 --- a/internal/pkg/plugin/reposcaffolding/utils_test.go +++ /dev/null @@ -1,96 +0,0 @@ -package reposcaffolding - -import ( - "fmt" - "os" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -var _ = Describe("utils for walk dir func", func() { - var ( - tempDir, tempFilePath string - ) - BeforeEach(func() { - tempDir = GinkgoT().TempDir() - }) - Context("filterGitFiles func", func() { - When("input path isDir", func() { - It("should return false", func() { - result := filterGitFiles(tempDir, true) - Expect(result).Should(BeFalse()) - }) - }) - When("input file contains invalid name", func() { - It("should return false", func() { - invalidGitFilePath := ".git/test" - result := filterGitFiles(invalidGitFilePath, false) - Expect(result).Should(BeFalse()) - invalidReadmeFile := "test/README.md" - result = filterGitFiles(invalidReadmeFile, false) - Expect(result).Should(BeFalse()) - }) - }) - When("input name is valid", func() { - It("should return true", func() { - validFileName := "test.go" - result := filterGitFiles(validFileName, false) - Expect(result).Should(BeTrue()) - }) - }) - }) - - Context("getRepoFileNameFunc func", func() { - It("should work as expected", func() { - testRepoName := "test_repo" - testAppName := "test_app" - srcPath := "test" - filePath := fmt.Sprintf("%s/%s/_app_name_.txt", srcPath, testRepoName) - result := getRepoFileNameFunc(testAppName, testRepoName)(filePath, srcPath) - Expect(result).Should(Equal(fmt.Sprintf("%s.txt", testAppName))) - }) - }) - - Context("processRepoFileFunc func", func() { - var ( - testContent []byte - testAppName string - ) - BeforeEach(func() { - testAppName = "test_app" - }) - When("file is not tpl", func() { - BeforeEach(func() { - testContent = []byte("testContent") - tempFile, err := os.CreateTemp(tempDir, "testFile") - Expect(err).Error().ShouldNot(HaveOccurred()) - tempFilePath = tempFile.Name() - err = os.WriteFile(tempFilePath, testContent, 0755) - Expect(err).Error().ShouldNot(HaveOccurred()) - }) - It("should return file content", func() { - result, err := processRepoFileFunc(testAppName, map[string]interface{}{})(tempFilePath) - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(result).Should(Equal(testContent)) - }) - }) - When("file is tpl", func() { - BeforeEach(func() { - tempFile, err := os.CreateTemp(tempDir, "testFile.tpl") - Expect(err).Error().ShouldNot(HaveOccurred()) - tempFilePath = tempFile.Name() - testContent = []byte("[[ .Name ]]_test") - err = os.WriteFile(tempFilePath, testContent, 0755) - Expect(err).Error().ShouldNot(HaveOccurred()) - }) - It("should work as expected", func() { - result, err := processRepoFileFunc(testAppName, map[string]interface{}{ - "Name": "devstream", - })(tempFilePath) - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(result).Should(Equal([]byte("devstream_test"))) - }) - }) - }) -}) diff --git a/internal/pkg/plugin/reposcaffolding/validate.go b/internal/pkg/plugin/reposcaffolding/validate.go deleted file mode 100644 index 86363270b..000000000 --- a/internal/pkg/plugin/reposcaffolding/validate.go +++ /dev/null @@ -1,19 +0,0 @@ -package reposcaffolding - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/util" - "github.com/devstream-io/devstream/pkg/util/validator" -) - -// validate validates the options provided by the core. -func validate(rawOptions configmanager.RawOptions) (configmanager.RawOptions, error) { - opts := new(options) - if err := util.DecodePlugin(rawOptions, opts); err != nil { - return nil, err - } - if err := validator.CheckStructError(opts).Combine(); err != nil { - return nil, err - } - return rawOptions, nil -} diff --git a/internal/pkg/plugin/reposcaffolding/validate_test.go b/internal/pkg/plugin/reposcaffolding/validate_test.go deleted file mode 100644 index b408e0ea2..000000000 --- a/internal/pkg/plugin/reposcaffolding/validate_test.go +++ /dev/null @@ -1,47 +0,0 @@ -package reposcaffolding - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" -) - -var _ = Describe("validate func", func() { - var ( - rawOpts configmanager.RawOptions - ) - When("reposcaffolding option is not valid", func() { - BeforeEach(func() { - rawOpts = configmanager.RawOptions{ - "not_exist": true, - } - }) - It("should return err", func() { - _, err := validate(rawOpts) - Expect(err).Should(HaveOccurred()) - }) - }) - When("reposcaffolding option is valid", func() { - BeforeEach(func() { - rawOpts = configmanager.RawOptions{ - "sourceRepo": map[string]string{ - "owner": "test_user", - "name": "test_repo", - "scmType": "github", - "branch": "main", - }, - "destinationRepo": map[string]string{ - "owner": "dst_user", - "name": "dst_repo", - "scmType": "github", - "branch": "main", - }, - } - }) - It("should return noraml", func() { - _, err := validate(rawOpts) - Expect(err).Error().ShouldNot(HaveOccurred()) - }) - }) -}) diff --git a/internal/pkg/plugin/trello/create.go b/internal/pkg/plugin/trello/create.go deleted file mode 100644 index 5c8383afa..000000000 --- a/internal/pkg/plugin/trello/create.go +++ /dev/null @@ -1,34 +0,0 @@ -package trello - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/cifile" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" -) - -// Create creates Tello board and lists(todo/doing/done). -func Create(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - // Initialize Operator with Operations - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - setDefault, - validate, - }, - ExecuteOperations: installer.ExecuteOperations{ - createBoard, - addTrelloSecret, - cifile.PushCIFiles, - }, - GetStatusOperation: getState, - } - - // Execute all Operations in Operator - status, err := operator.Execute(configmanager.RawOptions(options)) - if err != nil { - return nil, err - } - log.Debugf("Return map: %v", status) - return status, nil -} diff --git a/internal/pkg/plugin/trello/delete.go b/internal/pkg/plugin/trello/delete.go deleted file mode 100644 index 81938386c..000000000 --- a/internal/pkg/plugin/trello/delete.go +++ /dev/null @@ -1,29 +0,0 @@ -package trello - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/cifile" -) - -// Create creates Tello board and lists(todo/doing/done). -func Delete(options configmanager.RawOptions) (bool, error) { - // Initialize Operator with Operations - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - setDefault, - validate, - }, - ExecuteOperations: installer.ExecuteOperations{ - deleteBoard, - cifile.DeleteCIFiles, - }, - } - - // Execute all Operations in Operator - _, err := operator.Execute(configmanager.RawOptions(options)) - if err != nil { - return false, err - } - return true, nil -} diff --git a/internal/pkg/plugin/trello/options.go b/internal/pkg/plugin/trello/options.go deleted file mode 100644 index 68310de77..000000000 --- a/internal/pkg/plugin/trello/options.go +++ /dev/null @@ -1,91 +0,0 @@ -package trello - -import ( - "fmt" - - "github.com/mitchellh/mapstructure" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/cifile" - "github.com/devstream-io/devstream/pkg/util/log" - "github.com/devstream-io/devstream/pkg/util/scm/git" - "github.com/devstream-io/devstream/pkg/util/trello" -) - -// options is the struct for configurations of the trellogithub plugin. -type options struct { - Scm *git.RepoInfo `validate:"required" mapstructure:"scm"` - Board *board `validate:"required" mapstructure:"board"` - - // used in package - CIFileConfig *cifile.CIFileConfig `mapstructure:"ci"` -} - -type board struct { - Name string `mapstructure:"name"` - Description string `mapstructure:"description"` - APIKey string `mapstructure:"apiKey"` - Token string `mapstructure:"token"` -} - -type boardIDInfo struct { - boardID string - todoListID string - doingListID string - doneListID string -} - -func newOptions(rawOptions configmanager.RawOptions) (*options, error) { - var opts options - if err := mapstructure.Decode(rawOptions, &opts); err != nil { - return nil, err - } - return &opts, nil -} - -func (b board) create(trelloClient trello.TrelloAPI) error { - trelloBoard, err := trelloClient.Get(b.Name, b.Description) - if err != nil { - return err - } - if trelloBoard != nil { - log.Debugf("Board already exists, description: %s, kanbanName: %s.", b.Description, b.Name) - return nil - } - _, err = trelloClient.Create(b.Name, b.Description) - return err -} - -func (b board) delete(trelloClient trello.TrelloAPI) error { - trelloBoard, err := trelloClient.Get(b.Name, b.Description) - if err != nil { - return err - } - if trelloBoard != nil { - return trelloBoard.Delete() - } - return nil -} - -func (b board) get(trelloClient trello.TrelloAPI) (*boardIDInfo, error) { - const defaultTrelloListNum = 3 - trelloBoard, err := trelloClient.Get(b.Name, b.Description) - if err != nil { - return nil, err - } - boardList, err := trelloBoard.GetLists() - if err != nil { - return nil, err - } - if len(boardList) != defaultTrelloListNum { - log.Errorf("Unknown lists format: len==%d.", len(boardList)) - return nil, fmt.Errorf("unknown lists format: len==%d", len(boardList)) - } - idData := &boardIDInfo{ - boardID: trelloBoard.ID, - todoListID: boardList[0].ID, - doingListID: boardList[1].ID, - doneListID: boardList[2].ID, - } - return idData, nil -} diff --git a/internal/pkg/plugin/trello/options_test.go b/internal/pkg/plugin/trello/options_test.go deleted file mode 100644 index e64a39ee7..000000000 --- a/internal/pkg/plugin/trello/options_test.go +++ /dev/null @@ -1,92 +0,0 @@ -package trello - -import ( - "fmt" - - trelloCommon "github.com/adlio/trello" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/pkg/util/trello" -) - -var _ = Describe("board struct", func() { - var ( - testBoard *board - mockTrello *trello.MockTrelloClient - errMsg string - ) - BeforeEach(func() { - testBoard = &board{ - Name: "test_board", - Description: "test_desc", - } - }) - Context("create method", func() { - When("get board failed", func() { - BeforeEach(func() { - errMsg = "get board failed" - mockTrello = &trello.MockTrelloClient{ - GetError: fmt.Errorf(errMsg), - } - }) - It("should return err", func() { - err := testBoard.create(mockTrello) - Expect(err).Should(HaveOccurred()) - Expect(err.Error()).Should(Equal(errMsg)) - }) - }) - When("board not exist", func() { - BeforeEach(func() { - mockTrello = &trello.MockTrelloClient{ - GetValue: &trelloCommon.Board{ - Name: "test", - }, - } - }) - It("should return nil", func() { - err := testBoard.create(mockTrello) - Expect(err).ShouldNot(HaveOccurred()) - }) - }) - When("create failed", func() { - BeforeEach(func() { - errMsg = "create board failed" - mockTrello = &trello.MockTrelloClient{ - CreateError: fmt.Errorf(errMsg), - } - }) - It("should return nil", func() { - err := testBoard.create(mockTrello) - Expect(err).Should(HaveOccurred()) - Expect(err.Error()).Should(Equal(errMsg)) - }) - }) - }) - Context("delete method", func() { - When("board not exist", func() { - BeforeEach(func() { - mockTrello = &trello.MockTrelloClient{} - }) - It("should return err", func() { - err := testBoard.delete(mockTrello) - Expect(err).ShouldNot(HaveOccurred()) - }) - }) - }) - Context("get method", func() { - When("get board failed", func() { - BeforeEach(func() { - errMsg = "get board failed" - mockTrello = &trello.MockTrelloClient{ - GetError: fmt.Errorf(errMsg), - } - }) - It("should return err", func() { - _, err := testBoard.get(mockTrello) - Expect(err).Should(HaveOccurred()) - Expect(err.Error()).Should(Equal(errMsg)) - }) - }) - }) -}) diff --git a/internal/pkg/plugin/trello/read.go b/internal/pkg/plugin/trello/read.go deleted file mode 100644 index 91686bdf7..000000000 --- a/internal/pkg/plugin/trello/read.go +++ /dev/null @@ -1,26 +0,0 @@ -package trello - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - "github.com/devstream-io/devstream/pkg/util/log" -) - -func Read(options map[string]interface{}) (map[string]interface{}, error) { - // Initialize Operator with Operations - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - setDefault, - validate, - }, - GetStatusOperation: getState, - } - - // Execute all Operations in Operator - status, err := operator.Execute(configmanager.RawOptions(options)) - if err != nil { - return nil, err - } - log.Debugf("Return map: %v", status) - return status, nil -} diff --git a/internal/pkg/plugin/trello/state.go b/internal/pkg/plugin/trello/state.go deleted file mode 100644 index 9b31647d5..000000000 --- a/internal/pkg/plugin/trello/state.go +++ /dev/null @@ -1,48 +0,0 @@ -package trello - -import ( - "fmt" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/cifile" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/mapz" - "github.com/devstream-io/devstream/pkg/util/trello" -) - -func getState(rawOptions configmanager.RawOptions) (statemanager.ResourceStatus, error) { - opts, err := newOptions(rawOptions) - if err != nil { - return nil, err - } - - c, err := trello.NewClient(opts.Board.APIKey, opts.Board.Token) - if err != nil { - return nil, err - } - idData, err := opts.Board.get(c) - if err != nil { - return nil, err - } - idDataMap, err := mapz.DecodeStructToMap(idData) - if err != nil { - return nil, err - } - - // get board status - resStatus := statemanager.ResourceStatus(idDataMap) - output := make(statemanager.ResourceOutputs) - output["boardId"] = fmt.Sprint(idData.boardID) - output["todoListId"] = fmt.Sprint(idData.todoListID) - output["doingListId"] = fmt.Sprint(idData.doingListID) - output["doneListId"] = fmt.Sprint(idData.doneListID) - resStatus.SetOutputs(output) - - // get ci status - ciFileStatus, err := cifile.GetCIFileStatus(rawOptions) - if err != nil { - return nil, err - } - resStatus["ci"] = ciFileStatus - return resStatus, nil -} diff --git a/internal/pkg/plugin/trello/tpl/issuebuilder.tpl.yml b/internal/pkg/plugin/trello/tpl/issuebuilder.tpl.yml deleted file mode 100644 index f32cbf2f8..000000000 --- a/internal/pkg/plugin/trello/tpl/issuebuilder.tpl.yml +++ /dev/null @@ -1,74 +0,0 @@ -name: Issues Builder -on: - issues: - types: [opened, reopened, edited, closed] - issue_comment: - types: [created, edited, deleted] -jobs: - issue_comment_integration: - if: ${{ github.event_name == 'issue_comment' }} - runs-on: ubuntu-latest - name: Integrate Issue Comment - env: - ACTION_VERSION: CyanRYi/trello-github-integration@v3.0.0 - TRELLO_API_KEY: ${{ secrets.TRELLO_API_KEY }} - TRELLO_API_TOKEN: ${{ secrets.TRELLO_TOKEN }} - TRELLO_BOARD_ID: ${{ secrets.TRELLO_BOARD_ID }} - TRELLO_TODO_LIST_ID: ${{ secrets.TRELLO_TODO_LIST_ID }} - TRELLO_DONE_LIST_ID: ${{ secrets.TRELLO_DONE_LIST_ID }} - TRELLO_MEMBER_MAP: ${{ secrets.TRELLO_MEMBER_MAP }} - steps: - - name: Add Comment - id: comment-create - if: ${{ github.event.action == 'created' }} - uses: CyanRYi/trello-github-integration@v3.0.0 - with: - trello-action: add_comment - - name: Edit Comment - id: comment-edit - if: ${{ github.event.action == 'edited' }} - uses: CyanRYi/trello-github-integration@v3.0.0 - with: - trello-action: edit_comment - - name: Delete Comment - id: comment-delete - if: ${{ github.event.action == 'deleted' }} - uses: CyanRYi/trello-github-integration@v3.0.0 - with: - trello-action: delete_comment - issue_integration: - if: ${{ github.event_name == 'issues' }} - runs-on: ubuntu-latest - name: Integrate Issue - env: - TRELLO_API_KEY: ${{ secrets.TRELLO_API_KEY }} - TRELLO_API_TOKEN: ${{ secrets.TRELLO_TOKEN }} - TRELLO_BOARD_ID: ${{ secrets.TRELLO_BOARD_ID }} - TRELLO_TODO_LIST_ID: ${{ secrets.TRELLO_TODO_LIST_ID }} - TRELLO_DONE_LIST_ID: ${{ secrets.TRELLO_DONE_LIST_ID }} - TRELLO_MEMBER_MAP: ${{ secrets.TRELLO_MEMBER_MAP }} - steps: - - name: Create Card - id: card-create - if: ${{ github.event.action == 'opened' }} - uses: CyanRYi/trello-github-integration@v3.0.0 - with: - trello-action: create_card - - name: Edit Card - id: card-edit - if: ${{ github.event.action == 'edited' }} - uses: CyanRYi/trello-github-integration@v3.0.0 - with: - trello-action: edit_card - - name: Close Card - id: card-move-to-done - if: ${{ github.event.action == 'closed' }} - uses: CyanRYi/trello-github-integration@v3.0.0 - with: - trello-action: move_card_to_done - - name: Reopen Card - id: card-move-to-backlog - if: ${{ github.event.action == 'reopened' }} - uses: CyanRYi/trello-github-integration@v3.0.0 - with: - trello-action: move_card_to_todo diff --git a/internal/pkg/plugin/trello/trello.go b/internal/pkg/plugin/trello/trello.go deleted file mode 100644 index fe547ccd9..000000000 --- a/internal/pkg/plugin/trello/trello.go +++ /dev/null @@ -1,90 +0,0 @@ -package trello - -import ( - "github.com/spf13/viper" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/pkg/util/scm" - "github.com/devstream-io/devstream/pkg/util/trello" -) - -func createBoard(rawOptions configmanager.RawOptions) error { - opts, err := newOptions(rawOptions) - if err != nil { - return err - } - c, err := trello.NewClient(opts.Board.APIKey, opts.Board.Token) - if err != nil { - return err - } - return opts.Board.create(c) - -} - -// deleteBoard delete specified board -func deleteBoard(rawOptions configmanager.RawOptions) error { - opts, err := newOptions(rawOptions) - if err != nil { - return err - } - c, err := trello.NewClient(opts.Board.APIKey, opts.Board.Token) - if err != nil { - return err - } - return opts.Board.delete(c) -} - -// addTrelloSecret will add trello secret in github -func addTrelloSecret(rawOptions configmanager.RawOptions) error { - opts, err := newOptions(rawOptions) - if err != nil { - return err - } - - // 1. init scm client - scmClient, err := scm.NewClientWithAuth(opts.Scm) - if err != nil { - return err - } - - // 2. init trello client - trelloClient, err := trello.NewClient(opts.Board.APIKey, opts.Board.Token) - if err != nil { - return err - } - trelloBoard, err := opts.Board.get(trelloClient) - if err != nil { - return err - } - - // 3. add github secret - // add key - if err := scmClient.AddRepoSecret("TRELLO_API_KEY", viper.GetString("trello_api_key")); err != nil { - return err - } - // add token - if err := scmClient.AddRepoSecret("TRELLO_TOKEN", viper.GetString("trello_token")); err != nil { - return err - } - // add board id - if err := scmClient.AddRepoSecret("TRELLO_BOARD_ID", trelloBoard.boardID); err != nil { - return err - } - // add todolist id - if err := scmClient.AddRepoSecret("TRELLO_TODO_LIST_ID", trelloBoard.todoListID); err != nil { - return err - } - // add doinglist id - if err := scmClient.AddRepoSecret("TRELLO_DOING_LIST_ID", trelloBoard.doingListID); err != nil { - return err - } - // add donelist id - if err := scmClient.AddRepoSecret("TRELLO_DONE_LIST_ID", trelloBoard.doneListID); err != nil { - return err - } - // add member map - if err := scmClient.AddRepoSecret("TRELLO_MEMBER_MAP", "[]"); err != nil { - return err - } - return nil -} diff --git a/internal/pkg/plugin/trello/trello_suit_test.go b/internal/pkg/plugin/trello/trello_suit_test.go deleted file mode 100644 index 1ea2394c9..000000000 --- a/internal/pkg/plugin/trello/trello_suit_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package trello_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestPlanmanager(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Plugin Trello Test Suite") -} diff --git a/internal/pkg/plugin/trello/update.go b/internal/pkg/plugin/trello/update.go deleted file mode 100644 index 4ece89047..000000000 --- a/internal/pkg/plugin/trello/update.go +++ /dev/null @@ -1,11 +0,0 @@ -package trello - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/statemanager" -) - -// Create creates Tello board and lists(todo/doing/done). -func Update(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - return Create(options) -} diff --git a/internal/pkg/plugin/trello/validate.go b/internal/pkg/plugin/trello/validate.go deleted file mode 100644 index 14289545d..000000000 --- a/internal/pkg/plugin/trello/validate.go +++ /dev/null @@ -1,54 +0,0 @@ -package trello - -import ( - "fmt" - - _ "embed" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/cifile" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/ci/cifile/server" - "github.com/devstream-io/devstream/pkg/util/mapz" - "github.com/devstream-io/devstream/pkg/util/types" - "github.com/devstream-io/devstream/pkg/util/validator" -) - -//go:embed tpl/issuebuilder.tpl.yml -var issueBuilderWorkflow string - -func validate(rawOptions configmanager.RawOptions) (configmanager.RawOptions, error) { - opts, err := newOptions(rawOptions) - if err != nil { - return nil, err - } - if err := validator.CheckStructError(opts).Combine(); err != nil { - return nil, err - } - return rawOptions, nil -} - -func setDefault(rawOptions configmanager.RawOptions) (configmanager.RawOptions, error) { - opts, err := newOptions(rawOptions) - if err != nil { - return nil, err - } - // set board default value - var boardDefaultConfig = &board{ - Name: fmt.Sprintf("%s/%s", opts.Scm.GetRepoOwner(), opts.Scm.GetRepoName()), - Description: fmt.Sprintf("Description is managed by DevStream, please don't modify. %s/%s", opts.Scm.GetRepoOwner(), opts.Scm.GetRepoName()), - } - if opts.Board == nil { - opts.Board = &board{} - } - types.FillStructDefaultValue(opts.Board, boardDefaultConfig) - - // set CIFileConfig - const workflowFileName = "trelloIssueBuilder.yml" - opts.CIFileConfig = &cifile.CIFileConfig{ - Type: server.CIGithubType, - ConfigContentMap: cifile.CIFileConfigMap{ - fmt.Sprintf("%s/%s", server.CiGitHubWorkConfigLocation, workflowFileName): issueBuilderWorkflow, - }, - } - return mapz.DecodeStructToMap(opts) -} diff --git a/internal/pkg/plugin/trello/validate_test.go b/internal/pkg/plugin/trello/validate_test.go deleted file mode 100644 index 9ed2c1a5c..000000000 --- a/internal/pkg/plugin/trello/validate_test.go +++ /dev/null @@ -1,58 +0,0 @@ -package trello - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" -) - -var _ = Describe("setDefault method", func() { - var rawOptions configmanager.RawOptions - When("board is not exist", func() { - BeforeEach(func() { - rawOptions = configmanager.RawOptions{ - "scm": configmanager.RawOptions{ - "name": "test", - "owner": "test_user", - "scmType": "github", - }, - } - }) - It("should set default value", func() { - data, err := setDefault(rawOptions) - Expect(err).ShouldNot(HaveOccurred()) - Expect(data["board"]).Should(Equal(map[string]any{ - "name": "test_user/test", - "description": "Description is managed by DevStream, please don't modify. test_user/test", - "apiKey": "", - "token": "", - })) - Expect(data["scm"]).Should(Equal(map[string]any{ - "owner": "test_user", - "name": "test", - "scmType": "github", - })) - Expect(data["ci"]).ShouldNot(BeEmpty()) - }) - }) -}) - -var _ = Describe("validate method", func() { - var rawOptions configmanager.RawOptions - When("board is not exist", func() { - BeforeEach(func() { - rawOptions = configmanager.RawOptions{ - "scm": configmanager.RawOptions{ - "name": "test", - "owner": "test_user", - "scmType": "github", - }, - } - }) - It("should return err", func() { - _, err := validate(rawOptions) - Expect(err).Should(HaveOccurred()) - }) - }) -}) diff --git a/internal/pkg/plugin/zentao/create.go b/internal/pkg/plugin/zentao/create.go deleted file mode 100644 index ff610d172..000000000 --- a/internal/pkg/plugin/zentao/create.go +++ /dev/null @@ -1,38 +0,0 @@ -package zentao - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/goclient" - "github.com/devstream-io/devstream/internal/pkg/statemanager" -) - -func Create(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - // Initialize Operator with Operations - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - goclient.Validate, - }, - ExecuteOperations: installer.ExecuteOperations{ - goclient.DealWithNsWhenInstall, - goclient.CreatePersistentVolumeWrapper(defaultPVPath), - goclient.CreatePersistentVolumeClaim, - goclient.CreateDeploymentWrapperLabelAndContainerPorts(defaultZentaolabels, &defaultZentaoPorts, defaultName), - goclient.CreateServiceWrapperLabelAndPorts(defaultZentaolabels, &defaultSVCPort), - goclient.WaitForReady(retryTimes), - }, - TerminateOperations: installer.TerminateOperations{ - goclient.DealWithErrWhenInstall, - }, - GetStatusOperation: goclient.GetStatus, - } - - // Execute all Operations in Operator - status, err := operator.Execute(options) - if err != nil { - return nil, err - } - - // Currently just store zentao's application status: "running" or "stopped" - return status, nil -} diff --git a/internal/pkg/plugin/zentao/delete.go b/internal/pkg/plugin/zentao/delete.go deleted file mode 100644 index 31cfab974..000000000 --- a/internal/pkg/plugin/zentao/delete.go +++ /dev/null @@ -1,26 +0,0 @@ -package zentao - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/goclient" -) - -func Delete(options configmanager.RawOptions) (bool, error) { - // Initialize Operator with Operations - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - goclient.Validate, - }, - ExecuteOperations: installer.ExecuteOperations{ - goclient.Delete, - }, - } - - // Execute all Operations in Operator - _, err := operator.Execute(options) - if err != nil { - return false, err - } - return true, nil -} diff --git a/internal/pkg/plugin/zentao/read.go b/internal/pkg/plugin/zentao/read.go deleted file mode 100644 index 42abc58cb..000000000 --- a/internal/pkg/plugin/zentao/read.go +++ /dev/null @@ -1,25 +0,0 @@ -package zentao - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/goclient" - "github.com/devstream-io/devstream/internal/pkg/statemanager" -) - -func Read(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - // Initialize Operator with Operations - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - goclient.Validate, - }, - GetStatusOperation: goclient.GetStatus, - } - - // Execute all Operations in Operator - status, err := operator.Execute(options) - if err != nil { - return nil, err - } - return status, nil -} diff --git a/internal/pkg/plugin/zentao/update.go b/internal/pkg/plugin/zentao/update.go deleted file mode 100644 index df9934cde..000000000 --- a/internal/pkg/plugin/zentao/update.go +++ /dev/null @@ -1,34 +0,0 @@ -package zentao - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/goclient" - "github.com/devstream-io/devstream/internal/pkg/statemanager" -) - -func Update(options configmanager.RawOptions) (statemanager.ResourceStatus, error) { - // Initialize Operator with Operations - operator := &installer.Operator{ - PreExecuteOperations: installer.PreExecuteOperations{ - goclient.Validate, - }, - ExecuteOperations: installer.ExecuteOperations{ - goclient.DeleteApp, - goclient.CreateDeploymentWrapperLabelAndContainerPorts(defaultZentaolabels, &defaultZentaoPorts, defaultName), - goclient.CreateServiceWrapperLabelAndPorts(defaultZentaolabels, &defaultSVCPort), - goclient.WaitForReady(retryTimes), - }, - TerminateOperations: installer.TerminateOperations{ - goclient.DealWithErrWhenInstall, - }, - GetStatusOperation: goclient.GetStatus, - } - - // Execute all Operations in Operator - status, err := operator.Execute(options) - if err != nil { - return nil, err - } - return status, nil -} diff --git a/internal/pkg/plugin/zentao/zentao.go b/internal/pkg/plugin/zentao/zentao.go deleted file mode 100644 index e49bcc78d..000000000 --- a/internal/pkg/plugin/zentao/zentao.go +++ /dev/null @@ -1,41 +0,0 @@ -package zentao - -import ( - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/util/intstr" -) - -var ( - // Retry times for check zentao deployment status, currently this means 5 seconds * 120 times = 10 minutes - retryTimes int = 120 - - defaultPVPath map[string]string = map[string]string{ - "zentao-pv": "/www/zentaopms", - "mysql-pv": "/var/lib/mysql", - } - - defaultZentaoPorts []corev1.ContainerPort = []corev1.ContainerPort{ - { - Name: "zentao", - Protocol: corev1.ProtocolTCP, - ContainerPort: 80, - }, - { - Name: "mysql", - Protocol: corev1.ProtocolTCP, - ContainerPort: 3306, - }, - } - - defaultSVCPort corev1.ServicePort = corev1.ServicePort{ - Name: "zentao", - Port: 80, - TargetPort: intstr.IntOrString{IntVal: 80}, - } - - defaultZentaolabels map[string]string = map[string]string{ - "app": "zentao", - } - - defaultName string = "zentao" -) diff --git a/internal/pkg/pluginengine/change.go b/internal/pkg/pluginengine/change.go deleted file mode 100644 index 9a4cbaf83..000000000 --- a/internal/pkg/pluginengine/change.go +++ /dev/null @@ -1,171 +0,0 @@ -package pluginengine - -import ( - "fmt" - "time" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" -) - -// Change is a wrapper for a single Tool and its Action to be executed. -type Change struct { - Tool *configmanager.Tool - ActionName statemanager.ComponentAction - Result *ChangeResult - Description string -} - -// ChangeResult holds the result of a change action. -type ChangeResult struct { - Succeeded bool - Error error - Time string - ReturnValue statemanager.ResourceStatus -} - -func (c *Change) String() string { - return fmt.Sprintf("\n{\n ActionName: %s,\n Tool: {Name: %s, InstanceID: %s}}\n}", - c.ActionName, c.Tool.Name, c.Tool.InstanceID) -} - -// execute changes the plan in batch. -// If any error occurs, it will stop executing the next batches and return the error. -func execute(smgr statemanager.Manager, changes []*Change, reverse bool) map[string]error { - errorsMap := make(map[string]error) - - log.Info("Start executing the plan.") - numOfChanges := len(changes) - log.Infof("Changes count: %d.", numOfChanges) - - // get changes in batch - // the changes in each batch do not have dependency on each other - // but the changes from next batch have dependency on the changes from previous batch - batchesOfChanges, err := topologicalSortChangesInBatch(changes) - - // for delete/destroy, the orders need to be reversed - // so that the dependencies are deleted at last - if reverse { - for i, j := 0, len(batchesOfChanges)-1; i < j; i, j = i+1, j-1 { - batchesOfChanges[i], batchesOfChanges[j] = batchesOfChanges[j], batchesOfChanges[i] - } - } - - if err != nil { - log.Errorf("Failed to sort changes in batch: %s", err) - errorsMap["dependency-analysis"] = err - return errorsMap - } - - currentChangeNum := 0 - for _, batch := range batchesOfChanges { - for _, c := range batch { - currentChangeNum += 1 - log.Separatorf("Processing progress: %d/%d.", currentChangeNum, numOfChanges) - log.Infof("Processing: (%s/%s) -> %s ...", c.Tool.Name, c.Tool.InstanceID, c.ActionName) - - var succeeded bool - var err error - var returnValue statemanager.ResourceStatus - - log.Debugf("Tool's raw changes are: %v.", c.Tool.Options) - - errs := HandleOutputsReferences(smgr, c.Tool.Options) - if len(errs) != 0 { - succeeded = false - - for _, e := range errs { - log.Errorf("Error: %s.", e) - } - log.Errorf("The outputs reference in tool (%s/%s) can't be resolved. Please double check your config.", c.Tool.Name, c.Tool.InstanceID) - - // not executing this change since its input isn't valid - continue - } - switch c.ActionName { - case statemanager.ActionCreate: - if returnValue, err = Create(c.Tool); err == nil { - succeeded = true - } - case statemanager.ActionUpdate: - if returnValue, err = Update(c.Tool); err == nil { - succeeded = true - } - case statemanager.ActionDelete: - succeeded, err = Delete(c.Tool) - } - if err != nil { - key := fmt.Sprintf("%s/%s/%s", c.Tool.Name, c.Tool.InstanceID, c.ActionName) - log.Errorf("%s/%s %s failed with error: %s", c.Tool.Name, c.Tool.InstanceID, c.ActionName, err) - errorsMap[key] = err - } - - c.Result = &ChangeResult{ - Succeeded: succeeded, - Error: err, - Time: time.Now().Format(time.RFC3339), - ReturnValue: returnValue, - } - - err = handleResult(smgr, c) - if err != nil { - errorsMap["handle-result"] = err - } - } - - // abort next batches if any error occurred in this batch - if len(errorsMap) != 0 { - break - } - } - - if len(errorsMap) != 0 { - log.Separatorf("Processing aborted.") - } else { - log.Separatorf("Processing done.") - } - - return errorsMap -} - -// handleResult is used to Write the latest StatesMap to the Backend. -func handleResult(smgr statemanager.Manager, change *Change) error { - log.Debugf("Start -> StatesMap now is:\n%s", string(smgr.GetStatesMap().Format())) - defer func() { - log.Debugf("End -> StatesMap now is:\n%s", string(smgr.GetStatesMap().Format())) - }() - - if !change.Result.Succeeded { - // do nothing when the change failed - return nil - } - - if change.ActionName == statemanager.ActionDelete { - key := statemanager.GenerateStateKeyByToolNameAndInstanceID(change.Tool.Name, change.Tool.InstanceID) - log.Infof("Prepare to delete '%s' from States.", key) - err := smgr.DeleteState(key) - if err != nil { - log.Debugf("Failed to delete state %s: %s.", key, err) - return err - } - log.Successf("Tool (%s/%s) delete done.", change.Tool.Name, change.Tool.InstanceID) - return nil - } - - key := statemanager.GenerateStateKeyByToolNameAndInstanceID(change.Tool.Name, change.Tool.InstanceID) - state := statemanager.State{ - Name: change.Tool.Name, - InstanceID: change.Tool.InstanceID, - DependsOn: change.Tool.DependsOn, - Options: change.Tool.Options, - ResourceStatus: change.Result.ReturnValue, - } - err := smgr.AddState(key, state) - if err != nil { - log.Debugf("Failed to add state %s: %s.", key, err) - return err - } - log.Successf("Tool (%s/%s) %s done.", change.Tool.Name, change.Tool.InstanceID, change.ActionName) - return nil -} diff --git a/internal/pkg/pluginengine/change_helper.go b/internal/pkg/pluginengine/change_helper.go deleted file mode 100644 index 55e8da48d..000000000 --- a/internal/pkg/pluginengine/change_helper.go +++ /dev/null @@ -1,327 +0,0 @@ -package pluginengine - -import ( - "fmt" - "reflect" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - "gopkg.in/yaml.v3" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" -) - -func generateCreateAction(tool *configmanager.Tool, description string) *Change { - return generateAction(tool, statemanager.ActionCreate, description) -} - -func generateUpdateAction(tool *configmanager.Tool, description string) *Change { - return generateAction(tool, statemanager.ActionUpdate, description) -} - -func generateDeleteAction(tool *configmanager.Tool, description string) *Change { - return generateAction(tool, statemanager.ActionDelete, description) -} - -func generateDeleteActionFromState(state statemanager.State) *Change { - return &Change{ - Tool: &configmanager.Tool{ - InstanceID: state.InstanceID, - Name: state.Name, - Options: state.Options, - }, - ActionName: statemanager.ActionDelete, - } -} - -func generateAction(tool *configmanager.Tool, action statemanager.ComponentAction, description string) *Change { - return &Change{ - Tool: tool.DeepCopy(), - ActionName: action, - Description: description, - } -} - -func ResourceDrifted(resourceStatusFromState, resourceStatusFromRead statemanager.ResourceStatus) (bool, error) { - // nil vs empty map - if cmp.Equal(resourceStatusFromState, resourceStatusFromRead, cmpopts.EquateEmpty()) { - return false, nil - } - - // use marshal and unmarshal to remove type details - resourceStatusFromStateBytes, err := yaml.Marshal(resourceStatusFromState) - if err != nil { - return true, err - } - resourceStatusFromStateWithoutType := map[string]interface{}{} - err = yaml.Unmarshal(resourceStatusFromStateBytes, &resourceStatusFromStateWithoutType) - if err != nil { - return true, err - } - - resourceStatusFromReadBytes, err := yaml.Marshal(resourceStatusFromRead) - if err != nil { - return true, err - } - resourceStatusFromReadWithoutType := map[string]interface{}{} - err = yaml.Unmarshal(resourceStatusFromReadBytes, &resourceStatusFromReadWithoutType) - if err != nil { - return true, err - } - - log.Debug(cmp.Diff(resourceStatusFromStateWithoutType, resourceStatusFromReadWithoutType)) - - return !cmp.Equal(resourceStatusFromStateWithoutType, resourceStatusFromReadWithoutType), nil -} - -func drifted(a, b map[string]interface{}) bool { - // nil vs empty map - if cmp.Equal(a, b, cmpopts.EquateEmpty()) { - return false - } - // since same struct will define custom type with underlying field type is string - // we use this filter and Transformer to make them Equal - // for example, we define a type "type customStr string" - // with this opt, cmp.Equal("test", customStr("test"), underlyingStringEqualOpt) return return true - underlyingStringEqualOpt := cmp.FilterValues(func(x, y interface{}) bool { - isString := func(v interface{}) bool { - return v != nil && reflect.TypeOf(v).ConvertibleTo(reflect.TypeOf(string(""))) - } - return isString(x) && isString(y) - }, cmp.Transformer("T", func(v interface{}) string { - return reflect.ValueOf(v).Convert(reflect.TypeOf(string(""))).String() - })) - - log.Debugf("detect tool sate changed => %s", cmp.Diff(a, b, underlyingStringEqualOpt)) - return !cmp.Equal(a, b, underlyingStringEqualOpt) -} - -// GetChangesForApply takes "State Manager" & "Config" then do some calculate and return a Plan. -// All actions should be executed is included in this Plan.changes. -// It generates "changes" according to: -// - config -// - state -// - resource status (by calling the Read() interface of the plugin) -func GetChangesForApply(smgr statemanager.Manager, cfg *configmanager.Config) (changes []*Change, err error) { - // 1. create a temporary state map used to store unprocessed tools. - tmpStatesMap := smgr.GetStatesMap().DeepCopy() - - // 2. handle dependency and sort the tools in the config into "batches" of tools - var batchesOfTools []configmanager.Tools - // the elements in batchesOfTools are sorted "batches" - // and each element/batch is a list of tools that, in theory, can run in parallel - // that is to say, the tools in the same batch won't depend on each other - batchesOfTools, err = topologicalSort(cfg.Tools) - if err != nil { - return changes, err - } - - // 3. generate changes for each tool - for _, batch := range batchesOfTools { - for _, tool := range batch { - state := smgr.GetState(statemanager.GenerateStateKeyByToolNameAndInstanceID(tool.Name, tool.InstanceID)) - - if state == nil { - // tool not in the state, create, no need to Read resource before Create - description := fmt.Sprintf("Tool (%s/%s) found in config but doesn't exist in the state, will be created.", tool.Name, tool.InstanceID) - changes = append(changes, generateCreateAction(tool, description)) - } else { - // tool found in the state - - // first, handle possible "outputs" references in the tool's config - // ignoring errors, since at this stage we are calculating changes, and the dependency might not have its output in the state yet - _ = HandleOutputsReferences(smgr, tool.Options) - - if drifted(state.Options, tool.Options) { - // tool's config differs from State's, Update - description := fmt.Sprintf("Tool (%s/%s) config drifted from the state, will be updated.", tool.Name, tool.InstanceID) - changes = append(changes, generateUpdateAction(tool, description)) - } else { - // tool's config is the same as State's - - // read resource status - resource, err := Read(tool) - if err != nil { - return changes, err - } - - if resource == nil { - // tool exists in the state, but resource doesn't exist, Create - description := fmt.Sprintf("Tool (%s/%s) state found but it seems the tool isn't created, will be created.", tool.Name, tool.InstanceID) - changes = append(changes, generateCreateAction(tool, description)) - } else if drifted, err := ResourceDrifted(state.ResourceStatus, resource); drifted || err != nil { - if err != nil { - return nil, err - } - // resource drifted from state, Update - description := fmt.Sprintf("Tool (%s/%s) drifted from the state, will be updated.", tool.Name, tool.InstanceID) - changes = append(changes, generateUpdateAction(tool, description)) - } else { - // resource is the same as the state, do nothing - log.Debugf("Tool (%s/%s) is the same as the state, do nothing.", tool.Name, tool.InstanceID) - } - } - } - - // delete the tool from the temporary state map since it's already been processed above - tmpStatesMap.Delete(statemanager.GenerateStateKeyByToolNameAndInstanceID(tool.Name, tool.InstanceID)) - } - } - - // what's left in the temporary state map "tmpStatesMap" contains tools that: - // - have a state (probably created previously) - // - don't have a definition in the config (probably deleted by the user) - // thus, we need to generate a "delete" change for it. - tmpStatesMap.Range(func(key, value interface{}) bool { - changes = append(changes, generateDeleteActionFromState(value.(statemanager.State))) - log.Infof("Change added: %s -> %s", key.(statemanager.StateKey), statemanager.ActionDelete) - return true - }) - - log.Debugf("Changes for the plan:") - for i, c := range changes { - log.Debugf("Change - %d/%d -> %s", i+1, len(changes), c.String()) - } - - return changes, nil -} - -// GetChangesForDelete takes "State Manager" & "Config" then do some calculation and return a Plan to delete all plugins in the Config. -// All actions should be executed is included in this Plan.changes. -func GetChangesForDelete(smgr statemanager.Manager, cfg *configmanager.Config, isForceDelete bool) (changes []*Change, err error) { - batchesOfTools, err := topologicalSort(cfg.Tools) - if err != nil { - return changes, err - } - - log.Debug("isForce:", isForceDelete) - for i := len(batchesOfTools) - 1; i >= 0; i-- { - batch := batchesOfTools[i] - for _, tool := range batch { - if !isForceDelete { - state := smgr.GetState(statemanager.GenerateStateKeyByToolNameAndInstanceID(tool.Name, tool.InstanceID)) - if state == nil { - continue - } - } - - description := fmt.Sprintf("Tool (%s/%s) will be deleted.", tool.Name, tool.InstanceID) - changes = append(changes, generateDeleteAction(tool, description)) - } - } - - log.Debugf("Changes for the plan:") - for i, c := range changes { - log.Debugf("Change - %d/%d -> %s", i+1, len(changes), c.String()) - } - - return changes, nil -} - -func GetChangesForDestroy(smgr statemanager.Manager, isForceDestroy bool) (changes []*Change, err error) { - // rebuilding tools from config - // because destroy will only be used when the config file is missing - var tools configmanager.Tools - for _, state := range smgr.GetStatesMap().ToList() { - tool := &configmanager.Tool{ - InstanceID: state.InstanceID, - Name: state.Name, - DependsOn: state.DependsOn, - Options: state.Options, - } - tools = append(tools, tool) - } - - batchesOfTools, err := topologicalSort(tools) - if err != nil { - return changes, err - } - - // reverse, for deletion - for i := len(batchesOfTools) - 1; i >= 0; i-- { - batch := batchesOfTools[i] - for _, tool := range batch { - if !isForceDestroy { - state := smgr.GetState(statemanager.GenerateStateKeyByToolNameAndInstanceID(tool.Name, tool.InstanceID)) - if state == nil { - continue - } - } - description := fmt.Sprintf("Tool (%s/%s) will be deleted.", tool.Name, tool.InstanceID) - changes = append(changes, generateDeleteAction(tool, description)) - } - } - - log.Debugf("Changes for the plan:") - for i, c := range changes { - log.Debugf("Change - %d/%d -> %s", i+1, len(changes), c.String()) - } - - return changes, nil -} - -// topologicalSortChangesInBatch returns a list of batches of changes, sorted by dependency defined in the config. -func topologicalSortChangesInBatch(changes []*Change) ([][]*Change, error) { - - // get tools from changes - tools := getToolsFromChanges(changes) - // map key to changes - changesKeyMap := getChangesKeyMap(changes) - - batchesOfChanges := make([][]*Change, 0) - - // topological sort the tools based on dependency - // tool in each batch does not have dependency on each other - // note: maybe tools gotten from changes do not contain all the tools in the config.Tools - // but it's still ok to call topologicalSort - batchesOfTools, err := topologicalSort(tools) - if err != nil { - return batchesOfChanges, err - } - - // group changes into batches based on the batches of tools - // so that the changes in each batch do not have dependency on each other - for _, batch := range batchesOfTools { - changesOneBatch := make([]*Change, 0) - // for each tool in the batch, find the change that matches it - for _, tool := range batch { - // only add the change that has the tool match with it - if change, ok := changesKeyMap[tool.KeyWithNameAndInstanceID()]; ok { - changesOneBatch = append(changesOneBatch, change) - } - } - - if len(changesOneBatch) > 0 { - batchesOfChanges = append(batchesOfChanges, changesOneBatch) - } - } - - return batchesOfChanges, nil -} - -func getToolsFromChanges(changes []*Change) configmanager.Tools { - // use slice instead of map to keep the order of tools - tools := make(configmanager.Tools, 0) - // use map to record the tool that has been added to the slice - toolsKeyMap := make(map[string]struct{}) - - // get tools from changes avoiding duplicated tools - for _, change := range changes { - if _, ok := toolsKeyMap[change.Tool.KeyWithNameAndInstanceID()]; !ok { - tools = append(tools, change.Tool) - toolsKeyMap[change.Tool.KeyWithNameAndInstanceID()] = struct{}{} - } - } - - return tools -} - -func getChangesKeyMap(changes []*Change) map[string]*Change { - changesKeyMap := make(map[string]*Change) - for _, change := range changes { - changesKeyMap[change.Tool.KeyWithNameAndInstanceID()] = change - } - return changesKeyMap -} diff --git a/internal/pkg/pluginengine/change_helper_test.go b/internal/pkg/pluginengine/change_helper_test.go deleted file mode 100644 index 1c23b5ef6..000000000 --- a/internal/pkg/pluginengine/change_helper_test.go +++ /dev/null @@ -1,90 +0,0 @@ -package pluginengine - -import ( - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/statemanager" -) - -func TestTopologicalSortChangesInBatch(t *testing.T) { - toolA := &configmanager.Tool{InstanceID: "a", Name: "a"} - toolB := &configmanager.Tool{InstanceID: "b", Name: "b"} - toolC := &configmanager.Tool{InstanceID: "c", Name: "c", DependsOn: []string{"a.a", "b.b"}} - toolD := &configmanager.Tool{InstanceID: "d", Name: "d", DependsOn: []string{"b.b"}} - toolE := &configmanager.Tool{InstanceID: "e", Name: "e", DependsOn: []string{"c.c"}} - - changes := []*Change{ - { - Tool: toolA, - ActionName: statemanager.ActionCreate, - }, - // there is no toolB - // because in real scenarios, some tools may not need to be changed - // that is to say, the tools extracted from changes may not contain all the tools defined in the config - // however, as this unit test demonstrates, the function still works properly in this situation - { - Tool: toolD, - ActionName: statemanager.ActionCreate, - }, - { - Tool: toolE, - ActionName: statemanager.ActionUpdate, - }, - // simulates a delete operation added at the end of apply - { - Tool: toolC, - ActionName: statemanager.ActionDelete, - }, - } - - expected := [][]*Change{ - // first batch - { - { - Tool: toolA, - ActionName: statemanager.ActionCreate, - }, - // although D depends on B - // but B is not in the changes list - // so D is in the first batch - { - Tool: toolD, - ActionName: statemanager.ActionCreate, - }, - }, - - // second batch - { - - { - Tool: toolC, - ActionName: statemanager.ActionDelete, - }, - }, - - // third batch - { - { - Tool: toolE, - ActionName: statemanager.ActionUpdate, - }, - }, - } - - _ = toolB - - actual, err := topologicalSortChangesInBatch(changes) - - assert.NoError(t, err) - - assert.Equal(t, len(expected), len(actual)) - for i, batch := range actual { - assert.Equal(t, len(expected[i]), len(batch)) - for j, change := range batch { - assert.Equal(t, *expected[i][j], *change) - } - } -} diff --git a/internal/pkg/pluginengine/cmd_apply.go b/internal/pkg/pluginengine/cmd_apply.go deleted file mode 100644 index 8dc550fe5..000000000 --- a/internal/pkg/pluginengine/cmd_apply.go +++ /dev/null @@ -1,64 +0,0 @@ -package pluginengine - -import ( - "errors" - "os" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/pluginmanager" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/interact" - "github.com/devstream-io/devstream/pkg/util/log" -) - -const askUserIfContinue string = "Continue? [y/n]" - -func Apply(configFile string, continueDirectly bool) error { - cfg, err := configmanager.NewManager(configFile).LoadConfig() - if err != nil { - return err - } - - err = pluginmanager.CheckLocalPlugins(cfg.Tools) - if err != nil { - log.Error(`Error checking required plugins. Maybe you forgot to run "dtm init" first?`) - return err - } - smgr, err := statemanager.NewManager(*cfg.Config.State) - if err != nil { - log.Debugf("Failed to get the manager: %s.", err) - return err - } - - changes, err := GetChangesForApply(smgr, cfg) - if err != nil { - log.Debugf("Get changes for apply failed: %s.", err) - return err - } - if len(changes) == 0 { - log.Info("No changes done since last apply. There is nothing to do.") - return nil - } - - for _, change := range changes { - log.Info(change.Description) - } - - if !continueDirectly { - continued := interact.AskUserIfContinue(askUserIfContinue) - if !continued { - os.Exit(0) - } - } - - errsMap := execute(smgr, changes, false) - if len(errsMap) != 0 { - for k, e := range errsMap { - log.Errorf("Errors Map: key(%s) -> value(%s)", k, e) - } - return errors.New("some error(s) occurred during plugins apply process") - } - - log.Success("All plugins applied successfully.") - return nil -} diff --git a/internal/pkg/pluginengine/cmd_delete.go b/internal/pkg/pluginengine/cmd_delete.go deleted file mode 100644 index 95c3d028f..000000000 --- a/internal/pkg/pluginengine/cmd_delete.go +++ /dev/null @@ -1,63 +0,0 @@ -package pluginengine - -import ( - "errors" - "os" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/pluginmanager" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/interact" - "github.com/devstream-io/devstream/pkg/util/log" -) - -func Remove(configFile string, continueDirectly bool, isForceDelete bool) error { - cfg, err := configmanager.NewManager(configFile).LoadConfig() - if err != nil { - return err - } - - err = pluginmanager.CheckLocalPlugins(cfg.Tools) - if err != nil { - log.Errorf(`Error checking required plugins. Maybe you forgot to run "dtm init" first?`) - return err - } - - smgr, err := statemanager.NewManager(*cfg.Config.State) - if err != nil { - log.Debugf("Failed to get the manager: %s.", err) - return err - } - - changes, err := GetChangesForDelete(smgr, cfg, isForceDelete) - - if err != nil { - return err - } - if len(changes) == 0 { - log.Info("Nothing needs to be deleted. There is nothing to do.") - return nil - } - for _, change := range changes { - log.Info(change.Description) - } - - if !continueDirectly { - continued := interact.AskUserIfContinue(askUserIfContinue) - if !continued { - os.Exit(0) - } - } - - errsMap := execute(smgr, changes, true) - if len(errsMap) != 0 { - err := errors.New("some error(s) occurred during plugins delete process") - for k, e := range errsMap { - log.Infof("%s -> %s.", k, e) - } - return err - } - - log.Success("All plugins deleted successfully.") - return nil -} diff --git a/internal/pkg/pluginengine/cmd_destroy.go b/internal/pkg/pluginengine/cmd_destroy.go deleted file mode 100644 index baa62907c..000000000 --- a/internal/pkg/pluginengine/cmd_destroy.go +++ /dev/null @@ -1,60 +0,0 @@ -package pluginengine - -import ( - "errors" - "fmt" - "os" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/interact" - "github.com/devstream-io/devstream/pkg/util/log" -) - -func Destroy(configFile string, continueDirectly bool, isForceDestroy bool) error { - cfg, err := configmanager.NewManager(configFile).LoadConfig() - if err != nil { - return err - } - if cfg == nil { - return fmt.Errorf("failed to load the config file") - } - - smgr, err := statemanager.NewManager(*cfg.Config.State) - if err != nil { - log.Debugf("Failed to get the manager: %s.", err) - return err - } - - changes, err := GetChangesForDestroy(smgr, isForceDestroy) - if err != nil { - log.Debugf("Get changes failed: %s.", err) - return err - } - if len(changes) == 0 { - log.Info("No tools have been deployed now. There is nothing to do.") - return nil - } - - for _, change := range changes { - log.Info(change.Description) - } - - if !continueDirectly { - continued := interact.AskUserIfContinue(askUserIfContinue) - if !continued { - os.Exit(0) - } - } - - errsMap := execute(smgr, changes, true) - if len(errsMap) != 0 { - for k, e := range errsMap { - log.Infof("%s -> %s", k, e) - } - return errors.New("some error(s) occurred during plugins destroy process") - } - - log.Success("All plugins destroyed successfully.") - return nil -} diff --git a/internal/pkg/pluginengine/cmd_verify.go b/internal/pkg/pluginengine/cmd_verify.go deleted file mode 100644 index 363e1fe4d..000000000 --- a/internal/pkg/pluginengine/cmd_verify.go +++ /dev/null @@ -1,49 +0,0 @@ -package pluginengine - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/pluginmanager" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" -) - -// Verify returns true if all the comments in this function are met -func Verify(configFile string) bool { - // 1. loading config file succeeded - cfg, err := configmanager.NewManager(configFile).LoadConfig() - if err != nil { - log.Errorf("verify failed, error: %s", err) - } - - if cfg == nil { - return false - } - - // 2. according to the config, all needed plugins exist - err = pluginmanager.CheckLocalPlugins(cfg.Tools) - if err != nil { - log.Info(err) - log.Info(`Maybe you forgot to run "dtm init" first?`) - return false - } - // 3. can successfully create the state - smgr, err := statemanager.NewManager(*cfg.Config.State) - if err != nil { - log.Errorf("Something is wrong with the state: %s.", err) - return false - } - - // 4. if the config, state and the resources/tools are exactly the same - changes, err := GetChangesForApply(smgr, cfg) - if err != nil { - log.Errorf("Get changes failed: %s.", err) - return false - } - if len(changes) != 0 { - for _, change := range changes { - log.Info(change.Description) - } - return false - } - return true -} diff --git a/internal/pkg/pluginengine/outputs.go b/internal/pkg/pluginengine/outputs.go deleted file mode 100644 index 0a22945d0..000000000 --- a/internal/pkg/pluginengine/outputs.go +++ /dev/null @@ -1,67 +0,0 @@ -package pluginengine - -import ( - "fmt" - "regexp" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" -) - -// HandleOutputsReferences renders outputs references in config file recursively. -// The parameter options will be changed. -func HandleOutputsReferences(smgr statemanager.Manager, options configmanager.RawOptions) []error { - errorsList := make([]error, 0) - - for optionKey, optionInterface := range options { - switch optionValue := optionInterface.(type) { - // only process string values in the options - // since all outputs references are strings, not ints, not booleans, not maps - case string: - log.Debugf("Before: %s: %s", optionKey, optionValue) - match, toolName, instanceID, outputReferenceKey := getToolNamePluginOutputKey(optionValue) - // do nothing, if the value string isn't in the format of a valid output reference - if !match { - continue - } - outputs, err := smgr.GetOutputs(statemanager.GenerateStateKeyByToolNameAndInstanceID(toolName, instanceID)) - if err != nil { - errorsList = append(errorsList, err) - continue - } - if val, ok := outputs[outputReferenceKey]; ok { - options[optionKey] = replaceOutputKeyWithValue(optionValue, val.(string)) - log.Debugf("After: %s: %s", optionKey, options[optionKey]) - } else { - errorsList = append(errorsList, fmt.Errorf("can't find Output reference key %s", outputReferenceKey)) - } - case configmanager.RawOptions: - // recursive if the value is a map (which means Tool.Option is a nested map) - log.Debugf("Got nested map: %v", optionValue) - errorsList = append(errorsList, HandleOutputsReferences(smgr, optionValue)...) - } - } - - log.Debugf("Final options: %v.", options) - - return errorsList -} - -// getToolNamePluginKindAndOutputReferenceKey returns (false, "", "", "") if regex doesn't match -// if matched, returns (true, name, instanceID, key) -func getToolNamePluginOutputKey(s string) (bool, string, string, string) { - regex := `.*\${{\s*([^.]*)\.([^.]*)\.outputs\.([^.\s]*)\s*}}.*` - r := regexp.MustCompile(regex) - if !r.MatchString(s) { - return false, "", "", "" - } - results := r.FindStringSubmatch(s) - return true, results[1], results[2], results[3] -} - -func replaceOutputKeyWithValue(s, val string) string { - regex := `\${{\s*[^.]*\.[^.]*\.outputs\.[^.]*\s*}}` - r := regexp.MustCompile(regex) - return r.ReplaceAllString(s, val) -} diff --git a/internal/pkg/pluginengine/outputs_test.go b/internal/pkg/pluginengine/outputs_test.go deleted file mode 100644 index e2912578c..000000000 --- a/internal/pkg/pluginengine/outputs_test.go +++ /dev/null @@ -1,54 +0,0 @@ -package pluginengine - -import ( - "testing" -) - -type getToolNamePluginOutputKeyTest struct { - arg string - match bool - name string - plugin string - key string -} - -var getToolNamePluginOutputKeyTests = []getToolNamePluginOutputKeyTest{ - {"${{a.b.outputs.d}}", true, "a", "b", "d"}, - {"prefix${{a.b.outputs.d}}suffix", true, "a", "b", "d"}, - {"${{ a.b.outputs.d }}", true, "a", "b", "d"}, - {"${{ a.b.c }}", false, "", "", ""}, -} - -func TestGetToolNamePluginOutputKey(t *testing.T) { - for _, test := range getToolNamePluginOutputKeyTests { - match, name, plugin, key := getToolNamePluginOutputKey(test.arg) - if match != test.match || name != test.name || plugin != test.plugin || key != test.key { - t.Errorf( - "Output %t, %s, %s, %s not equal to expected %t, %s, %s, %s. Input: %s.", - match, name, plugin, key, - test.match, test.name, test.plugin, test.key, - test.arg, - ) - } - } -} - -type replaceOutputKeyWithValueTest struct { - arg1 string - arg2 string - expected string -} - -var replaceOutputKeyWithValueTests = []replaceOutputKeyWithValueTest{ - {"${{a.b.outputs.d}}", "value", "value"}, - {"prefix/${{ a.b.outputs.d }}/suffix", "value", "prefix/value/suffix"}, - {"${{a.b.c}}", "value", "${{a.b.c}}"}, -} - -func TestIsValidOutputsReference(t *testing.T) { - for _, test := range replaceOutputKeyWithValueTests { - if got := replaceOutputKeyWithValue(test.arg1, test.arg2); got != test.expected { - t.Errorf("Output %s not equal to expected %s. Input: %s, %s.", got, test.expected, test.arg1, test.arg2) - } - } -} diff --git a/internal/pkg/pluginengine/plugin.go b/internal/pkg/pluginengine/plugin.go deleted file mode 100644 index 18e9f919b..000000000 --- a/internal/pkg/pluginengine/plugin.go +++ /dev/null @@ -1,72 +0,0 @@ -package pluginengine - -import ( - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/pluginmanager" - "github.com/devstream-io/devstream/internal/pkg/statemanager" -) - -// DevStreamPlugin is a struct, on which Create/Read/Update/Delete interfaces are defined. -type DevStreamPlugin interface { - // Create, Read, and Update return two results, the first being the "state" - Create(configmanager.RawOptions) (statemanager.ResourceStatus, error) - Read(configmanager.RawOptions) (statemanager.ResourceStatus, error) - Update(configmanager.RawOptions) (statemanager.ResourceStatus, error) - // Delete returns (true, nil) if there is no error; otherwise it returns (false, error) - Delete(configmanager.RawOptions) (bool, error) -} - -// Create loads the plugin and calls the Create method of that plugin. -func Create(tool *configmanager.Tool) (statemanager.ResourceStatus, error) { - pluginDir, err := pluginmanager.GetPluginDir() - if err != nil { - return nil, err - } - - p, err := loadPlugin(pluginDir, tool) - if err != nil { - return nil, err - } - return p.Create(tool.Options) -} - -// Update loads the plugin and calls the Update method of that plugin. -func Update(tool *configmanager.Tool) (statemanager.ResourceStatus, error) { - pluginDir, err := pluginmanager.GetPluginDir() - if err != nil { - return nil, err - } - - p, err := loadPlugin(pluginDir, tool) - if err != nil { - return nil, err - } - return p.Update(tool.Options) -} - -func Read(tool *configmanager.Tool) (statemanager.ResourceStatus, error) { - pluginDir, err := pluginmanager.GetPluginDir() - if err != nil { - return nil, err - } - - p, err := loadPlugin(pluginDir, tool) - if err != nil { - return nil, err - } - return p.Read(tool.Options) -} - -// Delete loads the plugin and calls the Delete method of that plugin. -func Delete(tool *configmanager.Tool) (bool, error) { - pluginDir, err := pluginmanager.GetPluginDir() - if err != nil { - return false, err - } - - p, err := loadPlugin(pluginDir, tool) - if err != nil { - return false, err - } - return p.Delete(tool.Options) -} diff --git a/internal/pkg/pluginengine/plugin_helper.go b/internal/pkg/pluginengine/plugin_helper.go deleted file mode 100644 index f6a0f761d..000000000 --- a/internal/pkg/pluginengine/plugin_helper.go +++ /dev/null @@ -1,29 +0,0 @@ -package pluginengine - -import ( - "fmt" - "plugin" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" -) - -func loadPlugin(pluginDir string, tool *configmanager.Tool) (DevStreamPlugin, error) { - mod := fmt.Sprintf("%s/%s", pluginDir, tool.GetPluginFileName()) - plug, err := plugin.Open(mod) - if err != nil { - return nil, err - } - - var devStreamPlugin DevStreamPlugin - symDevStreamPlugin, err := plug.Lookup("DevStreamPlugin") - if err != nil { - return nil, err - } - - devStreamPlugin, ok := symDevStreamPlugin.(DevStreamPlugin) - if !ok { - return nil, fmt.Errorf("DevStreamPlugin type error") - } - - return devStreamPlugin, nil -} diff --git a/internal/pkg/pluginengine/pluginengine_suite_test.go b/internal/pkg/pluginengine/pluginengine_suite_test.go deleted file mode 100644 index be4fe3a64..000000000 --- a/internal/pkg/pluginengine/pluginengine_suite_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package pluginengine_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestPluginengine(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Pluginengine Suite") -} diff --git a/internal/pkg/pluginengine/pluginengine_test.go b/internal/pkg/pluginengine/pluginengine_test.go deleted file mode 100644 index 61b9b6db3..000000000 --- a/internal/pkg/pluginengine/pluginengine_test.go +++ /dev/null @@ -1,242 +0,0 @@ -package pluginengine_test - -import ( - "fmt" - "os" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/internal/pkg/backend/local" - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/pluginengine" - "github.com/devstream-io/devstream/internal/pkg/statemanager" -) - -var _ = Describe("Pluginengine", func() { - var ( - smgr statemanager.Manager - err error - - trelloInstance = "mytrelloboard" - trelloName = "trello" - expectedBoardId = "1" - expectedTodoListId = "2" - trelloKey = statemanager.StateKey(fmt.Sprintf("%s_%s", trelloName, trelloInstance)) - ) - - BeforeEach(func() { - defer GinkgoRecover() - - stateCfg := configmanager.State{ - Backend: "local", - Options: configmanager.StateConfigOptions{ - StateFile: "devstream.state", - }, - } - smgr, err = statemanager.NewManager(stateCfg) - Expect(err).ToNot(HaveOccurred()) - Expect(smgr).NotTo(BeNil()) - _, _ = GinkgoWriter.Write([]byte("new a statemanager")) - - DeferCleanup(func() { - os.Remove(local.DefaultStateFile) - }) - }) - - It("should be 'one install'", func() { - instanceID := "a" - name := "tool-a" - - cfg := &configmanager.Config{ - Tools: configmanager.Tools{getTool(name, instanceID)}, - } - changes, _ := pluginengine.GetChangesForApply(smgr, cfg) - GinkgoWriter.Print(changes) - Expect(len(changes)).To(Equal(1)) - c := changes[0] - Expect(c.Tool.InstanceID).To(Equal(instanceID)) - Expect(c.ActionName).To(Equal(statemanager.ActionCreate)) - }) - - It("should be 'two install'", func() { - instanceID1, instanceID2 := "a", "b" - name1, name2 := "tool-a", "too-b" - - cfg := &configmanager.Config{ - Tools: configmanager.Tools{getTool(name1, instanceID1), getTool(name2, instanceID2)}, - } - changes, _ := pluginengine.GetChangesForApply(smgr, cfg) - - Expect(len(changes)).To(Equal(2)) - c1 := changes[0] - Expect(c1.Tool.InstanceID).To(Equal(instanceID1)) - Expect(c1.Tool.Name).To(Equal(name1)) - Expect(c1.ActionName).To(Equal(statemanager.ActionCreate)) - - c2 := changes[1] - Expect(c2.Tool.InstanceID).To(Equal(instanceID2)) - Expect(c2.Tool.Name).To(Equal(name2)) - Expect(c2.ActionName).To(Equal(statemanager.ActionCreate)) - }) - - It("should be 1 uninstall when `dtm delete` is triggered against a config with 1 tool and a successful state", func() { - instanceID := "a" - name := "tool-a" - - cfg := &configmanager.Config{ - Tools: configmanager.Tools{getTool(name, instanceID)}, - } - - err = smgr.AddState(statemanager.StateKey(fmt.Sprintf("%s_%s", name, instanceID)), statemanager.State{}) - Expect(err).NotTo(HaveOccurred()) - changes, _ := pluginengine.GetChangesForDelete(smgr, cfg, false) - - Expect(len(changes)).To(Equal(1)) - c := changes[0] - Expect(c.Tool.InstanceID).To(Equal(instanceID)) - Expect(c.Tool.Name).To(Equal(name)) - Expect(c.ActionName).To(Equal(statemanager.ActionDelete)) - }) - - It("should handle outputs correctly", func() { - resStatus := &statemanager.ResourceStatus{} - resStatus.SetOutputs(statemanager.ResourceOutputs{ - "boardId": expectedBoardId, - "todoListId": expectedTodoListId, - }) - trelloState := statemanager.State{ - InstanceID: "mytrelloboard", - Name: "trello", - Options: configmanager.RawOptions{}, - ResourceStatus: *resStatus, - } - err = smgr.AddState(trelloKey, trelloState) - Expect(err).NotTo(HaveOccurred()) - - dependantOptions := configmanager.RawOptions{ - "boardId": fmt.Sprintf("${{ %s.%s.outputs.boardId }}", trelloName, trelloInstance), - "todoListId": fmt.Sprintf("${{ %s.%s.outputs.todoListId }}", trelloName, trelloInstance), - } - expectResult := configmanager.RawOptions{ - "boardId": expectedBoardId, - "todoListId": expectedTodoListId, - } - errs := pluginengine.HandleOutputsReferences(smgr, dependantOptions) - Expect(len(errs)).To(BeZero()) - Expect(dependantOptions).To(Equal(expectResult)) - }) - - It("should handle output interpolation correctly", func() { - trelloState := statemanager.State{ - InstanceID: "mytrelloboard", - Name: "trello", - Options: configmanager.RawOptions{}, - ResourceStatus: statemanager.ResourceStatus{ - "outputs": statemanager.ResourceOutputs{ - "boardId": expectedBoardId, - }, - }, - } - err = smgr.AddState(trelloKey, trelloState) - Expect(err).NotTo(HaveOccurred()) - - dependantOptions := map[string]interface{}{ - "boardId": fmt.Sprintf("prefix/${{ %s.%s.outputs.boardId }}/suffix", trelloName, trelloInstance), - } - expectResult := map[string]interface{}{ - "boardId": fmt.Sprintf("prefix/%s/suffix", expectedBoardId), - } - errs := pluginengine.HandleOutputsReferences(smgr, dependantOptions) - Expect(len(errs)).To(BeZero()) - Expect(dependantOptions).To(Equal(expectResult)) - }) - - It("should give an error when output doesn't exist in the state", func() { - trelloState := statemanager.State{ - Name: "trello", - InstanceID: "mytrelloboard", - Options: map[string]interface{}{}, - ResourceStatus: map[string]interface{}{}, - } - err = smgr.AddState(trelloKey, trelloState) - Expect(err).NotTo(HaveOccurred()) - - dependantOptions := map[string]interface{}{ - "boardId": fmt.Sprintf("${{ %s.%s.outputs.boardId }}", trelloName, trelloInstance), - } - expectResult := map[string]interface{}{ - "boardId": fmt.Sprintf("${{ %s.%s.outputs.boardId }}", trelloName, trelloInstance), - } - errs := pluginengine.HandleOutputsReferences(smgr, dependantOptions) - Expect(len(errs)).To(Equal(1)) - Expect(dependantOptions).To(Equal(expectResult)) - }) - - It("should give an error when the referred key doesn't exist", func() { - trelloState := statemanager.State{ - Name: "trello", - InstanceID: "mytrelloboard", - Options: configmanager.RawOptions{}, - ResourceStatus: statemanager.ResourceStatus{ - "outputs": statemanager.ResourceOutputs{ - "boardId": expectedBoardId, - "todoListId": expectedTodoListId, - }, - }, - } - err = smgr.AddState(trelloKey, trelloState) - Expect(err).NotTo(HaveOccurred()) - - dependantOptions := map[string]interface{}{ - "boardId": fmt.Sprintf("${{ %s.%s.outputs.boardId }}", trelloName, trelloInstance), - "todoListId": fmt.Sprintf("${{ %s.%s.outputs.todoListId }}", trelloName, trelloInstance), - "someKey": fmt.Sprintf("${{ %s.%s.outputs.keyNotExist }}", trelloName, trelloInstance), - } - expectResult := map[string]interface{}{ - "boardId": expectedBoardId, - "todoListId": expectedTodoListId, - "someKey": fmt.Sprintf("${{ %s.%s.outputs.keyNotExist }}", trelloName, trelloInstance), - } - errs := pluginengine.HandleOutputsReferences(smgr, dependantOptions) - Expect(len(errs)).To(Equal(1)) - Expect(dependantOptions).To(Equal(expectResult)) - }) - - It("should work for nested maps", func() { - trelloState := statemanager.State{ - Name: trelloName, - InstanceID: trelloInstance, - Options: configmanager.RawOptions{}, - ResourceStatus: statemanager.ResourceStatus{ - "outputs": statemanager.ResourceOutputs{ - "boardId": expectedBoardId, - }, - }, - } - err = smgr.AddState(trelloKey, trelloState) - Expect(err).NotTo(HaveOccurred()) - - dependantOptions := configmanager.RawOptions{ - "outerKey": configmanager.RawOptions{ - "innerKey": fmt.Sprintf("${{ %s.%s.outputs.boardId }}", trelloName, trelloInstance), - }, - } - expectResult := configmanager.RawOptions{ - "outerKey": configmanager.RawOptions{ - "innerKey": expectedBoardId, - }, - } - errs := pluginengine.HandleOutputsReferences(smgr, dependantOptions) - Expect(len(errs)).To(Equal(0)) - Expect(dependantOptions).To(Equal(expectResult)) - }) -}) - -func getTool(name, instance string) *configmanager.Tool { - return &configmanager.Tool{ - Name: name, - InstanceID: instance, - Options: map[string]interface{}{"key": "value"}, - } -} diff --git a/internal/pkg/pluginengine/topological_sort.go b/internal/pkg/pluginengine/topological_sort.go deleted file mode 100644 index c188bcc20..000000000 --- a/internal/pkg/pluginengine/topological_sort.go +++ /dev/null @@ -1,79 +0,0 @@ -package pluginengine - -import ( - "fmt" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/pkg/util/log" -) - -func dependencyResolved(tool *configmanager.Tool, unprocessedNodeSet map[string]bool) bool { - res := true - - for _, dep := range tool.DependsOn { - // if the tool's dependency is still not processed yet / still in the graph - log.Debugf("TOOL %s.%s dependency NOT solved\n", tool.Name, tool.InstanceID) - if _, ok := unprocessedNodeSet[dep]; ok { - res = false - break - } - } - - log.Debugf("TOOL %s %s %t\n", tool.Name, tool.InstanceID, res) - return res -} - -func topologicalSort(tools configmanager.Tools) ([]configmanager.Tools, error) { - // the final result that contains sorted Tools - // it's a sorted/ordered slice, - // each element is a slice of Tools that can run parallel without any particular order - res := make([]configmanager.Tools, 0) - - // a "graph", which contains "nodes" that haven't been processed yet - unprocessedNodeSet := make(map[string]bool) - for _, tool := range tools { - unprocessedNodeSet[tool.KeyWithNameAndInstanceID()] = true - } - - // while there is still a node in the graph left to be processed: - for len(unprocessedNodeSet) > 0 { - // the next batch of tools that can run in parallel - batch := make(configmanager.Tools, 0) - - for _, tool := range tools { - // if the tool has already been processed (not in the unprocessedNodeSet anymore), pass - if _, ok := unprocessedNodeSet[tool.KeyWithNameAndInstanceID()]; !ok { - continue - } - - // if there isn't any dependency: it's the "start" of the graph - // we can put it into the first batch - if len(tool.DependsOn) == 0 { - log.Debugf("TOOL %s.%s dependency already solved\n", tool.Name, tool.InstanceID) - batch = append(batch, tool) - } else { - if dependencyResolved(tool, unprocessedNodeSet) { - log.Debugf("TOOL %s.%s dependency already solved\n", tool.Name, tool.InstanceID) - batch = append(batch, tool) - } - } - } - log.Debugf("BATCH: %v", batch) - - // there are still nodes unprocessed but there is no node whose dependency is solved - // this means there might be a loop in the graph - if len(batch) == 0 && len(unprocessedNodeSet) > 0 { - return res, fmt.Errorf("dependency loop detected in the config") - } - - // remove tools from the unprocessedNodeSet because they have been added to the batch - for _, tool := range batch { - delete(unprocessedNodeSet, tool.KeyWithNameAndInstanceID()) - } - - // add the batch to the final result - res = append(res, batch) - } - - return res, nil -} diff --git a/internal/pkg/pluginengine/topological_sort_test.go b/internal/pkg/pluginengine/topological_sort_test.go deleted file mode 100644 index a99f1d707..000000000 --- a/internal/pkg/pluginengine/topological_sort_test.go +++ /dev/null @@ -1,100 +0,0 @@ -package pluginengine - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" -) - -func TestNoDependency(t *testing.T) { - tools := configmanager.Tools{ - {InstanceID: "a", Name: "a"}, - {InstanceID: "b", Name: "b"}, - {InstanceID: "c", Name: "c"}, - {InstanceID: "d", Name: "d"}, - } - expectedRes := - []configmanager.Tools{ - { - {InstanceID: "a", Name: "a"}, - {InstanceID: "b", Name: "b"}, - {InstanceID: "c", Name: "c"}, - {InstanceID: "d", Name: "d"}, - }, - } - actualRes, err := topologicalSort(tools) - assert.Equal(t, nil, err) - assert.Equal(t, expectedRes, actualRes) -} - -func TestSingleDependency(t *testing.T) { - tools := configmanager.Tools{ - {InstanceID: "a", Name: "a"}, - {InstanceID: "c", Name: "c", DependsOn: []string{"a.a"}}, - } - expectedRes := - []configmanager.Tools{ - { - {InstanceID: "a", Name: "a"}, - }, - { - {InstanceID: "c", Name: "c", DependsOn: []string{"a.a"}}, - }, - } - actualRes, err := topologicalSort(tools) - assert.Equal(t, nil, err) - assert.Equal(t, expectedRes, actualRes) -} - -func TestMultiDependencies(t *testing.T) { - tools := configmanager.Tools{ - {InstanceID: "a", Name: "a"}, - {InstanceID: "b", Name: "b"}, - {InstanceID: "c", Name: "c", DependsOn: []string{"a.a", "b.b"}}, - {InstanceID: "d", Name: "d", DependsOn: []string{"c.c"}}, - } - expectedRes := - []configmanager.Tools{ - { - {InstanceID: "a", Name: "a"}, - {InstanceID: "b", Name: "b"}, - }, - { - {InstanceID: "c", Name: "c", DependsOn: []string{"a.a", "b.b"}}, - }, - { - {InstanceID: "d", Name: "d", DependsOn: []string{"c.c"}}, - }, - } - actualRes, err := topologicalSort(tools) - assert.Equal(t, nil, err) - assert.Equal(t, expectedRes, actualRes) -} - -func TestDependencyLoop(t *testing.T) { - tools := configmanager.Tools{ - {InstanceID: "a", Name: "a"}, - {InstanceID: "b", Name: "b", DependsOn: []string{"d.d"}}, - {InstanceID: "c", Name: "c", DependsOn: []string{"b.b"}}, - {InstanceID: "d", Name: "d", DependsOn: []string{"c.c"}}, - } - expectedRes := - []configmanager.Tools{ - { - {InstanceID: "a", Name: "a"}, - {InstanceID: "b", Name: "b"}, - }, - { - {InstanceID: "c", Name: "c", DependsOn: []string{"a.a", "b.b"}}, - }, - { - {InstanceID: "d", Name: "d", DependsOn: []string{"c.c"}}, - }, - } - actualRes, err := topologicalSort(tools) - assert.Equal(t, fmt.Errorf("dependency loop detected in the config"), err) - assert.NotEqual(t, expectedRes, actualRes) -} diff --git a/internal/pkg/pluginmanager/downloader.go b/internal/pkg/pluginmanager/downloader.go deleted file mode 100644 index ef6d6cc8b..000000000 --- a/internal/pkg/pluginmanager/downloader.go +++ /dev/null @@ -1,57 +0,0 @@ -// 1. get plugins *.so -// 2. show progress bar on console - -package pluginmanager - -import ( - "fmt" - "net/http" - "os" - "path/filepath" - "time" - - "github.com/devstream-io/devstream/pkg/util/downloader" - "github.com/devstream-io/devstream/pkg/util/log" -) - -type PluginDownloadClient struct { - *http.Client - baseURL string -} - -func NewPluginDownloadClient(baseURL string) *PluginDownloadClient { - dClient := PluginDownloadClient{} - dClient.Client = http.DefaultClient - dClient.Client.Timeout = time.Second * 60 * 60 - dClient.baseURL = baseURL - return &dClient -} - -// download from release assets -func (pd *PluginDownloadClient) download(pluginsDir, pluginOrMD5Filename, version string) error { - downloadURL := fmt.Sprintf("%s/v%s/%s", pd.baseURL, version, pluginOrMD5Filename) - dc := downloader.New().WithProgressBar().WithClient(pd.Client) - _, err := dc.Download(downloadURL, pluginOrMD5Filename, pluginsDir) - return err -} - -// reDownload plugins from remote -func (pd *PluginDownloadClient) reDownload(pluginDir, pluginFileName, pluginMD5FileName, version string) error { - if err := os.Remove(filepath.Join(pluginDir, pluginFileName)); err != nil { - return err - } - if err := os.Remove(filepath.Join(pluginDir, pluginMD5FileName)); err != nil { - return err - } - // download .so file - if err := pd.download(pluginDir, pluginFileName, version); err != nil { - return err - } - log.Successf("[%s] download succeeded.", pluginFileName) - // download .md5 file - if err := pd.download(pluginDir, pluginMD5FileName, version); err != nil { - return err - } - log.Successf("[%s] download succeeded.", pluginMD5FileName) - return nil -} diff --git a/internal/pkg/pluginmanager/downloader_test.go b/internal/pkg/pluginmanager/downloader_test.go deleted file mode 100644 index 3076e311b..000000000 --- a/internal/pkg/pluginmanager/downloader_test.go +++ /dev/null @@ -1,82 +0,0 @@ -package pluginmanager - -import ( - "os" - "path/filepath" - "runtime" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" -) - -var _ = Describe("downloader", func() { - var ( - server *mockPluginServer - pdc *PluginDownloadClient - tempDir string - ) - - const ( - pluginName = "test_plugin" - pluginVersion = "0.1.0" - ) - - BeforeEach(func() { - tempDir = GinkgoT().TempDir() - server = newMockPluginServer() - pdc = NewPluginDownloadClient(server.URL()) - }) - - Describe("download func", func() { - When("server return err code", func() { - BeforeEach(func() { - server.registerPluginNotFound(pluginName, pluginVersion, runtime.GOOS, runtime.GOARCH) - }) - - It("should return err for download from url error", func() { - tool := &configmanager.Tool{Name: pluginName} - pluginFileName := tool.GetPluginFileNameWithOSAndArch(runtime.GOOS, runtime.GOARCH) - err := pdc.download(tempDir, pluginFileName, pluginVersion) - Expect(err).Error().Should(HaveOccurred()) - Expect(err.Error()).Should(ContainSubstring("404")) - }) - }) - - When("response return success", func() { - var testContent string - - BeforeEach(func() { - testContent = "test" - server.registerPluginOK(pluginName, testContent, pluginVersion, runtime.GOOS, runtime.GOARCH) - }) - - It("should download plugins successfully", func() { - // plugin file name and md5 file name - tool := &configmanager.Tool{Name: pluginName} - pluginFileName := tool.GetPluginFileNameWithOSAndArch(runtime.GOOS, runtime.GOARCH) - pluginMD5FileName := tool.GetPluginMD5FileNameWithOSAndArch(runtime.GOOS, runtime.GOARCH) - - // download .so file - err := pdc.download(tempDir, pluginFileName, pluginVersion) - Expect(err).ShouldNot(HaveOccurred()) - // check plugin file is downloaded - fileContent, err := os.ReadFile(filepath.Join(tempDir, pluginFileName)) - Expect(err).ShouldNot(HaveOccurred()) - Expect(string(fileContent)).Should(Equal(testContent)) - - // download .md5 file - err = pdc.download(tempDir, pluginMD5FileName, pluginVersion) - Expect(err).ShouldNot(HaveOccurred()) - md5Matched, err := ifPluginAndMD5Match(tempDir, pluginFileName, pluginMD5FileName) - Expect(err).ShouldNot(HaveOccurred()) - Expect(md5Matched).Should(BeTrue()) - }) - }) - }) - - AfterEach(func() { - server.Close() - }) -}) diff --git a/internal/pkg/pluginmanager/manager.go b/internal/pkg/pluginmanager/manager.go deleted file mode 100644 index c38d547d8..000000000 --- a/internal/pkg/pluginmanager/manager.go +++ /dev/null @@ -1,172 +0,0 @@ -package pluginmanager - -import ( - "errors" - "fmt" - "os" - "path/filepath" - "strings" - - "github.com/spf13/viper" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/version" - "github.com/devstream-io/devstream/pkg/util/log" - "github.com/devstream-io/devstream/pkg/util/md5" -) - -const defaultReleaseUrl = "https://download.devstream.io" - -func DownloadPlugins(tools configmanager.Tools, pluginDir, os, arch string) error { - return downloadPlugins(defaultReleaseUrl, tools, pluginDir, os, arch, version.Version) -} - -func downloadPlugins(baseURL string, tools configmanager.Tools, pluginDir, osName, arch, version string) error { - if pluginDir == "" { - return fmt.Errorf(`plugins directory should not be ""`) - } - - log.Infof("Using dir <%s> to store plugins.", pluginDir) - - // download all plugins that don't exist locally - dc := NewPluginDownloadClient(baseURL) - - for _, tool := range tools { - pluginName := tool.GetPluginNameWithOSAndArch(osName, arch) - pluginFileName := tool.GetPluginFileNameWithOSAndArch(osName, arch) - pluginMD5FileName := tool.GetPluginMD5FileNameWithOSAndArch(osName, arch) - - // a new line to make outputs more beautiful - fmt.Println() - log.Separator(pluginName) - - _, pluginFileErr := os.Stat(filepath.Join(pluginDir, pluginFileName)) - _, pluginMD5FileErr := os.Stat(filepath.Join(pluginDir, pluginMD5FileName)) - - // plugin does not exist - if pluginFileErr != nil { - if !errors.Is(pluginFileErr, os.ErrNotExist) { - return pluginFileErr - } - // download .so file - if err := dc.download(pluginDir, pluginFileName, version); err != nil { - return err - } - log.Successf("[%s] download succeeded.", pluginFileName) - } - // .md5 does not exist - if pluginMD5FileErr != nil { - if !errors.Is(pluginMD5FileErr, os.ErrNotExist) { - return pluginMD5FileErr - } - // download .md5 file - if err := dc.download(pluginDir, pluginMD5FileName, version); err != nil { - return err - } - log.Successf("[%s] download succeeded.", pluginMD5FileName) - } - - // check if the plugin matches with .md5 - isMD5Match, err := ifPluginAndMD5Match(pluginDir, pluginFileName, pluginMD5FileName) - if err != nil { - return err - } - - if !isMD5Match { - // if existing .so doesn't match with .md5, re-download - log.Infof("Plugin: [%s] doesn't match with .md5 and will be downloaded.", pluginFileName) - if err = dc.reDownload(pluginDir, pluginFileName, pluginMD5FileName, version); err != nil { - return err - } - // check if the downloaded plugin md5 matches with .md5 - if isMD5Match, err = ifPluginAndMD5Match(pluginDir, pluginFileName, pluginMD5FileName); err != nil { - return err - } - if !isMD5Match { - return fmt.Errorf("plugin %s doesn't match with .md5", tool.Name) - } - } - - log.Infof("Initialize [%s] finished.", pluginName) - log.Separatorf(pluginName) - } - return nil -} - -// CheckLocalPlugins checks if the local plugins exists, and matches with md5 value. -func CheckLocalPlugins(tools configmanager.Tools) error { - pluginDir, err := GetPluginDir() - if err != nil { - return err - } - - for _, tool := range tools { - pluginFileName := tool.GetPluginFileName() - pluginMD5FileName := tool.GetPluginMD5FileName() - if _, err = os.Stat(filepath.Join(pluginDir, pluginFileName)); err != nil { - if errors.Is(err, os.ErrNotExist) { - return fmt.Errorf("plugin %s(%s) doesn't exist", tool.Name, pluginFileName) - } - return err - } - if _, err = os.Stat(filepath.Join(pluginDir, pluginMD5FileName)); err != nil { - if errors.Is(err, os.ErrNotExist) { - return fmt.Errorf(".md5 file of plugin %s doesn't exist", tool.Name) - } - return err - } - matched, err := ifPluginAndMD5Match(pluginDir, pluginFileName, pluginMD5FileName) - if err != nil { - return err - } - if !matched { - return fmt.Errorf("plugin %s doesn't match with .md5", tool.Name) - } - } - return nil -} - -// pluginAndMD5Matches checks if the plugins match with .md5 -// it returns true if the plugin matches with .md5 -// it returns error if the plugin doesn't exist or .md5 doesn't exist -func ifPluginAndMD5Match(pluginDir, soFileName, md5FileName string) (bool, error) { - soFilePath := filepath.Join(pluginDir, soFileName) - md5FilePath := filepath.Join(pluginDir, md5FileName) - isMD5Match, err := md5.FileMatchesMD5(soFilePath, md5FilePath) - if err != nil { - return false, err - } - return isMD5Match, nil -} - -func GetPluginDir() (string, error) { - pluginDir := viper.GetString("plugin-dir") - if pluginDir == "" { - return "", fmt.Errorf(`plugins directory is ""`) - } - log.Debugf("Plugin directory: %s.", pluginDir) - - realPluginDir, err := handlePathWithHome(pluginDir) - if err != nil { - return "", err - } - log.Debugf("Real plugin directory: %s.", realPluginDir) - - return realPluginDir, nil -} - -// handlePathWithHome deal with "~" in the filePath -func handlePathWithHome(filePath string) (string, error) { - if !strings.Contains(filePath, "~") { - return filePath, nil - } - - homeDir, err := os.UserHomeDir() - if err != nil { - return "", err - } - retPath := filepath.Join(homeDir, strings.TrimPrefix(filePath, "~")) - log.Debugf("real path: %s.", retPath) - - return retPath, nil -} diff --git a/internal/pkg/pluginmanager/manager_test.go b/internal/pkg/pluginmanager/manager_test.go deleted file mode 100644 index 6505a076d..000000000 --- a/internal/pkg/pluginmanager/manager_test.go +++ /dev/null @@ -1,224 +0,0 @@ -package pluginmanager - -import ( - "fmt" - "net/http" - "os" - "path/filepath" - "runtime" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "github.com/onsi/gomega/ghttp" - "github.com/spf13/viper" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/pkg/util/md5" -) - -var _ = Describe("downloadPlugins func", func() { - - var ( - server *mockPluginServer - tools configmanager.Tools - tempDir string - ) - - const version = "0.1.0" - - BeforeEach(func() { - tempDir = GinkgoT().TempDir() - server = newMockPluginServer() - }) - - When("plugins are right", func() { - const ( - argocd = "argocd" - jenkins = "jenkins" - ) - BeforeEach(func() { - tools = configmanager.Tools{ - {Name: argocd}, - {Name: jenkins}, - } - server.registerPluginOK(argocd, argocd+"content", version, runtime.GOOS, runtime.GOARCH) - server.registerPluginOK(jenkins, jenkins+"content", version, runtime.GOOS, runtime.GOARCH) - }) - - It("should download plugins successfully", func() { - err := downloadPlugins(server.URL(), tools, tempDir, runtime.GOOS, runtime.GOARCH, version) - Expect(err).ShouldNot(HaveOccurred()) - viper.Set("plugin-dir", tempDir) - err = CheckLocalPlugins(tools) - Expect(err).ShouldNot(HaveOccurred()) - }) - }) - - When("pluginDir is Empty", func() { - It("should return err", func() { - err := downloadPlugins(server.URL(), tools, "", runtime.GOOS, runtime.GOARCH, version) - Expect(err).Error().Should(HaveOccurred()) - Expect(err.Error()).Should(ContainSubstring("plugins directory should not be ")) - }) - }) - - When("plugin is not exist", func() { - BeforeEach(func() { - const invalidPlugin = "invalidPlugin" - tools = configmanager.Tools{ - {Name: invalidPlugin}, - } - server.registerPluginNotFound(invalidPlugin, version, runtime.GOOS, runtime.GOARCH) - }) - It("should return err", func() { - - err := downloadPlugins(server.URL(), tools, tempDir, runtime.GOOS, runtime.GOARCH, version) - Expect(err).Should(HaveOccurred()) - }) - }) -}) - -var _ = Describe("MD5", func() { - var ( - err error - config *configmanager.Config - tempDir, file, fileMD5, filePath, fileMD5Path string - tools configmanager.Tools - ) - - createNewFile := func(fileName string) error { - f1, err := os.Create(fileName) - if err != nil { - return err - } - defer f1.Close() - return nil - } - - addMD5File := func(fileName, md5FileName string) error { - md5, err := md5.CalcFileMD5(fileName) - if err != nil { - return err - } - md5File, err := os.Create(md5FileName) - if err != nil { - return err - } - _, err = md5File.Write([]byte(md5)) - if err != nil { - return err - } - return nil - } - - BeforeEach(func() { - tempDir = GinkgoT().TempDir() - viper.Set("plugin-dir", tempDir) - tools = configmanager.Tools{ - {InstanceID: "a", Name: "a"}, - } - config = &configmanager.Config{Tools: tools} - - file = tools[0].GetPluginFileName() - filePath = filepath.Join(tempDir, file) - fileMD5 = tools[0].GetPluginMD5FileName() - fileMD5Path = filepath.Join(tempDir, fileMD5) - err := createNewFile(filePath) - Expect(err).NotTo(HaveOccurred()) - - err = addMD5File(filePath, fileMD5Path) - Expect(err).NotTo(HaveOccurred()) - - }) - - Describe("CheckLocalPlugins func", func() { - It("should match .md5 file content", func() { - err = CheckLocalPlugins(config.Tools) - Expect(err).NotTo(HaveOccurred()) - }) - - It("should mismatch .md5 file content", func() { - err = createNewFile(fileMD5Path) - Expect(err).NotTo(HaveOccurred()) - err = CheckLocalPlugins(config.Tools) - expectErrMsg := fmt.Sprintf("plugin %s doesn't match with .md5", tools[0].InstanceID) - Expect(err).Error().Should(HaveOccurred()) - Expect(err.Error()).To(Equal(expectErrMsg)) - }) - }) - - Describe("ifPluginAndMD5Match func", func() { - It("should match .md5 file content", func() { - matched, err := ifPluginAndMD5Match(tempDir, file, fileMD5) - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(matched).To(BeTrue()) - }) - - It("should mismatch .md5 file content", func() { - err = createNewFile(fileMD5Path) - Expect(err).NotTo(HaveOccurred()) - matched, err := ifPluginAndMD5Match(tempDir, file, fileMD5) - Expect(err).ToNot(HaveOccurred()) - Expect(matched).To(BeFalse()) - }) - }) - - Describe("reDownload func", func() { - var ( - pbDownloadClient *PluginDownloadClient - pluginVersion string - ) - - BeforeEach(func() { - pbDownloadClient = NewPluginDownloadClient("not_exist_url") - }) - - When("pluginFile not exist", func() { - It("should return error", func() { - notExistPluginFile := "not_exist_plugin" - err = pbDownloadClient.reDownload(tempDir, notExistPluginFile, fileMD5, pluginVersion) - Expect(err).Error().Should(HaveOccurred()) - err = pbDownloadClient.reDownload(tempDir, file, notExistPluginFile, pluginVersion) - Expect(err).Error().Should(HaveOccurred()) - }) - }) - - When("old plugin exist", func() { - var ( - server *ghttp.Server - rspContent string - ) - - BeforeEach(func() { - pluginVersion = "1.0" - reqPath := fmt.Sprintf("/v%s/%s", pluginVersion, file) - md5Path := fmt.Sprintf("/v%s/%s", pluginVersion, fileMD5) - server = ghttp.NewServer() - pbDownloadClient = NewPluginDownloadClient(server.URL()) - rspContent = "reDownload Content" - server.AppendHandlers( - ghttp.CombineHandlers( - ghttp.VerifyRequest("GET", reqPath), - ghttp.RespondWith(http.StatusOK, rspContent), - ), - ghttp.CombineHandlers( - ghttp.VerifyRequest("GET", md5Path), - ghttp.RespondWith(http.StatusOK, rspContent), - ), - ) - }) - - It("should re download success if download success", func() { - err = pbDownloadClient.reDownload(tempDir, file, fileMD5, pluginVersion) - Expect(err).Error().ShouldNot(HaveOccurred()) - newFileContent, err := os.ReadFile(filePath) - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(string(newFileContent)).Should(Equal(rspContent)) - }) - - AfterEach(func() { - server.Close() - }) - }) - }) -}) diff --git a/internal/pkg/pluginmanager/pluginServer_mock.go b/internal/pkg/pluginmanager/pluginServer_mock.go deleted file mode 100644 index 1147d7f0b..000000000 --- a/internal/pkg/pluginmanager/pluginServer_mock.go +++ /dev/null @@ -1,58 +0,0 @@ -package pluginmanager - -import ( - "fmt" - "net/http" - "strings" - - "github.com/onsi/gomega/ghttp" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/version" - "github.com/devstream-io/devstream/pkg/util/md5" -) - -type mockPluginServer struct { - *ghttp.Server -} - -func newMockPluginServer() *mockPluginServer { - return &mockPluginServer{Server: ghttp.NewServer()} -} - -// registerPluginOK uploads a plugin with the given name and content to the server. -func (s *mockPluginServer) registerPluginOK(plugin, content, wantVersion, os, arch string) { - version.Version = wantVersion // re-wire version, because many inner functions use version.Version - tool := configmanager.Tool{Name: plugin} - pluginFileName := tool.GetPluginFileNameWithOSAndArch(os, arch) - pluginMD5FileName := tool.GetPluginMD5FileNameWithOSAndArch(os, arch) - md5Content, _ := md5.CalcMD5(strings.NewReader(content)) - s.AppendHandlers( - ghttp.CombineHandlers( - ghttp.VerifyRequest("GET", fmt.Sprintf("/v%s/%s", wantVersion, pluginFileName)), - ghttp.RespondWith(http.StatusOK, content), - ), - ghttp.CombineHandlers( - ghttp.VerifyRequest("GET", fmt.Sprintf("/v%s/%s", wantVersion, pluginMD5FileName)), - ghttp.RespondWith(http.StatusOK, md5Content), - ), - ) -} - -// registerPluginNotFound sets up the server to return a 404 for the given plugin. -func (s *mockPluginServer) registerPluginNotFound(plugin, wantVersion, os, arch string) { - version.Version = wantVersion // re-wire version, because many inner functions use version.Version - tool := configmanager.Tool{Name: plugin} - pluginFileName := tool.GetPluginFileNameWithOSAndArch(os, arch) - pluginMD5FileName := tool.GetPluginMD5FileNameWithOSAndArch(os, arch) - s.AppendHandlers( - ghttp.CombineHandlers( - ghttp.VerifyRequest("GET", fmt.Sprintf("/v%s/%s", wantVersion, pluginFileName)), - ghttp.RespondWith(http.StatusNotFound, ""), - ), - ghttp.CombineHandlers( - ghttp.VerifyRequest("GET", fmt.Sprintf("/v%s/%s", wantVersion, pluginMD5FileName)), - ghttp.RespondWith(http.StatusNotFound, ""), - ), - ) -} diff --git a/internal/pkg/pluginmanager/pluginmd5_suit_test.go b/internal/pkg/pluginmanager/pluginmd5_suit_test.go deleted file mode 100644 index e84695e69..000000000 --- a/internal/pkg/pluginmanager/pluginmd5_suit_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package pluginmanager - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestPluginmanager(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Util Pluginmanager Suite") -} diff --git a/internal/pkg/scaffold/scaffold.go b/internal/pkg/scaffold/scaffold.go new file mode 100644 index 000000000..23eab6085 --- /dev/null +++ b/internal/pkg/scaffold/scaffold.go @@ -0,0 +1,91 @@ +package scaffold + +func Scaffold(tree string) error { + return nil +} + +// parseTreeToList parses the directory tree represented by a string and +// returns a list of directories and files. +// for example input:" +// testdir/ +// ├── dir1/ +// │ ├── file1.go +// │ └── dir2/ +// │ ├── file1.go +// │ └── file2.go +// └── FILE3.md +// " +// output: +// +// []string{ +// "testdir/", +// "testdir/dir1/", +// "testdir/dir1/file1.go", +// "testdir/dir1/dir2/", +// "testdir/dir1/dir2/file1.go", +// "testdir/dir1/dir2/file2.go", +// } +//func parseTreeToList(tree string) ([]string, error) { +// tree = strings.ReplaceAll(tree, "\r\n", "\n") +// tree = strings.ReplaceAll(tree, "\r", "\n") +// tree = strings.ReplaceAll(tree, "\t", "") +// tree = strings.ReplaceAll(tree, "\n\n", "\n") +// tree = strings.ReplaceAll(tree, "─", "-") +// tree = strings.ReplaceAll(tree, "│", "|") +// +// lines := strings.Split(tree, "\n") +// var result []string +// +// for _, line := range lines { +// line = strings.TrimSpace(line) +// +// if len(line) == 0 { +// continue +// } +// +// level := 0 +// for ; strings.HasPrefix(line, " "); level++ { +// line = line[2:] +// } +// +// prefix := strings.Repeat(" ", level) +// +// if strings.HasSuffix(line, "/") { +// result = append(result, prefix+line) +// } else { +// result = append(result, prefix+line) +// } +// +// } +// +// return result, nil +//} +// +//func ParseTree(tree string) ([]string, error) { +// var result []string +// +// lines := strings.Split(tree, "\n") +// for _, line := range lines { +// if strings.TrimSpace(line) == "" { +// continue +// } +// +// parts := strings.Split(line, " ") +// level := strings.Count(parts[0], "│") + strings.Count(parts[0], "└") + strings.Count(parts[0], "├") +// name := strings.TrimSpace(parts[len(parts)-1]) +// +// // Construct the full path by combining the names of all parent directories +// path := "" +// for i := 1; i < level; i++ { +// if len(result) < i { +// return nil, fmt.Errorf("parent directory not found: %s", line) +// } +// path += result[i-1] + "/" +// } +// path += name +// +// result = append(result, path) +// } +// +// return result, nil +//} diff --git a/internal/pkg/scaffold/scaffold_test.go b/internal/pkg/scaffold/scaffold_test.go new file mode 100644 index 000000000..f14c60b11 --- /dev/null +++ b/internal/pkg/scaffold/scaffold_test.go @@ -0,0 +1 @@ +package scaffold_test diff --git a/internal/pkg/scaffold/tree/parse.go b/internal/pkg/scaffold/tree/parse.go new file mode 100644 index 000000000..fae5de8e3 --- /dev/null +++ b/internal/pkg/scaffold/tree/parse.go @@ -0,0 +1,41 @@ +package tree + +import "strings" + +// testdir/ +// ├── dir1/ +// │ ├── file1.go +// │ └── dir2/ +// │ ├── file1.go +// │ └── file2.go +// └── FILE3.md +func ParseTree(treeText string) *TreeNode { + lines := strings.Split(treeText, "\n") + rootLine := strings.TrimSpace(lines[0]) + rootName := strings.TrimSuffix(rootLine, "/") + root := NewTreeNode(rootName, true) + stack := []*TreeNode{root} + + for _, line := range lines[1:] { + // dir1 as an example, the indent is 3+2=5 in "├── dir1/". + indent := strings.LastIndex(line, "──") + 2 + // line[indent:] apply to dir1/, the result is " dir1/" + // name is "dir1/" + name := strings.TrimSpace(line[indent:]) + isDir := strings.HasSuffix(name, "/") + if isDir { + name = strings.TrimSuffix(name, "/") + } + + node := NewTreeNode(name, isDir) + parent := stack[indent/4] + parent.AddChild(node) + + if isDir { + stack = append(stack, node) + } else { + stack = append(stack[:indent/4+1], node) + } + } + return root +} diff --git a/internal/pkg/scaffold/tree/tree.go b/internal/pkg/scaffold/tree/tree.go new file mode 100644 index 000000000..5c23662e4 --- /dev/null +++ b/internal/pkg/scaffold/tree/tree.go @@ -0,0 +1,35 @@ +package tree + +import "fmt" + +/** + * feat: some description here + */ +type TreeNode struct { + Name string + IsDir bool + Children []*TreeNode +} + +func NewTreeNode(name string, isDir bool) *TreeNode { + return &TreeNode{ + Name: name, + IsDir: isDir, + Children: []*TreeNode{}, + } +} + +func (t *TreeNode) AddChild(child *TreeNode) { + t.Children = append(t.Children, child) +} + +func (t *TreeNode) PrintTree(prefix string) { + if t.IsDir { + fmt.Println(prefix + t.Name + "/") + } else { + fmt.Println(prefix + t.Name) + } + for _, child := range t.Children { + child.PrintTree(prefix + " ") + } +} diff --git a/internal/pkg/show/config/config.go b/internal/pkg/show/config/config.go deleted file mode 100644 index 128d3fd44..000000000 --- a/internal/pkg/show/config/config.go +++ /dev/null @@ -1,37 +0,0 @@ -package config - -import ( - "fmt" - - "github.com/spf13/viper" -) - -var templates = map[string]string{ - "quickstart": QuickStart, - "gitops": GitOps, - "apps": Apps, -} - -//go:generate go run gen_embed_var.go -func Show() error { - // at first, check is template arg is set - template := viper.GetString("template") - if template != "" { - if tmpl, ok := templates[template]; ok { - fmt.Println(tmpl) - return nil - } - return fmt.Errorf("illegal template name : < %s >", template) - } - - plugin := viper.GetString("plugin") - if plugin == "" { - fmt.Println(DefaultConfig) - return nil - } - if config, ok := pluginDefaultConfigs[plugin]; ok { - fmt.Println(config) - return nil - } - return fmt.Errorf("illegal plugin name : < %s >", plugin) -} diff --git a/internal/pkg/show/config/default.yaml b/internal/pkg/show/config/default.yaml deleted file mode 100644 index e0abfed83..000000000 --- a/internal/pkg/show/config/default.yaml +++ /dev/null @@ -1,24 +0,0 @@ -config: - state: - backend: local # backend can be local or s3 - options: - stateFile: devstream.state - -vars: - githubUsername: daniel-hutao - repo: go-webapp-demo - -tools: -- name: repo-scaffolding - instanceID: default - options: - destinationRepo: - owner: [[ githubUsername ]] - org: "" - name: [[ repo ]] - branch: main - scmType: github - sourceRepo: - org: devstream-io - name: dtm-scaffolding-golang - scmType: github diff --git a/internal/pkg/show/config/embed_gen.go b/internal/pkg/show/config/embed_gen.go deleted file mode 100644 index c4dffdd40..000000000 --- a/internal/pkg/show/config/embed_gen.go +++ /dev/null @@ -1,75 +0,0 @@ -// Code generated by gen_embed_var.go; DO NOT EDIT. -package config - -import _ "embed" - -//go:embed default.yaml -var DefaultConfig string - -// plugin default config -var ( - - //go:embed plugins/argocdapp.yaml - ArgocdappDefaultConfig string - - //go:embed plugins/ci-generic.yaml - CiGenericDefaultConfig string - - //go:embed plugins/devlake-config.yaml - DevlakeConfigDefaultConfig string - - //go:embed plugins/github-actions.yaml - GithubActionsDefaultConfig string - - //go:embed plugins/gitlab-ce-docker.yaml - GitlabCeDockerDefaultConfig string - - //go:embed plugins/gitlab-ci.yaml - GitlabCiDefaultConfig string - - //go:embed plugins/harbor-docker.yaml - HarborDockerDefaultConfig string - - //go:embed plugins/helm-installer.yaml - HelmInstallerDefaultConfig string - - //go:embed plugins/jenkins-pipeline.yaml - JenkinsPipelineDefaultConfig string - - //go:embed plugins/jira.yaml - JiraDefaultConfig string - - //go:embed plugins/repo-scaffolding.yaml - RepoScaffoldingDefaultConfig string - - //go:embed plugins/trello.yaml - TrelloDefaultConfig string - - //go:embed plugins/zentao.yaml - ZentaoDefaultConfig string -) - -var pluginDefaultConfigs = map[string]string{ - "argocdapp": ArgocdappDefaultConfig, - "ci-generic": CiGenericDefaultConfig, - "devlake-config": DevlakeConfigDefaultConfig, - "github-actions": GithubActionsDefaultConfig, - "gitlab-ce-docker": GitlabCeDockerDefaultConfig, - "gitlab-ci": GitlabCiDefaultConfig, - "harbor-docker": HarborDockerDefaultConfig, - "helm-installer": HelmInstallerDefaultConfig, - "jenkins-pipeline": JenkinsPipelineDefaultConfig, - "jira": JiraDefaultConfig, - "repo-scaffolding": RepoScaffoldingDefaultConfig, - "trello": TrelloDefaultConfig, - "zentao": ZentaoDefaultConfig, -} - -//go:embed templates/quickstart.yaml -var QuickStart string - -//go:embed templates/gitops.yaml -var GitOps string - -//go:embed templates/apps.yaml -var Apps string diff --git a/internal/pkg/show/config/gen_embed.tpl b/internal/pkg/show/config/gen_embed.tpl deleted file mode 100644 index 81756b654..000000000 --- a/internal/pkg/show/config/gen_embed.tpl +++ /dev/null @@ -1,30 +0,0 @@ -// Code generated by gen_embed_var.go; DO NOT EDIT. -package [[.Package]] - -import _ "embed" - -//go:embed default.yaml -var DefaultConfig string - -// plugin default config -var ( -[[range .Plugins]] -//go:embed [[$.Dir]]/[[.]].yaml -[[UpperCamelCase .]]DefaultConfig string -[[end]] -) - -var pluginDefaultConfigs = map[string]string{ - [[- range .Plugins]] - "[[.]]":[[UpperCamelCase . -]]DefaultConfig, -[[- end]] -} - -//go:embed templates/quickstart.yaml -var QuickStart string - -//go:embed templates/gitops.yaml -var GitOps string - -//go:embed templates/apps.yaml -var Apps string diff --git a/internal/pkg/show/config/gen_embed_var.go b/internal/pkg/show/config/gen_embed_var.go deleted file mode 100644 index 20cb9f4bd..000000000 --- a/internal/pkg/show/config/gen_embed_var.go +++ /dev/null @@ -1,128 +0,0 @@ -//go:build ignore -// +build ignore - -// This program is run via "go generate" to generate the code. - -package main - -import ( - _ "embed" - "flag" - "go/format" - "log" - "os" - "path/filepath" - "strings" - "text/template" - - "github.com/devstream-io/devstream/pkg/util/file" - templateUtil "github.com/devstream-io/devstream/pkg/util/template" - - "golang.org/x/text/cases" - "golang.org/x/text/language" -) - -type Option struct { - Plugins []string - Dir string - Path string - Package string - Funcs template.FuncMap -} - -const ( - templatesSrc = "../../../../examples" - templatesDst = "templates" -) - -func main() { - srcDir := flag.String("dir", "plugins", "source directory for yaml files") - packageName := flag.String("pkg", "config", "package name") - flag.Parse() - - if err := copyTemplates(templatesSrc, templatesDst); err != nil { - log.Fatal(err) - } - - plugins, err := getYamlFiles(*srcDir) - if err != nil { - log.Fatal(err) - } - - generate(&Option{ - Plugins: plugins, - Dir: *srcDir, - Path: "embed_gen.go", - Package: *packageName, - Funcs: template.FuncMap{ - "UpperCamelCase": UpperCamelCase, - }, - }) -} - -//go:embed gen_embed.tpl -var templateCode string - -func copyTemplates(src, dst string) error { - return filepath.WalkDir(src, func(path string, d os.DirEntry, err error) error { - if err != nil { - return err - } - if d.IsDir() || !strings.HasSuffix(path, ".yaml") { - return nil - } - return file.CopyFile(path, filepath.Join(dst, filepath.Base(path))) - }) -} - -// generate generates the code for Option `o` into a file named by `o.Path`. -func generate(o *Option) { - content, err := templateUtil.NewRenderClient(&templateUtil.TemplateOption{ - Name: "gen_embed", - FuncMap: o.Funcs, - }, templateUtil.ContentGetter).Render(templateCode, o) - - if err != nil { - log.Fatal("template Execute error:", err) - } - - formatted, err := format.Source([]byte(content)) - if err != nil { - log.Fatal("format:", err) - log.Fatal("template Execute error:", err) - } - - if err := os.WriteFile(o.Path, formatted, 0644); err != nil { - log.Fatal("writeFile:", err) - } - -} - -// getYamlFiles returns a list of YAML files' names in the given directory. -func getYamlFiles(dir string) ([]string, error) { - - files, err := os.ReadDir(dir) - if err != nil { - return nil, err - } - - var fileNames []string - for _, file := range files { - if file.IsDir() { - continue - } - if !strings.HasSuffix(file.Name(), ".yaml") { - continue - } - fileNames = append(fileNames, strings.TrimSuffix(file.Name(), ".yaml")) - } - - return fileNames, nil -} - -// UpperCamelCase returns a string with the first letter in upper case. -func UpperCamelCase(s string) string { - s = strings.Replace(s, "-", " ", -1) - s = cases.Title(language.English).String(s) - return strings.Replace(s, " ", "", -1) -} diff --git a/internal/pkg/show/config/plugins/argocdapp.yaml b/internal/pkg/show/config/plugins/argocdapp.yaml deleted file mode 100644 index 273aa9e96..000000000 --- a/internal/pkg/show/config/plugins/argocdapp.yaml +++ /dev/null @@ -1,38 +0,0 @@ -tools: -# name of the tool -- name: argocdapp - # id of the tool instance - instanceID: default - # format: name.instanceID; If specified, dtm will make sure the dependency is applied first before handling this tool. - dependsOn: [ "argocd.ARGOCD_INSTANCE_NAME" ] - # options for the plugin - options: - # information on the ArgoCD Application - app: - # name of the ArgoCD Application - name: hello - # where the ArgoCD Application custom resource will be created - namespace: argocd - # destination of the ArgoCD Application - destination: - # on which server to deploy - server: https://kubernetes.default.svc - # in which namespace to deploy - namespace: default - # source of the application - source: - # which values file to use in the Helm chart - valuefile: values.yaml - # path of the Helm chart - path: charts/go-hello-http - # Helm chart repo URL, this is only an example, do not use this - repoURL: YOUR_CHART_REPO_URL - # Helm chart repo branch - repoBranch: YOUR_CHART_REPO_BRANCH - # if repo doesn't contain path, use imageRepo to create a helm config - imageRepo: - # imageRepo address - url: IMAGE_REPO_ADDRESS - user: IMAGE_REPO_OWNER - # config inital image tag - initalTag: IMAGE_REPO_TAG diff --git a/internal/pkg/show/config/plugins/ci-generic.yaml b/internal/pkg/show/config/plugins/ci-generic.yaml deleted file mode 100644 index 778e7ebcb..000000000 --- a/internal/pkg/show/config/plugins/ci-generic.yaml +++ /dev/null @@ -1,30 +0,0 @@ -tools: -# name of the tool -- name: ci-generic - # id of the tool instance - instanceID: default - # format: name.instanceID; If specified, dtm will make sure the dependency is applied first before handling this tool. - dependsOn: [ ] - # options for the plugin - options: - ci: - # If your ci file is local or remote, you can set the this field to get ci file - configLocation: JENKINSFILE_LOCATION - # If you want to config ci in devstream, you can config configContents directly - configContents: - Jenkinsfile: JENKINSFILE_CONTENT - # support jenkins-pipeline/gitlab-ci/github-actions for now - type: jenkins-pipeline - projectRepo: - # scm common field - branch: YOUR_REPO_BRANCH - token: YOUR_REPO_SCM_TOKEN - # you can directly use the url of repo - url: YOUR_REPO_URL - # or you can config detailed fields for this repo - owner: YOUR_REPO_OWNER - org: YOUR_REPO_ORG - name: YOUR_REPO_NAME - scmType: github - # you can config this field if you are using self-host gitlab - baseURL: YOUR_SELF_HOST_GITLAB_URL diff --git a/internal/pkg/show/config/plugins/devlake-config.yaml b/internal/pkg/show/config/plugins/devlake-config.yaml deleted file mode 100644 index c5ea90e8e..000000000 --- a/internal/pkg/show/config/plugins/devlake-config.yaml +++ /dev/null @@ -1,28 +0,0 @@ -tools: -# name of the tool -- name: devlake-config - # id of the tool instance - instanceID: default - # format: name.instanceID; If specified, dtm will make sure the dependency is applied first before handling this tool. - dependsOn: [ ] - # options for the plugin - options: - devlakeAddr: http://127.0.0.1:1234 - plugins: - - name: github - connections: - - name: "default" - endpoint: "https://github.com/changeme/changeme" - proxy: "" - rateLimitPerHour: 0 - auth: - token: "xxx" - - name: jira - connections: - - name: "default" - endpoint: "https://changeme.atlassian.net" - proxy: "" - rateLimitPerHour: 0 - auth: - username: "changeme" - password: "changeme" diff --git a/internal/pkg/show/config/plugins/github-actions.yaml b/internal/pkg/show/config/plugins/github-actions.yaml deleted file mode 100644 index ed5074963..000000000 --- a/internal/pkg/show/config/plugins/github-actions.yaml +++ /dev/null @@ -1,51 +0,0 @@ -tools: -# name of the tool -- name: github-actions - # id of the tool instance - instanceID: default - # format: name.instanceID; If specified, dtm will make sure the dependency is applied first before handling this tool. - dependsOn: [ ] - # options for the plugin - options: - scm: - # scm common field - branch: YOUR_REPO_BRANCH - token: YOUR_REPO_SCM_TOKEN - # you can directly use the url of repo (git@github.com/root/test-exmaple.git for example) - url: YOUR_REPO_URL - # or you can config detailed fields for this repo - owner: YOUR_REPO_OWNER - org: YOUR_REPO_ORG - name: YOUR_REPO_NAME - scmType: github - pipeline: - # configLocation is the location of workflows, it can be remote or local address - # if you don't config this field, devstream will use https://raw.githubusercontent.com/devstream-io/dtm-pipeline-templates/main/github-actions/workflows/main.yml - configLocation: https://raw.githubusercontent.com/devstream-io/devstream/main/staging/dtm-github-action-example/general - # language config is required - language: - framework: # support gin/flask/spring for now - name: LANGUAGE # support go/java/nodejs/python for now - imageRepo: - # image repo URL for pulling/pushing - url: http://harbor.example.com:80 - # image repo user name - user: admin - # image repo password - password: YOUR_IMAGE_REPO_PASSWORD - dingTalk: - # dingtalk robot name - name: YOUR_DINGTALK_ROBOT_NAME - # dingtalk webhook - webhook: https://oapi.dingtalk.com/robot/send?access_token=changemeByConfig - # dingtalk securityType, we support "SECRET" and "KEY" - securityType: YOUR_DINGTALK_SECRET_TYPE - # dingtalk securityValue - securityValue: YOUR_DINGTALK_SECRET_VALUE - sonarqube: - # sonarqube address - url: http://sonar.example.com - # sonarqube token - token: YOUR_SONAR_TOKEN - # sonarqube name in jenkins - name: sonar_test diff --git a/internal/pkg/show/config/plugins/gitlab-ce-docker.yaml b/internal/pkg/show/config/plugins/gitlab-ce-docker.yaml deleted file mode 100644 index ce3935fb4..000000000 --- a/internal/pkg/show/config/plugins/gitlab-ce-docker.yaml +++ /dev/null @@ -1,28 +0,0 @@ -tools: -# name of the tool -- name: gitlab-ce-docker - # id of the tool instance - instanceID: default - # format: name.instanceID; If specified, dtm will make sure the dependency is applied first before handling this tool. - dependsOn: [ ] - # options for the plugin - options: - # hostname for running docker. (default: gitlab.example.com) - hostname: gitlab.example.com - # pointing to the directory where the configuration, logs, and data files will reside. - # (default: /srv/gitlab) - # 1. it should be a absolute path - # 2. once the tool is installed, it can't be changed - gitlabHome: /srv/gitlab - # ssh port exposed in the host machine. (default: 22) - sshPort: 22 - # http port exposed in the host machine. (default: 80) - httpPort: 80 - # https port exposed in the host machine. - # (default: 443) - # todo: support https, reference: https://docs.gitlab.com/omnibus/settings/nginx.html#enable-https - httpsPort: 443 - # whether to delete the gitlabHome directory when the tool is removed. (default: false) - rmDataAfterDelete: false - # gitlab-ce tag. (default: "rc") - imageTag: "rc" diff --git a/internal/pkg/show/config/plugins/gitlab-ci.yaml b/internal/pkg/show/config/plugins/gitlab-ci.yaml deleted file mode 100644 index f80297784..000000000 --- a/internal/pkg/show/config/plugins/gitlab-ci.yaml +++ /dev/null @@ -1,42 +0,0 @@ -tools: -# name of the tool -- name: gitlab-ci - # id of the tool instance - instanceID: default - # format: name.instanceID; If specified, dtm will make sure the dependency is applied first before handling this tool. - dependsOn: [ ] - # options for the plugin - options: - scm: - # scm common field - branch: YOUR_REPO_BRANCH - token: YOUR_REPO_SCM_TOKEN - # you can directly use the url of repo (git@gitlab.com/root/test-exmaple.git for example) - url: YOUR_REPO_URL - # or you can config detailed fields for this repo - owner: YOUR_REPO_OWNER - org: YOUR_REPO_ORG - name: YOUR_REPO_NAME - scmType: gitlab - # you can config this field if you are using self-host gitlab - baseURL: YOUR_SELF_HOST_GITLAB_URL - pipeline: - # configLocation is the location of gitlab ci file, it can be remote or local address, - # if you don't config this field, devstream will use https://raw.githubusercontent.com/devstream-io/dtm-pipeline-templates/main/gitlab-ci/.gitlab-ci.yml - configLocation: .gitlabci.yml - # language config is required - language: - framework: # support gin/flask/spring for now - name: LANGUAGE # support go/java/nodejs/python for now - imageRepo: - # image repo URL for pulling/pushing - url: http://harbor.example.com:80 - # image repo user name - user: admin - # image repo password - password: YOUR_IMAGE_REPO_PASSWORD - # whether create gitlab runner for this project - # if runner.enable is true, devstream will use helm to install the project's runner - # and diable shared runner of this project - runner: - enable: false diff --git a/internal/pkg/show/config/plugins/harbor-docker.yaml b/internal/pkg/show/config/plugins/harbor-docker.yaml deleted file mode 100644 index 8d9c0af96..000000000 --- a/internal/pkg/show/config/plugins/harbor-docker.yaml +++ /dev/null @@ -1,10 +0,0 @@ -tools: -# name of the tool -- name: harbor-docker - # id of the tool instance - instanceID: default - # format: name.instanceID; If specified, dtm will make sure the dependency is applied first before handling this tool. - dependsOn: [ ] - # options for the plugin - options: - hostname: harbor.example.com diff --git a/internal/pkg/show/config/plugins/helm-installer.yaml b/internal/pkg/show/config/plugins/helm-installer.yaml deleted file mode 100644 index 492b42fa4..000000000 --- a/internal/pkg/show/config/plugins/helm-installer.yaml +++ /dev/null @@ -1,5 +0,0 @@ -tools: -# name of the tool -- name: helm-installer - # id of the tool instance. e.g. jenkins-001, harbor-002 - instanceID: changeme-001 diff --git a/internal/pkg/show/config/plugins/jenkins-pipeline.yaml b/internal/pkg/show/config/plugins/jenkins-pipeline.yaml deleted file mode 100644 index df1714a5c..000000000 --- a/internal/pkg/show/config/plugins/jenkins-pipeline.yaml +++ /dev/null @@ -1,67 +0,0 @@ -tools: -# name of the tool -- name: jenkins-pipeline - # id of the tool instance - instanceID: default - # format: name.instanceID; If specified, dtm will make sure the dependency is applied first before handling this tool. - dependsOn: [ ] - # options for the plugin - options: - jenkins: - # url is used to config jenkins url - url: http://jenkins.example.com:8080 - # jenkins' user name - user: admin - # jenkins namespace in k8s cluster - namespace: jenkins - # restart jenkins if true for plugin install - enableRestart: false - # jenkins login password - password: JENKINS_PASSWORD - # if offline is true, jenkins-pipeline will not install jenkins plugins and share library - # it will use a local Jenkinsfile for jenkins-pipeline - offline: false - scm: - # scm common field - branch: YOUR_REPO_BRANCH - token: YOUR_REPO_SCM_TOKEN - # you can directly use the url of repo (git@github.com/root/test-exmaple.git for example) - url: YOUR_REPO_URL - # or you can config detailed fields for this repo - owner: YOUR_REPO_OWNER - org: YOUR_REPO_ORG - name: YOUR_REPO_NAME - scmType: github - pipeline: - # jobName is jenkins's job name; or ; e.g. jobs/test-job, test-job, jobs2/test-job - jobName: test-job - # configLocation is the location of Jenkinsfile, it can be remote or local address - # if you don't config this field, devstream will use https://raw.githubusercontent.com/devstream-io/dtm-pipeline-templates/main/jenkins-pipeline/general/Jenkinsfile - configLocation: https://raw.githubusercontent.com/devstream-io/devstream/main/staging/dtm-jenkins-pipeline-example/general/Jenkinsfile - # language config is required - language: - framework: # support gin/flask/spring for now - name: LANGUAGE # support go/java/nodejs/python for now - imageRepo: - # image repo URL for pulling/pushing - url: http://harbor.example.com:80 - # image repo user name - user: admin - # image repo password - password: YOUR_IMAGE_REPO_PASSWORD - dingTalk: - # dingtalk robot name - name: YOUR_DINGTALK_ROBOT_NAME - # dingtalk webhook - webhook: https://oapi.dingtalk.com/robot/send?access_token=changemeByConfig - # dingtalk securityType, we support "SECRET" and "KEY" - securityType: YOUR_DINGTALK_SECRET_TYPE - # dingtalk securityValue - securityValue: YOUR_DINGTALK_SECRET_VALUE - sonarqube: - # sonarqube address - url: http://sonar.example.com - # sonarqube token - token: YOUR_SONAR_TOKEN - # sonarqube name in jenkins - name: sonar_test diff --git a/internal/pkg/show/config/plugins/jira.yaml b/internal/pkg/show/config/plugins/jira.yaml deleted file mode 100644 index dbce3ad96..000000000 --- a/internal/pkg/show/config/plugins/jira.yaml +++ /dev/null @@ -1,29 +0,0 @@ -tools: -# name of the tool -- name: jira - # id of the tool instance - instanceID: default - # format: name.instanceID; If specified, dtm will make sure the dependency is applied first before handling this tool. - dependsOn: [ ] - # options for the plugin - options: - scm: - # scm common field - branch: YOUR_REPO_BRANCH - token: YOUR_REPO_SCM_TOKEN - # you can directly use the url of repo (git@github.com/root/test-exmaple.git for example) - url: YOUR_REPO_URL - # or you can config detailed fields for this repo - owner: YOUR_REPO_OWNER - org: YOUR_REPO_ORG - name: YOUR_REPO_NAME - scmType: github - jira: - # jira token - token: JIRA_TOKEN - # "base url: https://id.atlassian.net" - baseURL: https://JIRA_ID.atlassian.net - # "need real user email in cloud Jira" - userEmail: JIRA_USER_EMAIL - # "get it from project url, like 'HEAP' from https://merico.atlassian.net/jira/software/projects/HEAP/pages" - projectKey: JIRA_PROJECT_KEY diff --git a/internal/pkg/show/config/plugins/repo-scaffolding.yaml b/internal/pkg/show/config/plugins/repo-scaffolding.yaml deleted file mode 100644 index 56da7902d..000000000 --- a/internal/pkg/show/config/plugins/repo-scaffolding.yaml +++ /dev/null @@ -1,38 +0,0 @@ -tools: -# name of the tool -- name: repo-scaffolding - # id of the tool instance - instanceID: default - # format: name.instanceID; If specified, dtm will make sure the dependency is applied first before handling this tool. - dependsOn: [ ] - # options for the plugin - options: - # desinationRepo is the config for desination repo - destinationRepo: - # scm common field - branch: YOUR_REPO_BRANCH - token: YOUR_REPO_SCM_TOKEN - # you can directly use the url of repo (git@gitlab.com/root/test-exmaple.git for example) - url: YOUR_REPO_URL - # or you can config detailed fields for this repo - owner: YOUR_REPO_OWNER - org: YOUR_REPO_ORG - name: YOUR_REPO_NAME - scmType: gitlab - # you can config this field if you are using self-host gitlab - baseURL: YOUR_SELF_HOST_GITLAB_URL - # sourceRepo is the template repo location, support github only - sourceRepo: - # scm common field - branch: YOUR_REPO_BRANCH - token: YOUR_REPO_SCM_TOKEN - # you can directly use the url of repo (git@github.com/root/test-exmaple.git for example) - url: YOUR_REPO_URL - # or you can config detailed fields for this repo - owner: YOUR_REPO_OWNER - org: YOUR_REPO_ORG - name: YOUR_REPO_NAME - scmType: github - # this is used for template render - vars: - imageRepo: YOUR_DOCKERHUB_REPOSITORY diff --git a/internal/pkg/show/config/plugins/trello.yaml b/internal/pkg/show/config/plugins/trello.yaml deleted file mode 100644 index 55ef22e6f..000000000 --- a/internal/pkg/show/config/plugins/trello.yaml +++ /dev/null @@ -1,26 +0,0 @@ -tools: -# name of the tool -- name: trello - # id of the tool instance - instanceID: default - # format: name.instanceID; If specified, dtm will make sure the dependency is applied first before handling this tool - dependsOn: [ ] - # options for the plugin - options: - # the repo's owner - board: - # the Trello board name. If empty, use owner/name as the board's name. - name: KANBAN_BOARD_NAME - # the Trello board description. If empty, use devstream's default description - description: KANBAN_DESCRIPTION - # Trello apiKey and token, see https://docs.servicenow.com/bundle/quebec-it-asset-management/page/product/software-asset-management2/task/generate-trello-apikey-token.html for more information - apikey: [[ env TRELLO_API_KEY ]] # use environment variable "TRELLO_API_KEY" to set the value - token: [[ env TRELLO_TOKEN ]] # use environment variable "TRELLO_TOKEN" to set the value - scm: - # scm common field - branch: YOUR_REPO_BRANCH - token: [[ env GITHUB_TOKEN ]] # use environment variable "GITHUB_TOKEN" to set the value - # you can directly use the url of repo (git@github.com/root/test-exmaple.git for example) - url: YOUR_REPO_URL - # or you can config detailed fields for this repo - owner: YOUR_REPO_OWNER diff --git a/internal/pkg/show/config/plugins/zentao.yaml b/internal/pkg/show/config/plugins/zentao.yaml deleted file mode 100644 index 4d3115afb..000000000 --- a/internal/pkg/show/config/plugins/zentao.yaml +++ /dev/null @@ -1,51 +0,0 @@ -tools: -# name of the tool -- name: zentao - # id of the tool instance - instanceID: default - # format: name.instanceID; If specified, dtm will make sure the dependency is applied first before handling this tool - dependsOn: [] - # options for the plugin - options: - # namespace for zentao application - namespace: 'zentao' - # storageClassName used to match pv and pvc - storageClassName: 'zentao-storage' - # two PersistentVolumes for zentao and mysql should be specified - persistentVolume: - # name of zentao pv - zentaoPVName: 'zentao-pv' - # capacity of zentao pv - zentaoPVCapacity: '1G' - # name of mysql pv - mysqlPVName: 'mysql-pv' - # capacity of mysql pv - mysqlPVCapacity: '1G' - # two PersistentVolumeClaims for zentao and mysql should be specified - persistentVolumeClaim: - # name of zentao pvc - zentaoPVCName: 'zentao-pvc' - # capacity of zentao pvc - zentaoPVCCapacity: '1G' - # name of mysql pvc - mysqlPVCName: 'mysql-pv' - # capacity of mysql pvc - mysqlPVCCapacity: '1G' - # zentao application is deployed by K8S Deployment - deployment: - # name of zentao deployment - name: 'zentao-dp' - # number of application replica - replicas: 3 - # zentao image - image: 'easysoft/zentao:latest' - # initial password name for mysql database, you can specify any name you like - mysqlPasswdName: 'MYSQL_ROOT_PASSWORD' - # initial password value for mysql database, you can specify any value you like - mysqlPasswdValue: '1234567' - # zentao application is exposed via K8S Service - service: - # name of zentao service - name: 'zentao-svc' - # nodePort of zentao service, currently zentao plugin only support `nodePort` type - nodePort: 30081 diff --git a/internal/pkg/show/config/templates/apps.yaml b/internal/pkg/show/config/templates/apps.yaml deleted file mode 100644 index 8559f9c1c..000000000 --- a/internal/pkg/show/config/templates/apps.yaml +++ /dev/null @@ -1,49 +0,0 @@ -config: - state: - backend: local - options: - stateFile: devstream.state - -vars: - GITHUB_USER: YOUR_GITHUB_USER - DOCKERHUB_USER: YOUR_DOCKERHUB_USER - -tools: -- name: helm-installer - instanceID: argocd - -apps: -- name: myapp1 - spec: - language: python - framework: django - repo: - url: github.com/[[ GITHUB_USER ]]/myapp1 - token: [[ env GITHUB_TOKEN ]] - repoTemplate: - url: github.com/devstream-io/dtm-repo-scaffolding-python-flask - ci: - - type: github-actions - options: - imageRepo: - user: [[ DOCKERHUB_USER ]] - password: [[ env IMAGE_REPO_PASSWORD ]] - cd: - - type: argocdapp -- name: myapp2 - spec: - language: golang - framework: gin - repo: - url: github.com/[[ GITHUB_USER ]]/myapp2 - token: [[ env GITHUB_TOKEN ]] - repoTemplate: - url: github.com/devstream-io/dtm-repo-scaffolding-golang-gin - ci: - - type: github-actions - options: - imageRepo: - user: [[ DOCKERHUB_USER ]] - password: [[ env IMAGE_REPO_PASSWORD ]] - cd: - - type: argocdapp diff --git a/internal/pkg/show/config/templates/gitops.yaml b/internal/pkg/show/config/templates/gitops.yaml deleted file mode 100644 index 5a06ee774..000000000 --- a/internal/pkg/show/config/templates/gitops.yaml +++ /dev/null @@ -1,60 +0,0 @@ -config: - state: - backend: local - options: - stateFile: devstream.state -vars: - githubUser: GITHUB_USER - dockerUser: DOCKERHUB_USER - app: helloworld - -tools: -- name: repo-scaffolding - instanceID: myapp - options: - destinationRepo: - owner: [[ githubUser ]] - name: [[ app ]] - branch: main - scmType: github - token: [[ env GITHUB_TOKEN ]] - sourceRepo: - org: devstream-io - name: dtm-repo-scaffolding-python-flask - scmType: github -- name: github-actions - instanceID: flask - dependsOn: [ repo-scaffolding.myapp ] - options: - scm: - owner: [[ githubUser ]] - name: [[ app ]] - scmType: github - token: [[ env GITHUB_TOKEN ]] - pipeline: - configLocation: https://raw.githubusercontent.com/devstream-io/dtm-pipeline-templates/main/github-actions/workflows/main.yml - language: - name: python - framework: flask - imageRepo: - user: [[ dockerUser ]] - password: [[ env IMAGE_REPO_PASSWORD ]] -- name: helm-installer - instanceID: argocd -- name: argocdapp - instanceID: default - dependsOn: [ "helm-installer.argocd", "github-actions.flask" ] - options: - app: - name: [[ app ]] - namespace: argocd - destination: - server: https://kubernetes.default.svc - namespace: default - source: - valuefile: values.yaml - path: helm/[[ app ]] - repoURL: ${{repo-scaffolding.myapp.outputs.repoURL}} - token: [[ env GITHUB_TOKEN ]] - imageRepo: - user: [[ dockerUser ]] diff --git a/internal/pkg/show/config/templates/quickstart.yaml b/internal/pkg/show/config/templates/quickstart.yaml deleted file mode 100644 index d09435166..000000000 --- a/internal/pkg/show/config/templates/quickstart.yaml +++ /dev/null @@ -1,42 +0,0 @@ -config: - state: # state config, backend can be local, s3 or k8s - backend: local - options: - stateFile: devstream.state - -vars: - RepoOwner: YOUR_GITHUB_USERNAME_CASE_SENSITIVE - ImageRepoUser: YOUR_DOCKER_USERNAME - -tools: -- name: repo-scaffolding - instanceID: golang-github - options: - destinationRepo: - owner: [[ RepoOwner ]] - name: go-webapp-devstream-demo - branch: main - scmType: github - token: [[ env GITHUB_TOKEN ]] - sourceRepo: - org: devstream-io - name: dtm-repo-scaffolding-golang-gin - scmType: github -- name: github-actions - instanceID: default - dependsOn: ["repo-scaffolding.golang-github"] - options: - scm: - owner: [[ RepoOwner ]] - name: go-webapp-devstream-demo - branch: main - scmType: github - token: [[ env GITHUB_TOKEN ]] - pipeline: - configLocation: https://raw.githubusercontent.com/devstream-io/dtm-pipeline-templates/main/github-actions/workflows/main.yml - language: - name: go - framework: gin - imageRepo: - user: [[ ImageRepoUser ]] - password: [[ env IMAGE_REPO_PASSWORD ]] diff --git a/internal/pkg/show/status/output.go b/internal/pkg/show/status/output.go deleted file mode 100644 index 90276940e..000000000 --- a/internal/pkg/show/status/output.go +++ /dev/null @@ -1,85 +0,0 @@ -package status - -import ( - "bytes" - "fmt" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - - "gopkg.in/yaml.v3" - - "github.com/devstream-io/devstream/internal/pkg/statemanager" -) - -type Output struct { - InstanceID string `yaml:"InstanceID"` - Plugin string `yaml:"Plugin"` - Drifted bool `yaml:"Drifted"` - Options map[string]interface{} `yaml:"Options"` - Status *Status `yaml:"Status"` -} - -type Status struct { - // if ResourceStatusInState == ResourceStatusFromRead, - // then InlineStatus is set to equals ResourceStatusInState and ResourceStatusFromRead. - InlineStatus statemanager.ResourceStatus `yaml:",inline,omitempty"` - ResourceStatusInState statemanager.ResourceStatus `yaml:"statusInState,omitempty"` - ResourceStatusFromRead statemanager.ResourceStatus `yaml:"statusNow,omitempty"` -} - -// If the resource has drifted, status.ResourceStatusInState & status.ResourceStatusFromRead must NOT be nil and status.InlineStatus should be nil. -// If the resource hasn't drifted, status.ResourceStatusInState & status.ResourceStatusFromRead should be nil and status.InlineStatus must NOT be nil. -func NewOutput(instanceID, plugin string, options configmanager.RawOptions, status *Status) (*Output, error) { - if ok, err := validateParams(instanceID, plugin, options, status); !ok { - return nil, err - } - - output := &Output{ - InstanceID: instanceID, - Plugin: plugin, - Drifted: false, - Options: options, - Status: status, - } - - if status.InlineStatus == nil { - output.Drifted = true - } - - return output, nil -} - -func (o *Output) Print() error { - var buf bytes.Buffer - encoder := yaml.NewEncoder(&buf) - encoder.SetIndent(2) - - err := encoder.Encode(o) - if err != nil { - return err - } - - fmt.Println(buf.String()) - return nil -} - -func validateParams(instanceID, plugin string, options configmanager.RawOptions, status *Status) (bool, error) { - if instanceID == "" || plugin == "" { - return false, fmt.Errorf("instanceID or plugin cannot be nil") - } - if options == nil { - return false, fmt.Errorf("options cannot be nil") - } - - if status == nil { - return false, fmt.Errorf("status cannot be nil") - } - if status.InlineStatus != nil && (status.ResourceStatusInState != nil || status.ResourceStatusFromRead != nil) { - return false, fmt.Errorf("illegal status content") - } - if status.InlineStatus == nil && (status.ResourceStatusInState == nil || status.ResourceStatusFromRead == nil) { - return false, fmt.Errorf("illegal status content") - } - - return true, nil -} diff --git a/internal/pkg/show/status/status.go b/internal/pkg/show/status/status.go deleted file mode 100644 index 78ea2b526..000000000 --- a/internal/pkg/show/status/status.go +++ /dev/null @@ -1,122 +0,0 @@ -package status - -import ( - "fmt" - "reflect" - "strings" - - "github.com/spf13/viper" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/pluginengine" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - "github.com/devstream-io/devstream/pkg/util/log" -) - -func Show(configFile string) error { - plugin := viper.GetString("plugin") - id := viper.GetString("id") - allFlag := viper.GetBool("all") - - if plugin == "" && id == "" { - allFlag = true - } - - if id == "" && !allFlag { - log.Warnf(`Empty instance name. Maybe you forgot to add --id=INSTANCE_ID. The default value "default" will be used.`) - id = "default" - } - - cfg, err := configmanager.NewManager(configFile).LoadConfig() - if err != nil { - return err - } - if cfg == nil { - return fmt.Errorf("failed to load the config file") - } - - smgr, err := statemanager.NewManager(*cfg.Config.State) - if err != nil { - log.Debugf("Failed to get State Manager: %s.", err) - return err - } - - if allFlag { - return showAll(smgr) - } - return showOne(smgr, id, plugin) -} - -// show all plugins' status -func showAll(smgr statemanager.Manager) error { - fmt.Println() - stateList := smgr.GetStatesMap().ToList() - - if len(stateList) == 0 { - log.Info("No resources found.") - return nil - } - - var retErrs = make([]string, 0) - for i, state := range stateList { - fmt.Printf("================= %d/%d =================\n\n", i+1, len(stateList)) - if err := showOne(smgr, state.InstanceID, state.Name); err != nil { - log.Errorf("Failed to show the status with <%s.%s>, error: %s.", state.InstanceID, state.Name, err) - retErrs = append(retErrs, err.Error()) - // the "continue" here is used to tell you we don't need to return when ONE plugin show failed - continue - } - } - - if len(retErrs) == 0 { - return nil - } - - return fmt.Errorf(strings.Join(retErrs, "; ")) -} - -// show one plugin status -func showOne(smgr statemanager.Manager, id, plugin string) error { - // get state from statemanager - state := smgr.GetState( - statemanager.GenerateStateKeyByToolNameAndInstanceID(plugin, id), - ) - if state == nil { - return fmt.Errorf("state with (id: %s, plugin: %s) not found", id, plugin) - } - - // get state from read - tool := &configmanager.Tool{ - InstanceID: id, - Name: plugin, - DependsOn: state.DependsOn, - Options: state.Options, - } - resourceStatusFromRead, err := pluginengine.Read(tool) - if err != nil { - log.Debugf("Failed to get the resource state with %s.%s. Error: %s.", id, plugin, err) - return err - } - - // assemble the status - var status = &Status{} - if reflect.DeepEqual(state.ResourceStatus, resourceStatusFromRead) { - status.InlineStatus = state.ResourceStatus - // set-to-nil has no effect, but make the logic more readable. Same as below. - status.ResourceStatusInState = nil - status.ResourceStatusFromRead = nil - } else { - status.InlineStatus = nil - status.ResourceStatusInState = state.ResourceStatus - status.ResourceStatusFromRead = resourceStatusFromRead - } - - // get the output - output, err := NewOutput(id, plugin, state.Options, status) - if err != nil { - log.Debugf("Failed to get the output: %s.", err) - } - - // print - return output.Print() -} diff --git a/internal/pkg/start/start.go b/internal/pkg/start/start.go deleted file mode 100644 index 08ad02a00..000000000 --- a/internal/pkg/start/start.go +++ /dev/null @@ -1,43 +0,0 @@ -package start - -import ( - "fmt" - "time" - - "github.com/devstream-io/devstream/internal/pkg/start/tool" -) - -func Start() error { - fmt.Println("I'll give some tools for you.") - time.Sleep(time.Second) - fmt.Println("Are you ready?") - time.Sleep(time.Second) - fmt.Println("Let's get started.") - fmt.Println() - - err := installToolsIfNotExist() - if err != nil { - return err - } - - fmt.Println("\nEverything is going well now.\nEnjoy it!☺️") - fmt.Println() - return nil -} - -func installToolsIfNotExist() error { - for _, t := range tool.GetTools() { - if !t.IfExists() { - if err := t.Install(); err != nil { - return err - } - } - if t.IfStopped != nil && t.IfStopped() { - if err := t.Start(); err != nil { - return err - } - } - fmt.Printf("✅ %s is ready.\n", t.Name) - } - return nil -} diff --git a/internal/pkg/start/tool/argocd.go b/internal/pkg/start/tool/argocd.go deleted file mode 100644 index 90d047d2d..000000000 --- a/internal/pkg/start/tool/argocd.go +++ /dev/null @@ -1,52 +0,0 @@ -package tool - -import ( - "fmt" - "os/exec" - "strings" - - "github.com/devstream-io/devstream/pkg/util/k8s" -) - -const ( - argocdRepoName = "argo" - argocdRepoURL = "https://argoproj.github.io/argo-helm" - argocdNamespace = "argocd" - argocdChartReleaseName = "argocd" - argocdChartName = argocdRepoName + "/" + "argo-cd" -) - -var toolArgocd = tool{ - Name: "Argo CD", - IfExists: func() bool { - cmd := exec.Command("helm", "status", argocdChartReleaseName, "-n", argocdNamespace) - return cmd.Run() == nil - }, - - Install: func() error { - if !confirm("Argo CD") { - return fmt.Errorf("user cancelled") - } - - // create namespace if not exist - kubeClient, err := k8s.NewClient() - if err != nil { - return err - } - if err = kubeClient.UpsertNameSpace(argocdNamespace); err != nil { - return err - } - - // install argocd by helm - err = execCommand([]string{"helm", "repo", "add", argocdRepoName, argocdRepoURL}) - if err != nil && !strings.Contains(err.Error(), "already exists") { - return err - } - - if err = execCommand([]string{"helm", "install", argocdChartReleaseName, argocdChartName, "-n", argocdNamespace, "--wait", "--create-namespace"}); err != nil { - return err - } - - return nil - }, -} diff --git a/internal/pkg/start/tool/docker.go b/internal/pkg/start/tool/docker.go deleted file mode 100644 index b3ee67cf9..000000000 --- a/internal/pkg/start/tool/docker.go +++ /dev/null @@ -1,64 +0,0 @@ -package tool - -import ( - "fmt" - "os/exec" - "time" - - "github.com/manifoldco/promptui" -) - -var toolDocker = tool{ - Name: "Docker", - - IfExists: func() bool { - _, err := exec.LookPath("docker") - return err == nil - }, - - Install: func() error { - if !confirm("Docker") { - return fmt.Errorf("user cancelled") - } - - if err := execCommand([]string{"brew", "install", "docker", "--cask"}); err != nil { - return err - } - return nil - }, - - IfStopped: func() bool { - cmd := exec.Command("docker", "version") - return cmd.Run() != nil - }, - - Start: func() error { - if err := execCommand([]string{"open", "-a", "Docker"}); err != nil { - return err - } - - return waitForDockerRun() - }, -} - -func waitForDockerRun() error { - fmt.Println("\nI've tried to start Docker for you.") - time.Sleep(time.Second) - fmt.Println("But the OS may ask you to authorize it manually.") - time.Sleep(time.Second) - fmt.Println("Please make sure your docker has been started.") - fmt.Println() - time.Sleep(time.Second) - prompt := promptui.Prompt{ - Label: "I've verified that Docker is running properly by using the `docker version` command.", - IsConfirm: true, - } - - _, err := prompt.Run() - if err != nil { - fmt.Println("Please make sure docker starts properly first.") - return err - } - - return nil -} diff --git a/internal/pkg/start/tool/helm.go b/internal/pkg/start/tool/helm.go deleted file mode 100644 index 978b10de4..000000000 --- a/internal/pkg/start/tool/helm.go +++ /dev/null @@ -1,26 +0,0 @@ -package tool - -import ( - "fmt" - "os/exec" -) - -var toolHelm = tool{ - Name: "Helm", - - IfExists: func() bool { - _, err := exec.LookPath("helm") - return err == nil - }, - - Install: func() error { - if !confirm("Helm") { - return fmt.Errorf("user cancelled") - } - - if err := execCommand([]string{"brew", "install", "helm"}); err != nil { - return err - } - return nil - }, -} diff --git a/internal/pkg/start/tool/helper.go b/internal/pkg/start/tool/helper.go deleted file mode 100644 index 57f89c626..000000000 --- a/internal/pkg/start/tool/helper.go +++ /dev/null @@ -1,30 +0,0 @@ -package tool - -import ( - "fmt" - "os" - "os/exec" - - "github.com/manifoldco/promptui" -) - -func confirm(name string) bool { - prompt := promptui.Prompt{ - Label: fmt.Sprintf("Install %s now", name), - IsConfirm: true, - } - - _, err := prompt.Run() - if err != nil { - fmt.Printf("%s must be installed. Quit now.", name) - return false - } - return true -} - -func execCommand(cmdStr []string) error { - cmd := exec.Command(cmdStr[0], cmdStr[1:]...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - return cmd.Run() -} diff --git a/internal/pkg/start/tool/minikube.go b/internal/pkg/start/tool/minikube.go deleted file mode 100644 index 9d6650d39..000000000 --- a/internal/pkg/start/tool/minikube.go +++ /dev/null @@ -1,38 +0,0 @@ -package tool - -import ( - "fmt" - "os/exec" -) - -var toolMinikube = tool{ - Name: "Minikube", - - IfExists: func() bool { - _, err := exec.LookPath("minikube") - return err == nil - }, - - Install: func() error { - if !confirm("Minikube") { - return fmt.Errorf("user cancelled") - } - - if err := execCommand([]string{"brew", "install", "minikube"}); err != nil { - return err - } - return nil - }, - - IfStopped: func() bool { - cmd := exec.Command("minikube", "status") - return cmd.Run() != nil - }, - - Start: func() error { - if err := execCommand([]string{"minikube", "start"}); err != nil { - return err - } - return nil - }, -} diff --git a/internal/pkg/start/tool/tool.go b/internal/pkg/start/tool/tool.go deleted file mode 100644 index 4e350f258..000000000 --- a/internal/pkg/start/tool/tool.go +++ /dev/null @@ -1,19 +0,0 @@ -package tool - -type existsFunc func() bool -type stopedFunc func() bool -type installFunc func() error -type startFunc func() error -type tool struct { - Name string - IfExists existsFunc - Install installFunc - // IfStopped can be nil if it is not needed. - // if IfStopped != nil -> Start can't be nil too - IfStopped stopedFunc - Start startFunc -} - -func GetTools() []tool { - return []tool{toolDocker, toolMinikube, toolHelm, toolArgocd} -} diff --git a/internal/pkg/statemanager/manager.go b/internal/pkg/statemanager/manager.go deleted file mode 100644 index 8bc32f6cf..000000000 --- a/internal/pkg/statemanager/manager.go +++ /dev/null @@ -1,131 +0,0 @@ -package statemanager - -import ( - "fmt" - - "gopkg.in/yaml.v3" - - "github.com/devstream-io/devstream/internal/pkg/backend" - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/pkg/util/log" -) - -type ComponentAction string - -const ( - ActionCreate ComponentAction = "Create" - ActionUpdate ComponentAction = "Update" - ActionDelete ComponentAction = "Delete" -) - -// Manager knows how to manage the StatesMap. -type Manager interface { - backend.Backend - - GetStatesMap() StatesMap - - GetState(key StateKey) *State - AddState(key StateKey, state State) error - UpdateState(key StateKey, state State) error - DeleteState(key StateKey) error - GetOutputs(key StateKey) (ResourceOutputs, error) -} - -// manager is the default implement with Manager -type manager struct { - backend.Backend - statesMap StatesMap -} - -var m *manager - -// NewManager returns a new Manager and reads states through backend defined in config. -func NewManager(stateConfig configmanager.State) (Manager, error) { - if m != nil { - return m, nil - } - - log.Debugf("The global manager m is not initialized.") - - // Get the backend from config - // backend config has been validated in configmanager.Manager.LoadConfig() - b, err := backend.GetBackend(stateConfig) - if err != nil { - log.Errorf("Failed to get the Backend: %s.", err) - return nil, err - } - - m = &manager{ - Backend: b, - statesMap: NewStatesMap(), - } - - // Read the initial states data from backend - data, err := b.Read() - if err != nil { - log.Debugf("Failed to read data from backend: %s.", err) - return nil, err - } - - tmpMap := make(map[StateKey]State) - if err = yaml.Unmarshal(data, tmpMap); err != nil { - log.Errorf("Failed to unmarshal the state of type < %s >, . error: %s.", stateConfig.Backend, err) - log.Errorf("Reading the state failed, it might have been compromised/modified by someone other than DTM.") - log.Errorf("The state is managed by DTM automatically. Please do not modify it yourself.") - return nil, fmt.Errorf("state format error") - } - for k, v := range tmpMap { - log.Debugf("Got a state from the backend: %s -> %v.", k, v) - m.statesMap.Store(k, v) - } - - return m, nil -} - -func (m *manager) GetStatesMap() StatesMap { - return m.statesMap -} - -func (m *manager) GetState(key StateKey) *State { - if s, exist := m.statesMap.Load(key); exist { - state, _ := s.(State) - return &state - } - return nil -} - -// AddState adds a new state to the manager. -// If the state already exists, update it. -func (m *manager) AddState(key StateKey, state State) error { - m.statesMap.Store(key, state) - return m.Backend.Write(m.GetStatesMap().Format()) -} - -// UpdateState adds a new state to the manager. -// If the state already exists, update it. -// note: maybe it is duplicated with AddState -func (m *manager) UpdateState(key StateKey, state State) error { - m.statesMap.Store(key, state) - return m.Backend.Write(m.GetStatesMap().Format()) -} - -// DeleteState deletes a state from the manager. -// If the state does not exist, do nothing. -func (m *manager) DeleteState(key StateKey) error { - m.statesMap.Delete(key) - return m.Backend.Write(m.GetStatesMap().Format()) -} - -// GetOutputs is used to get the origin outputs of a toolName_InstanceID -func (m *manager) GetOutputs(key StateKey) (ResourceOutputs, error) { - state := m.GetState(key) - if state == nil { - return nil, fmt.Errorf(`key (%s) not in state, it may be failed when "Create"`, key) - } - - if value := state.ResourceStatus.GetOutputs(); value != nil { - return value, nil - } - - return nil, fmt.Errorf("outputs not in state %s", state.Name) -} diff --git a/internal/pkg/statemanager/manager_suit_test.go b/internal/pkg/statemanager/manager_suit_test.go deleted file mode 100644 index 7ad3f8c79..000000000 --- a/internal/pkg/statemanager/manager_suit_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package statemanager_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestPlanmanager(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Util Statemanager Suite") -} diff --git a/internal/pkg/statemanager/manager_test.go b/internal/pkg/statemanager/manager_test.go deleted file mode 100644 index 05f8dc3de..000000000 --- a/internal/pkg/statemanager/manager_test.go +++ /dev/null @@ -1,96 +0,0 @@ -package statemanager_test - -import ( - "os" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/internal/pkg/backend/local" - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/statemanager" -) - -var _ = Describe("Statemanager", func() { - var smgr statemanager.Manager - var err error - var testKey statemanager.StateKey - var testState statemanager.State - - BeforeEach(func() { - stateCfg := configmanager.State{ - Backend: "local", - Options: configmanager.StateConfigOptions{ - StateFile: "devstream.state", - }, - } - smgr, err = statemanager.NewManager(stateCfg) - Expect(err).NotTo(HaveOccurred()) - Expect(smgr).NotTo(BeNil()) - testKey = statemanager.StateKey("name_githubactions") - testState = statemanager.State{ - InstanceID: "name", - Name: "githubactions", - Options: configmanager.RawOptions{"a": "value"}, - ResourceStatus: statemanager.ResourceStatus{"a": "value"}, - } - }) - - Describe("Manager", func() { - It("Should add state right", func() { - err = smgr.AddState(testKey, testState) - Expect(err).NotTo(HaveOccurred()) - - stateB := smgr.GetState(testKey) - Expect(&testState).To(Equal(stateB)) - - err = smgr.DeleteState(testKey) - Expect(err).NotTo(HaveOccurred()) - - stateC := smgr.GetState(testKey) - Expect(stateC).To(BeZero()) - }) - - It("Should get the state list", func() { - // Adding order: A,C,B - // List order should be: A,B,C - key := statemanager.StateKey("a_githubactions") - stateA := statemanager.State{ - InstanceID: "a", - Name: "githubactions", - Options: map[string]interface{}{"a": "value"}, - ResourceStatus: map[string]interface{}{"a": "value"}, - } - err = smgr.AddState(key, stateA) - Expect(err).NotTo(HaveOccurred()) - - key = statemanager.StateKey("c_githubactions") - stateC := statemanager.State{ - InstanceID: "c", - Name: "githubactions", - Options: configmanager.RawOptions{"c": "value"}, - ResourceStatus: statemanager.ResourceStatus{"c": "value"}, - } - err = smgr.AddState(key, stateC) - Expect(err).NotTo(HaveOccurred()) - - key = statemanager.StateKey("b_githubactions") - stateB := statemanager.State{ - InstanceID: "b", - Name: "githubactions", - Options: configmanager.RawOptions{"b": "value"}, - ResourceStatus: statemanager.ResourceStatus{"b": "value"}, - } - err = smgr.AddState(key, stateB) - Expect(err).NotTo(HaveOccurred()) - - stateList := smgr.GetStatesMap().ToList() - Expect(stateList).To(Equal([]statemanager.State{stateA, stateB, stateC})) - }) - }) - - AfterEach(func() { - err = os.RemoveAll(local.DefaultStateFile) - Expect(err).NotTo(HaveOccurred()) - }) -}) diff --git a/internal/pkg/statemanager/state.go b/internal/pkg/statemanager/state.go deleted file mode 100644 index 38789fc46..000000000 --- a/internal/pkg/statemanager/state.go +++ /dev/null @@ -1,114 +0,0 @@ -package statemanager - -import ( - "bytes" - "fmt" - "sort" - - "golang.org/x/exp/maps" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - - "gopkg.in/yaml.v3" - - "github.com/devstream-io/devstream/pkg/util/log" - "github.com/devstream-io/devstream/pkg/util/mapz/concurrentmap" -) - -// We call what the plugin created a ResourceStatus, and which is stored as part of the state. -type ResourceStatus map[string]interface{} - -type ResourceOutputs map[string]interface{} - -// State is the single component's state. -type State struct { - Name string `yaml:"name"` - InstanceID string `yaml:"instanceID"` - DependsOn []string `yaml:"dependsOn"` - Options configmanager.RawOptions `yaml:"options"` - ResourceStatus ResourceStatus `yaml:"resourceStatus"` -} - -func (rs ResourceStatus) SetOutputs(outputs ResourceOutputs) { - (rs)["outputs"] = outputs -} - -func (rs ResourceStatus) GetOutputs() ResourceOutputs { - outputs, ok := (rs)["outputs"] - if !ok { - return nil - } - outputStatus, isStatus := outputs.(ResourceStatus) - if !isStatus { - return outputs.(ResourceOutputs) - } - // if outputs type is ResourceStatus, transfer this type to ResourceOutputs - outputData := ResourceOutputs{} - maps.Copy(outputData, outputStatus) - return outputData -} - -type StatesMap struct { - *concurrentmap.ConcurrentMap -} - -func NewStatesMap() StatesMap { - return StatesMap{ - ConcurrentMap: concurrentmap.NewConcurrentMap(StateKey(""), State{}), - } -} - -func (s StatesMap) DeepCopy() StatesMap { - newStatesMap := NewStatesMap() - s.Range(func(key, value interface{}) bool { - newStatesMap.Store(key, value) - return true - }) - return newStatesMap -} - -func (s StatesMap) ToList() []State { - var res []State - s.Range(func(key, value interface{}) bool { - res = append(res, value.(State)) - return true - }) - - sort.Slice(res, func(i, j int) bool { - keyi := fmt.Sprintf("%s.%s", res[i].InstanceID, res[i].Name) - keyj := fmt.Sprintf("%s.%s", res[j].InstanceID, res[j].Name) - return keyi < keyj - }) - - return res -} - -func (s StatesMap) Format() []byte { - tmpMap := make(map[StateKey]State) - s.Range(func(key, value interface{}) bool { - tmpMap[key.(StateKey)] = value.(State) - return true - }) - - if len(tmpMap) == 0 { - return []byte{} - } - - var buf bytes.Buffer - encoder := yaml.NewEncoder(&buf) - encoder.SetIndent(2) - err := encoder.Encode(&tmpMap) - if err != nil { - log.Error(err) - return nil - } - - return buf.Bytes() -} - -// Note: Please use the GenerateStateKeyByToolNameAndInstanceID function to generate StateKey instance. -type StateKey string - -func GenerateStateKeyByToolNameAndInstanceID(toolName string, instanceID string) StateKey { - return StateKey(fmt.Sprintf("%s_%s", toolName, instanceID)) -} diff --git a/internal/pkg/statemanager/state_test.go b/internal/pkg/statemanager/state_test.go deleted file mode 100644 index 651f4e22c..000000000 --- a/internal/pkg/statemanager/state_test.go +++ /dev/null @@ -1,91 +0,0 @@ -package statemanager - -import ( - "fmt" - "reflect" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/pkg/util/mapz/concurrentmap" -) - -var _ = Describe("Statemanager.state", func() { - Describe("GenerateStateKeyByToolNameAndInstanceID func", func() { - It("should ouput state key base on toolName and plugin kind", func() { - var testCases = []struct { - toolName string - plugKind string - expectStateKey StateKey - }{ - {"test_tool", "test_kind", "test_tool_test_kind"}, - {"123", "1", "123_1"}, - } - for _, t := range testCases { - funcResult := GenerateStateKeyByToolNameAndInstanceID(t.toolName, t.plugKind) - Expect(funcResult).Should(Equal(t.expectStateKey)) - } - }) - }) - - Describe("NewStatesMap func", func() { - It("should return normal statesMap", func() { - newStatesMap := NewStatesMap() - Expect(newStatesMap).ShouldNot(BeNil()) - }) - }) - - Describe("StatesMap struct", func() { - var ( - testMap StatesMap - testStateKey StateKey - testStateVal State - ) - - BeforeEach(func() { - testStateKey = StateKey("test_key") - testStateVal = State{ - Name: "test_tool", - InstanceID: "test_instance", - } - testMap = StatesMap{ - ConcurrentMap: concurrentmap.NewConcurrentMap( - StateKey(""), State{}, - ), - } - }) - - Context("DeepCopy method", func() { - It("should return not same map", func() { - newTestMap := testMap.DeepCopy() - oldMapAddress := reflect.ValueOf(&testMap) - newMapAddress := reflect.ValueOf(&newTestMap) - Expect(oldMapAddress).ShouldNot(Equal(newMapAddress)) - valEqual := reflect.DeepEqual(newTestMap, testMap) - Expect(valEqual).Should(BeTrue()) - }) - }) - - Describe("ToList method", func() { - It("should return not empty list if have key", func() { - testMap.Store(testStateKey, testStateVal) - tList := testMap.ToList() - Expect(len(tList)).Should(Equal(1)) - Expect(tList[0].Name).Should(Equal(testStateVal.Name)) - Expect(tList[0].InstanceID).Should(Equal(testStateVal.InstanceID)) - }) - }) - - Describe("Format method", func() { - It("should return formated info for map", func() { - testMap.Store(testStateKey, testStateVal) - formatedInfo := testMap.Format() - formatResult := fmt.Sprintf( - "test_key:\n name: %s\n instanceID: %s\n dependsOn: []\n options: {}\n resourceStatus: {}\n", - testStateVal.Name, testStateVal.InstanceID, - ) - Expect(string(formatedInfo)).Should(Equal(formatResult)) - }) - }) - }) -}) diff --git a/internal/pkg/upgrade/upgrade.go b/internal/pkg/upgrade/upgrade.go deleted file mode 100644 index 7d98dda52..000000000 --- a/internal/pkg/upgrade/upgrade.go +++ /dev/null @@ -1,211 +0,0 @@ -package upgrade - -import ( - "io" - "net/http" - "os" - "path/filepath" - "runtime" - "strings" - - "github.com/Masterminds/semver" - - "github.com/devstream-io/devstream/internal/pkg/version" - "github.com/devstream-io/devstream/pkg/util/downloader" - "github.com/devstream-io/devstream/pkg/util/interact" - "github.com/devstream-io/devstream/pkg/util/log" -) - -const ( - //the rollback step for the applyUpgrade function - STEP1 = iota - STEP2 - STEP3 - STEP4 - COMPLETED - - assetName = "dtm-" + runtime.GOOS + "-" + runtime.GOARCH - dtmTmpFileName = "dtm-tmp" - dtmBakFileName = "dtm-bak" - dtmDownloadUrl = "https://download.devstream.io/" - latestVersion = "https://download.devstream.io/latest_version" -) - -// since dtm file name can be changeable by user,so it should be a variable to get current dtm file name -var dtmFileName string - -// Upgrade updates dtm binary file to the latest release version -func Upgrade(continueDirectly bool) error { - if version.Dev { - log.Info("Dtm upgrade: do not support to upgrade dtm in development version.") - os.Exit(0) - } - - // get dtm bin file path like `/usr/local/bin/dtm-linux-amd64` - binFilePath, err := os.Executable() - if err != nil { - return err - } - // get dtm bin file name like `dtm-linux-amd64` - _, dtmFileName = filepath.Split(binFilePath) - - log.Debugf("Dtm upgrade: dtm file name is : %v", dtmFileName) - - workDir := strings.Trim(binFilePath, dtmFileName) - if err != nil { - return err - } - log.Debugf("Dtm upgrade: work path is : %v", workDir) - - // 1. Get the latest release version - request, err := http.Get(latestVersion) - if err != nil { - return err - } - - body, err := io.ReadAll(request.Body) - latestReleaseTagName := string(body[0 : len(body)-1]) - if err != nil { - return err - } - log.Debugf("Dtm upgrade: got latest release version: %v", latestReleaseTagName) - - // 2. Check whether Upgrade is needed - // To Use Semantic Version to judge. "https://semver.org/" - shouldUpgrade, err := checkUpgrade(version.Version, latestReleaseTagName) - if err != nil { - return err - } - if shouldUpgrade { - log.Infof("Dtm upgrade: new dtm version: %v is available.", latestReleaseTagName) - if !continueDirectly { - continued := interact.AskUserIfContinue("Would you like to Upgrade? [y/n]") - if !continued { - os.Exit(0) - } - } - - // 3. Download the latest release version of dtm - log.Info("Dtm upgrade: downloading the latest release version of dtm, please wait for a while.") - var downloadURL strings.Builder - downloadURL.WriteString(dtmDownloadUrl) - downloadURL.WriteString(latestReleaseTagName + "/") - downloadURL.WriteString(assetName) - downloadSize, downloadError := downloader.New().WithProgressBar().Download(downloadURL.String(), dtmTmpFileName, workDir) - if downloadError != nil { - log.Debugf("Failed to download dtm: %v-%v.", latestReleaseTagName, assetName) - return downloadError - } - log.Debugf("Downloaded <%d> bytes.", downloadSize) - - // 4. Replace old dtm with the latest one - if err = applyUpgrade(workDir); err != nil { - log.Debug("Failed to replace dtm with latest version.") - return err - } - log.Info("Dtm upgrade successfully!") - return nil - } - - log.Info("Dtm upgrade: dtm is the latest version.") - return nil -} - -// checkUpgrade use third-party library `github.com/Masterminds/semver` to compare versions -func checkUpgrade(oldVersion, newVersion string) (bool, error) { - oldSemVer, newSemVer, err := parseVersion(oldVersion, newVersion) - if err != nil { - return false, err - } - - if oldSemVer.Compare(newSemVer) == -1 { - return true, nil - } - return false, nil -} - -// parseVersion parses string version to semver.Version -func parseVersion(old, new string) (*semver.Version, *semver.Version, error) { - oldSemVer, err := semver.NewVersion(old) - if err != nil { - return nil, nil, err - } - - newSemVer, err := semver.NewVersion(new) - if err != nil { - return nil, nil, err - } - - return oldSemVer, newSemVer, nil -} - -// applyUpgrade use os.Rename replace old version dtm with the latest one -// (1) rename current dtm file name to `dtm-bak`. -// (2) rename `dtm-tmp` to current dtm file name. -// (3) grant new dtm file execute permission. -// (4) remove `dtm-bak` binary file. - -func applyUpgrade(workDir string) error { - dtmFilePath := filepath.Join(workDir, dtmFileName) - dtmBakFilePath := filepath.Join(workDir, dtmBakFileName) - dtmTmpFilePath := filepath.Join(workDir, dtmTmpFileName) - updateProgress := STEP1 - defer func() { - for ; updateProgress >= STEP1; updateProgress-- { - switch updateProgress { - //If the error occur when step 1 (rename dtmFileName to `dtm-bak`), delete `dtm-tmp` - case STEP1: - if err := os.Remove(dtmTmpFilePath); err != nil { - log.Debugf("Dtm upgrade rollback error: %s", err.Error()) - } - - //the error occur in the step 2 - case STEP2: - if err := os.Rename(dtmBakFilePath, dtmFilePath); err != nil { - log.Debugf("Dtm upgrade rollback error: %s", err.Error()) - } - - //the error occur in the step 3 - case STEP3: - if err := os.Rename(dtmFilePath, dtmTmpFilePath); err != nil { - log.Debugf("Dtm upgrade rollback error: %s", err.Error()) - } - - //the error occur in the step 4 - case STEP4: - if err := os.Chmod(dtmFilePath, 0644); err != nil { - log.Debugf("Dtm upgrade rollback error: %s", err.Error()) - } - case COMPLETED: - //Successfully completed all step - return - } - } - }() - - if err := os.Rename(dtmFilePath, dtmBakFilePath); err != nil { - return err - } - updateProgress++ - log.Debugf("Dtm upgrade: rename %s to dtm-bak successfully.", dtmFileName) - - if err := os.Rename(dtmTmpFilePath, dtmFilePath); err != nil { - return err - } - updateProgress++ - log.Debugf("Dtm upgrade: rename dtm-tmp to %s successfully.", dtmFileName) - - if err := os.Chmod(dtmFilePath, 0755); err != nil { - return err - } - updateProgress++ - log.Debugf("Dtm upgrade: grant %s execute permission successfully.", dtmFileName) - - if err := os.Remove(dtmBakFilePath); err != nil { - return err - } - updateProgress++ - log.Debug("Dtm upgrade: remove dtm-bak successfully.") - - return nil -} diff --git a/internal/pkg/upgrade/upgrade_test.go b/internal/pkg/upgrade/upgrade_test.go deleted file mode 100644 index bf2baa6d9..000000000 --- a/internal/pkg/upgrade/upgrade_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package upgrade - -import ( - "testing" -) - -func Test_checkForUpdates(t *testing.T) { - type args struct { - old string - new string - } - tests := []struct { - name string - args args - want bool - }{ - {name: "true case with prefix", args: args{old: "v0.6.0", new: "v0.7.0"}, want: true}, - {name: "true case without prefix", args: args{old: "0.6.3", new: "0.7.1"}, want: true}, - {name: "true case with and without prefix", args: args{old: "1.16.100", new: "v1.17.0"}, want: true}, - - {name: "false case with prefix", args: args{old: "v0.7.0", new: "v0.7.0"}, want: false}, - {name: "false case without prefix", args: args{old: "0.7.3", new: "0.7.3"}, want: false}, - {name: "true case with and without prefix", args: args{old: "1.18.100", new: "v1.17.0"}, want: false}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := checkUpgrade(tt.args.old, tt.args.new) - if err != nil || got != tt.want { - t.Errorf("checkForUpdates() error = %v, old version = %v, new version = %v, want = %v, got = %v", err, tt.args.old, tt.args.new, tt.want, got) - return - } - }) - } -} diff --git a/internal/pkg/version/version.go b/internal/pkg/version/version.go deleted file mode 100644 index 833d42627..000000000 --- a/internal/pkg/version/version.go +++ /dev/null @@ -1,16 +0,0 @@ -package version - -import "regexp" - -// Version is the version of DevStream. -// Assign the value when building with the -X parameter. Example: -// -X github.com/devstream-io/devstream/internal/pkg/version.Version=${VERSION} -// See the Makefile for more info. -var Version string - -var Dev bool - -func init() { - // check is in dev version - Dev = !regexp.MustCompile(`^\d+\.\d+\.\d+$`).MatchString(Version) -} diff --git a/internal/response/response.go b/internal/response/response.go new file mode 100644 index 000000000..3f3f6466e --- /dev/null +++ b/internal/response/response.go @@ -0,0 +1,80 @@ +package response + +import ( + "encoding/json" + "fmt" + "github.com/devstream-io/devstream/internal/log" + + "gopkg.in/yaml.v3" +) + +type StatusCode int +type MessageText string + +type Response struct { + Status StatusCode `json:"status" yaml:"status"` + Message MessageText `json:"message" yaml:"message"` + Log string `json:"log" yaml:"log"` +} + +var ( + StatusOK StatusCode = 0 + StatusError StatusCode = 1 +) + +var ( + MessageOK MessageText = "OK" + MessageError MessageText = "ERROR" +) + +func New(status StatusCode, message MessageText, log string) *Response { + return &Response{ + Status: status, + Message: message, + Log: log, + } +} + +func (r *Response) Print(format string) { + log.Debugf("Format: %s", format) + switch format { + case "json": + r.printJSON() + case "yaml": + r.printYAML() + default: + r.printRaw() + } +} + +func (r *Response) printRaw() { + fmt.Println(r.toRaw()) +} + +func (r *Response) printJSON() { + fmt.Println(r.toJSON()) +} + +func (r *Response) printYAML() { + fmt.Println(r.toYAML()) +} + +func (r *Response) toRaw() string { + return r.Log +} + +func (r *Response) toJSON() string { + str, err := json.Marshal(r) + if err != nil { + return err.Error() + } + return string(str) +} + +func (r *Response) toYAML() string { + str, err := yaml.Marshal(r) + if err != nil { + return err.Error() + } + return string(str) +} diff --git a/main.go b/main.go new file mode 100644 index 000000000..f311a15e1 --- /dev/null +++ b/main.go @@ -0,0 +1,11 @@ +/* +Copyright © 2023 NAME HERE + +*/ +package main + +import "github.com/devstream-io/devstream/cmd" + +func main() { + cmd.Execute() +} diff --git a/mkdocs.yml b/mkdocs.yml deleted file mode 100644 index 1696bfd2d..000000000 --- a/mkdocs.yml +++ /dev/null @@ -1,163 +0,0 @@ -site_name: "DevStream - Your DevOps Toolchain Manager" -theme: - name: material - features: - - content.tabs.link - - content.code.annotate - palette: - - scheme: default - primary: indigo - accent: indigo - toggle: - icon: material/brightness-7 - name: Switch to dark mode - - scheme: slate - primary: indigo - accent: indigo - toggle: - icon: material/brightness-4 - name: Switch to light mode -edit_uri: edit/main/docs -site_url: https://docs.devstream.io -repo_name: devstream -repo_url: https://github.com/devstream-io/devstream -site_description: The DevStream Docs -markdown_extensions: -- abbr -- toc: - permalink: true -- pymdownx.snippets: - base_path: - - "docs" - - "docs/../internal/pkg/show/config/plugins/" - auto_append: - - includes/glossary.md -- pymdownx.highlight: - anchor_linenums: true - auto_title: true -- pymdownx.superfences -- pymdownx.tabbed: - alternate_style: true -- attr_list -- md_in_html -- admonition -- pymdownx.details -extra_css: -- assets/versions.css -extra_javascript: -- assets/versions.js -extra: - alternate: - - name: English - link: "" - lang: en - - name: 中文 - link: index.zh/ - lang: zh -plugins: -- search: -- i18n: - languages: - en: "English" - zh: "简体中文" - default_language: 'en' - no_translation: - en: "This page isn't translated to English." - zh: "这个页面还没有中文翻译。" - translate_nav: - en: - DTM Commands Explained in Depth: "DTM Commands Explained in Depth" - Plugins: "Plugins" - Use Cases: "Use Cases" - Overview: "Overview" - Reference: "Reference" - Developer Guide: "Developer Guide" - Core Concepts: "Core Concepts" - Commands: "Commands" - Contributor Ladder: "Contributor Ladder" - Road Map: "Road Map" - Code Review Guide: "Code Review Guide" - Case Overview: "Case Overview" - Contribute Workflow: "Contribute Workflow" - DevStream Architecture: "DevStream Architecture" - Environment Setup and Development: "Environment Setup and Development" - Docs Contribution: "Docs Contribution" - Helm Installer: "Helm Installer" - zh: - DTM Commands Explained in Depth: "DTM 命令详解" - Plugins: "插件" - Use Cases: "应用场景" - Tools Deployment: "工具部署" - Overview: "概览" - Reference: "参考实践" - Developer Guide: "开发指导" - Core Concepts: "核心概念" - Commands: "命令" - Contributor Ladder: "贡献者成长阶梯" - Road Map: "路线图" - Code Review Guide: "代码 review 指导" - Case Overview: "场景概览" - Contribute Workflow: "贡献与协作流程" - DevStream Architecture: "DevStream 架构" - Environment Setup and Development: "环境搭建与开发" - Docs Contribution: "文档类贡献" - Helm Installer: "Helm 安装器" -- literate-nav -nav: -- Overview: . -- understanding_the_basics*.md -- quickstart*.md -- install*.md -- Core Concepts: - - core-concepts/overview*.md - - core-concepts/tools*.md - - core-concepts/apps*.md - - core-concepts/state*.md - - core-concepts/config*.md -- Commands: - - commands/autocomplete*.md - - commands/*.md - - commands/develop*.md - - commands/verify*.md -- Use Cases: - - use-cases/case-overview*.md - - GitLab + Jenkins + Harbor: - - use-cases/gitlab-jenkins-harbor/*.md - - GitLab/GitHub + Argo CD: - - use-cases/gitops-python-flask/*.md - - Tools Deployment: - - use-cases/helm-installer*.md - - GitOps(to be updated): - - use-cases/gitops/*.md - - Reference: - - use-cases/reference/*.md -- Plugins: - - plugins/plugins-list*.md - - Helm Installer: - - plugins/helm-installer/helm-installer*.md - - plugins/helm-installer/*.md - - plugins/*.md -- contributing_guide*.md -- contributor_ladder*.md -- Governance ⧉: https://github.com/devstream-io/devstream/blob/main/GOVERNANCE.md -- PMC: pmc.md -- Developer Guide: - - Contribute Workflow: - - development/git-workflow/git-workflow*.md - - development/git-workflow/good-first-issue*.md - - development/git-workflow/help-wanted*.md - - development/git-workflow/commit-messages*.md - - development/git-workflow/branch-and-release*.md - - development/git-workflow/reviewing*.md - - development/architecture*.md - - development/project-layout*.md - - Environment Setup and Development: - - development/dev/dev-env-setup*.md - - development/dev/lint*.md - - development/dev/build*.md - - development/dev/test*.md - - development/dev/creating-a-plugin*.md - - Docs Contribution: - - development/docs-contribution/mkdocs*.md - - development/docs-contribution/translation*.md -- Road Map ⧉: https://github.com/devstream-io/devstream/blob/main/ROADMAP.md diff --git a/pkg/util/cli/status.go b/pkg/util/cli/status.go deleted file mode 100644 index 43d9c5908..000000000 --- a/pkg/util/cli/status.go +++ /dev/null @@ -1,52 +0,0 @@ -package cli - -import ( - "bytes" - "fmt" - "time" - - "github.com/briandowns/spinner" - - "github.com/devstream-io/devstream/pkg/util/log" -) - -type Status struct { - spinner *spinner.Spinner - logBuffer *bytes.Buffer - statusMessage string - successFormat string - failureFormat string -} - -func StatusForPlugin() *Status { - spinner := spinner.New(spinner.CharSets[7], 5000*time.Microsecond) - _ = spinner.Color("yellow") - return &Status{ - spinner: spinner, - logBuffer: new(bytes.Buffer), - successFormat: "\x1b[32m✓\x1b[0m %s\n", - failureFormat: "\x1b[31m✗\x1b[0m %s\n", - } -} - -func (s *Status) Start(status string) { - s.statusMessage = status - s.spinner.Suffix = fmt.Sprintf(" %s", status) - log.RedirectOutput(s.logBuffer) - s.spinner.Start() -} - -// End completes the current status, ending any previous spinning and -// marking the status as success or failure -func (s *Status) End(err error) { - log.RecoverOutput() - if err == nil { - s.spinner.FinalMSG = fmt.Sprintf(s.successFormat, s.statusMessage) - } else { - s.spinner.FinalMSG = fmt.Sprintf(s.failureFormat, s.statusMessage) - } - // logBuffer contains log during status.Start to status.End - // we should write this logBuffer to something for error trace - s.logBuffer.Reset() - s.spinner.Stop() -} diff --git a/pkg/util/cli/viper.go b/pkg/util/cli/viper.go deleted file mode 100644 index cbf21ddab..000000000 --- a/pkg/util/cli/viper.go +++ /dev/null @@ -1,23 +0,0 @@ -package cli - -import ( - "log" - - "github.com/spf13/cobra" - "github.com/spf13/viper" -) - -type PreRunFunc func(cmd *cobra.Command, args []string) - -// BindPFlags accepts a list of flag names and binds the corresponding flags to Viper. This function servers -// as a workaround to a known bug in Viper, in which two commands can't share the same flag name. -// To learn more about the bug, see: https://github.com/spf13/viper/issues/233 -func BindPFlags(flagNames []string) PreRunFunc { - return func(cmd *cobra.Command, args []string) { - for _, name := range flagNames { - if err := viper.BindPFlag(name, cmd.Flags().Lookup(name)); err != nil { - log.Fatalf("Failed to bind flag %s: %s", name, err) - } - } - } -} diff --git a/pkg/util/cloud/aws/s3/client.go b/pkg/util/cloud/aws/s3/client.go deleted file mode 100644 index 31170eab3..000000000 --- a/pkg/util/cloud/aws/s3/client.go +++ /dev/null @@ -1,18 +0,0 @@ -package s3 - -import ( - "context" - - "github.com/aws/aws-sdk-go-v2/config" - "github.com/aws/aws-sdk-go-v2/service/s3" -) - -func NewClient(ctx context.Context, region string) (*s3.Client, error) { - cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion(region)) - if err != nil { - return nil, err - } - - client := s3.NewFromConfig(cfg) - return client, nil -} diff --git a/pkg/util/cloud/aws/s3/s3file.go b/pkg/util/cloud/aws/s3/s3file.go deleted file mode 100644 index a1423ad9b..000000000 --- a/pkg/util/cloud/aws/s3/s3file.go +++ /dev/null @@ -1,108 +0,0 @@ -package s3 - -import ( - "bytes" - "context" - "fmt" - "io" - "strings" - - "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/service/s3" - - awsutil "github.com/devstream-io/devstream/pkg/util/cloud/aws/util" - "github.com/devstream-io/devstream/pkg/util/log" -) - -type S3API interface { - GetObject(ctx context.Context, params *s3.GetObjectInput, optFns ...func(*s3.Options)) (*s3.GetObjectOutput, error) - PutObject(ctx context.Context, params *s3.PutObjectInput, optFns ...func(*s3.Options)) (*s3.PutObjectOutput, error) -} - -type S3File struct { - Bucket string - Region string - Key string - ctx context.Context - api S3API -} - -func NewS3File(ctx context.Context, api S3API, bucket, region, key string) (*S3File, error) { - file := S3File{ - Bucket: bucket, - Region: region, - Key: key, - ctx: ctx, - api: api, - } - - err := createFileIfNotExist(ctx, api, bucket, key) - if err != nil { - return nil, err - } - - return &file, nil -} - -func createFileIfNotExist(ctx context.Context, api S3API, bucket, key string) error { - params := &s3.GetObjectInput{ - Bucket: aws.String(bucket), - Key: aws.String(key), - } - _, err := api.GetObject(ctx, params) - if err == nil { - // file exist - return nil - } - - if !strings.Contains(err.Error(), "404") { - // not 404 error - return err - } else { - // file not found, create - log.Infof("S3 key %s not found in bucket %s, will create.", key, bucket) - params := &s3.PutObjectInput{ - Bucket: aws.String(bucket), - Key: aws.String(key), - Body: bytes.NewReader(make([]byte, 0)), - } - _, err := api.PutObject(ctx, params) - if err == nil { - log.Infof("S3 key %s created in bucket %s.", key, bucket) - } - return err - } -} - -func (f *S3File) Put(data []byte) error { - params := &s3.PutObjectInput{ - Bucket: aws.String(f.Bucket), - Key: aws.String(f.Key), - Body: bytes.NewReader(data), - } - _, err := f.api.PutObject(f.ctx, params) - if err != nil { - awsutil.LogAWSError(err) - return fmt.Errorf("failed to upload %s to bucket %s", f.Key, f.Bucket) - } - return nil -} - -func (f *S3File) Get() ([]byte, error) { - params := &s3.GetObjectInput{ - Bucket: aws.String(f.Bucket), - Key: aws.String(f.Key), - } - out, err := f.api.GetObject(f.ctx, params) - if err != nil { - awsutil.LogAWSError(err) - return nil, fmt.Errorf("failed to download %s from bucket %s", f.Key, f.Bucket) - } - - defer out.Body.Close() - data, err := io.ReadAll(out.Body) - if err != nil { - return nil, err - } - return data, nil -} diff --git a/pkg/util/cloud/aws/s3/s3file_test.go b/pkg/util/cloud/aws/s3/s3file_test.go deleted file mode 100644 index b7c89553d..000000000 --- a/pkg/util/cloud/aws/s3/s3file_test.go +++ /dev/null @@ -1,140 +0,0 @@ -package s3_test - -import ( - "bytes" - "context" - "io" - "strconv" - "testing" - - "github.com/aws/aws-sdk-go-v2/service/s3" - - s3api "github.com/devstream-io/devstream/pkg/util/cloud/aws/s3" -) - -const ( - TestBucket = "test-bucket" - TestRegion = "ap-northeast-1" - TestKey = "foo.txt" - TestContent = "hello, world!" -) - -type MockS3Client struct { - t *testing.T - bucket string - key string - content []byte -} - -func NewMockS3Client(t *testing.T, bucket, key string, content []byte) *MockS3Client { - return &MockS3Client{ - t: t, - bucket: bucket, - key: key, - content: content, - } -} - -func (mock *MockS3Client) GetObject(ctx context.Context, params *s3.GetObjectInput, optFns ...func(*s3.Options)) (*s3.GetObjectOutput, error) { - mock.t.Helper() - - checkStringParam(mock.t, "bucket", mock.bucket, params.Bucket) - checkStringParam(mock.t, "key", mock.key, params.Key) - - return &s3.GetObjectOutput{ - Body: io.NopCloser(bytes.NewReader([]byte(TestContent))), - }, nil -} - -func (mock *MockS3Client) PutObject(ctx context.Context, params *s3.PutObjectInput, optFns ...func(*s3.Options)) (*s3.PutObjectOutput, error) { - mock.t.Helper() - - checkStringParam(mock.t, "bucket", mock.bucket, params.Bucket) - checkStringParam(mock.t, "key", mock.key, params.Key) - checkBodyParam(mock.t, mock.content, params.Body) - - return &s3.PutObjectOutput{}, nil -} - -func checkStringParam(t *testing.T, paramName, expected string, actual *string) { - t.Helper() - - if actual == nil { - t.Fatalf("expect %s to not be nil", paramName) - } - if expected != *actual { - t.Errorf("expect %v, got %v", expected, actual) - } -} - -func checkBodyParam(t *testing.T, expected []byte, body io.Reader) { - t.Helper() - - actual, err := io.ReadAll(body) - if err != nil { - t.Fatalf("failed to get data from body: %s", err) - } - if !bytes.Equal(expected, actual) { - t.Errorf("expect %v, got %v", expected, actual) - } -} - -func TestGet(t *testing.T) { - cases := []struct { - bucket string - key string - expect []byte - }{ - { - bucket: TestBucket, - key: TestKey, - expect: []byte(TestContent), - }, - } - - for i, tt := range cases { - t.Run(strconv.Itoa(i), func(t *testing.T) { - file, err := s3api.NewS3File(context.TODO(), NewMockS3Client(t, tt.bucket, tt.key, nil), TestBucket, TestRegion, TestKey) - if err != nil { - t.Fatalf("failed to create s3 file: %s", err) - } - - content, err := file.Get() - if err != nil { - t.Fatalf("failed to get content from s3 file: %s", err) - } - - if e, a := tt.expect, content; !bytes.Equal(e, a) { - t.Errorf("expect %v, got %v", e, a) - } - }) - } -} - -func TestPut(t *testing.T) { - cases := []struct { - bucket string - key string - content []byte - }{ - { - bucket: TestBucket, - key: TestKey, - content: []byte(TestContent), - }, - } - - for i, tt := range cases { - t.Run(strconv.Itoa(i), func(t *testing.T) { - file, err := s3api.NewS3File(context.TODO(), NewMockS3Client(t, tt.bucket, tt.key, tt.content), TestBucket, TestRegion, TestKey) - if err != nil { - t.Fatalf("failed to create s3 file: %s", err) - } - - err = file.Put([]byte(TestContent)) - if err != nil { - t.Fatalf("failed to get content from s3 file: %s", err) - } - }) - } -} diff --git a/pkg/util/cloud/aws/util/log.go b/pkg/util/cloud/aws/util/log.go deleted file mode 100644 index e2c60b445..000000000 --- a/pkg/util/cloud/aws/util/log.go +++ /dev/null @@ -1,10 +0,0 @@ -package util - -import "github.com/devstream-io/devstream/pkg/util/log" - -func LogAWSError(err error) { - if err == nil { - return - } - log.Errorf("AWS error: %s", err) -} diff --git a/pkg/util/docker/docker.go b/pkg/util/docker/docker.go deleted file mode 100644 index 89255727a..000000000 --- a/pkg/util/docker/docker.go +++ /dev/null @@ -1,67 +0,0 @@ -package docker - -import ( - "sort" - - mapset "github.com/deckarep/golang-set/v2" -) - -// Operator is an interface for docker operations -// It is implemented by shDockerOperator -// in the future, we can add other implementations such as SdkDockerOperator -type Operator interface { - ImageIfExist(imageNameWithTag string) bool - ImagePull(imageNameWithTag string) error - ImageRemove(imageNameWithTag string) error - - ContainerIfExist(containerName string) bool - ContainerIfRunning(containerName string) bool - // ContainerRun runs a container with the given options - // params is a list of additional parameters for docker run - // params will be appended to the end of the command - ContainerRun(opts *RunOptions) error - ContainerStop(containerName string) error - ContainerRemove(containerName string) error - - // ContainerListMounts lists container mounts - ContainerListMounts(containerName string) (Mounts, error) - - ContainerGetHostname(containerName string) (string, error) - ContainerListPortPublishes(containerName string) ([]PortPublish, error) - ContainerGetPortBinding(containerName string, containerPort uint) (hostPort uint, err error) - - ComposeUp() error - ComposeDown() error - ComposeState() (map[string]interface{}, error) -} - -type MountPoint struct { - Type string `json:"Type"` - Source string `json:"Source"` - Destination string `json:"Destination"` - Mode string `json:"Mode"` - Rw bool `json:"RW"` - Propagation string `json:"Propagation"` -} - -type Mounts []MountPoint - -// ExtractSources returns a list of sources for the given mounts -func (ms Mounts) ExtractSources() []string { - sources := make([]string, 0) - for _, mount := range ms { - sources = append(sources, mount.Source) - } - sort.Slice(sources, func(i, j int) bool { - return sources[i] < sources[j] - }) - - return sources -} - -func IfVolumesDiffer(volumesBefore, volumesCurrent []string) bool { - beforeSet := mapset.NewSet[string](volumesBefore...) - currentSet := mapset.NewSet[string](volumesCurrent...) - - return !beforeSet.Equal(currentSet) -} diff --git a/pkg/util/docker/docker_suite_test.go b/pkg/util/docker/docker_suite_test.go deleted file mode 100644 index aab992003..000000000 --- a/pkg/util/docker/docker_suite_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package docker - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestDocker(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Docker Suite") -} diff --git a/pkg/util/docker/dockersh/compose.go b/pkg/util/docker/dockersh/compose.go deleted file mode 100644 index 55b5c8696..000000000 --- a/pkg/util/docker/dockersh/compose.go +++ /dev/null @@ -1,28 +0,0 @@ -package dockersh - -import ( - "bytes" - "strings" -) - -func (op *ShellOperator) ComposeUp() error { - return ExecInSystemWithParams(".", []string{"docker", "compose", "up", "-d"}, nil, true) -} - -func (op *ShellOperator) ComposeDown() error { - return ExecInSystemWithParams(".", []string{"docker", "compose", "down", "-v"}, nil, true) -} - -func (op *ShellOperator) ComposeState() (map[string]interface{}, error) { - var buf = &bytes.Buffer{} - err := ExecInSystemWithParams(".", []string{"docker", "compose", "ls"}, buf, true) - if err != nil { - return nil, err - } - - bufStr := buf.String() - // TODO(daniel-hutao): enhancement is needed - return map[string]interface{}{ - "Running": strings.Contains(bufStr, "running"), - }, nil -} diff --git a/pkg/util/docker/dockersh/dockersh_suite_test.go b/pkg/util/docker/dockersh/dockersh_suite_test.go deleted file mode 100644 index e07468de5..000000000 --- a/pkg/util/docker/dockersh/dockersh_suite_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package dockersh - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestDockersh(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Dockersh Suite") -} diff --git a/pkg/util/docker/dockersh/exec.go b/pkg/util/docker/dockersh/exec.go deleted file mode 100644 index b5e5e1e27..000000000 --- a/pkg/util/docker/dockersh/exec.go +++ /dev/null @@ -1,88 +0,0 @@ -package dockersh - -import ( - "bufio" - "bytes" - "fmt" - "io" - "os/exec" - "strings" - "sync" - - "github.com/devstream-io/devstream/pkg/util/log" -) - -// ExecInSystemWithParams can exec a command with some params in system. -// All logs produced by command would be print to stdout and write into logsBuffer if it is not nil -func ExecInSystemWithParams(execPath string, params []string, logsBuffer *bytes.Buffer, print bool) error { - paramStr := strings.Join(params, " ") - return ExecInSystem(execPath, paramStr, logsBuffer, print) -} - -// ExecInSystem can exec a command with a full command in system. -// All logs produced by command would be print to stdout and write into logsBuffer if it is not nil -func ExecInSystem(execPath string, fullCommand string, logsBuffer *bytes.Buffer, print bool) error { - c := "-c" - cmdName := "sh" - - log.Infof("Cmd: %s.", fullCommand) - - cmd := exec.Command(cmdName, c, fullCommand) - cmd.Dir = execPath - - stdout, err := cmd.StdoutPipe() - if err != nil { - return err - } - - stderr, err := cmd.StderrPipe() - if err != nil { - return err - } - - // print logs - var lock sync.Mutex - outReader := bufio.NewReader(stdout) - errReader := bufio.NewReader(stderr) - - printLog := func(reader *bufio.Reader, stdType string) { - for { - line, err := reader.ReadString('\n') - - if err != nil || err == io.EOF { - break - } - - if print { - fmt.Printf("%s: %s", stdType, line) - } - - if logsBuffer != nil { - lock.Lock() - logsBuffer.WriteString(line) - lock.Unlock() - } - } - } - - var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() - printLog(outReader, "Stdout") - }() - - wg.Add(1) - go func() { - defer wg.Done() - printLog(errReader, "Stderr") - }() - - err = cmd.Start() - if err != nil { - return err - } - - wg.Wait() - return cmd.Wait() -} diff --git a/pkg/util/docker/dockersh/operator.go b/pkg/util/docker/dockersh/operator.go deleted file mode 100644 index 17ae68e90..000000000 --- a/pkg/util/docker/dockersh/operator.go +++ /dev/null @@ -1,248 +0,0 @@ -package dockersh - -import ( - "bytes" - "encoding/json" - "fmt" - "os/exec" - "regexp" - "strconv" - "strings" - - "github.com/devstream-io/devstream/pkg/util/docker" - "github.com/devstream-io/devstream/pkg/util/log" -) - -// ShellOperator is an implementation of /pkg/util/docker.Operator interface by using shell commands -type ShellOperator struct{} - -func (op *ShellOperator) ImageIfExist(imageNameWithTag string) bool { - // eg. docker image ls gitlab/gitlab-ce:rc -q - // output: image id (if exist) - cmdString := fmt.Sprintf("docker image ls %v -q", imageNameWithTag) - outputBuffer := &bytes.Buffer{} - err := ExecInSystem(".", cmdString, outputBuffer, false) - if err != nil { - return false - } - - return strings.TrimSpace(outputBuffer.String()) != "" -} - -func (op *ShellOperator) ImagePull(imageNameWithTag string) error { - err := ExecInSystemWithParams(".", []string{"docker", "pull", imageNameWithTag}, nil, true) - - return err -} - -func (op *ShellOperator) ImageRemove(imageNameWithTag string) error { - log.Infof("Removing image %v ...", imageNameWithTag) - - cmdString := fmt.Sprintf("docker rmi %s", imageNameWithTag) - err := ExecInSystem(".", cmdString, nil, true) - - return err -} - -func (op *ShellOperator) ContainerIfExist(containerName string) bool { - cmdString := fmt.Sprintf("docker inspect %s", containerName) - outputBuffer := &bytes.Buffer{} - err := ExecInSystem(".", cmdString, outputBuffer, false) - if err != nil { - return false - } - - if strings.Contains("No such object", outputBuffer.String()) { - return false - } - - return true -} - -func (op *ShellOperator) ContainerIfRunning(containerName string) bool { - command := exec.Command("docker", "inspect", "--format='{{.State.Status}}'", containerName) - output, err := command.Output() - if err != nil { - return false - } - - if strings.Contains(string(output), "running") { - return true - } - - return false -} - -func (op *ShellOperator) ContainerRun(opts *docker.RunOptions) error { - // build the command - cmdString, err := BuildContainerRunCommand(opts) - if err != nil { - return err - } - log.Debugf("Docker run command: %s", cmdString) - - // run the command - err = ExecInSystem(".", cmdString, nil, true) - if err != nil { - return fmt.Errorf("docker run failed: %v", err) - } - - // check if the container is started successfully - if ok := op.ContainerIfRunning(opts.ContainerName); !ok { - return fmt.Errorf("failed to run container") - } - - return nil -} - -// BuildContainerRunCommand builds the docker run command string from the given options and additional params -func BuildContainerRunCommand(opts *docker.RunOptions) (string, error) { - if err := opts.Validate(); err != nil { - return "", err - } - - cmdBuilder := strings.Builder{} - cmdBuilder.WriteString("docker run --detach ") - if opts.Hostname != "" { - cmdBuilder.WriteString(fmt.Sprintf("--hostname %s ", opts.Hostname)) - } - for _, publish := range opts.PortPublishes { - cmdBuilder.WriteString(fmt.Sprintf("--publish %d:%d ", publish.HostPort, publish.ContainerPort)) - } - cmdBuilder.WriteString(fmt.Sprintf("--name %s ", opts.ContainerName)) - if opts.RestartAlways { - cmdBuilder.WriteString("--restart always ") - } - for _, volume := range opts.Volumes { - cmdBuilder.WriteString(fmt.Sprintf("--volume %s:%s ", volume.HostPath, volume.ContainerPath)) - } - for _, param := range opts.RunParams { - cmdBuilder.WriteString(param + " ") - } - cmdBuilder.WriteString(docker.CombineImageNameAndTag(opts.ImageName, opts.ImageTag)) - - return cmdBuilder.String(), nil -} - -func (op *ShellOperator) ContainerStop(containerName string) error { - log.Infof("Stopping container %v ...", containerName) - - cmdString := fmt.Sprintf("docker stop %s", containerName) - err := ExecInSystem(".", cmdString, nil, true) - - return err -} - -func (op *ShellOperator) ContainerRemove(containerName string) error { - log.Infof("Removing container %v ...", containerName) - - cmdString := fmt.Sprintf("docker rm %s", containerName) - err := ExecInSystem(".", cmdString, nil, true) - - return err -} - -func (op *ShellOperator) ContainerListMounts(containerName string) (docker.Mounts, error) { - cmdString := fmt.Sprintf(`docker inspect --format='{{json .Mounts}}' %s`, containerName) - - outputBuffer := &bytes.Buffer{} - - err := ExecInSystem(".", cmdString, outputBuffer, false) - if err != nil { - return nil, err - } - - mounts := make([]docker.MountPoint, 0) - err = json.Unmarshal([]byte(strings.TrimSpace(outputBuffer.String())), &mounts) - if err != nil { - return nil, fmt.Errorf("failed to unmarshal docker inspect output when list mounts: %v", err) - } - - log.Debugf("Container %v mounts: %v", containerName, mounts) - - return mounts, nil -} - -func (op *ShellOperator) ContainerGetHostname(container string) (string, error) { - cmdString := fmt.Sprintf("docker inspect --format='{{.Config.Hostname}}' %s", container) - outputBuffer := &bytes.Buffer{} - - err := ExecInSystem(".", cmdString, outputBuffer, false) - if err != nil { - return "", err - } - - return strings.TrimSpace(strings.TrimSpace(outputBuffer.String())), nil - -} - -func (op *ShellOperator) ContainerListPortPublishes(containerName string) ([]docker.PortPublish, error) { - // get container port binding map - // the result is like: - // 22/tcp->8122 - // 443/tcp->8443 - // 80/tcp->8180 - format := "'{{range $p,$conf := .NetworkSettings.Ports}}{{$p}}->{{(index $conf 0).HostPort}}{{println}}{{end}}'" - cmdString := fmt.Sprintf("docker inspect --format=%s %s", format, containerName) - outputBuffer := &bytes.Buffer{} - err := ExecInSystem(".", cmdString, outputBuffer, false) - if err != nil { - return nil, err - } - portBindings := strings.Split(strings.TrimSpace(outputBuffer.String()), "\n") - log.Debugf("Container %v port bindings: %v", containerName, portBindings) - - publishes, err := buildPortPublishes(portBindings) - if err != nil { - return publishes, err - } - - return publishes, nil -} - -func buildPortPublishes(portBindings []string) (PortPublishes []docker.PortPublish, err error) { - // 22/tcp->8122 - // 443/tcp->8443 - // 80/tcp->8180 - re := regexp.MustCompile(`^(\d+)/(tcp|udp)->(\d+)$`) - - for _, portBinding := range portBindings { - match := re.FindStringSubmatch(portBinding) - // match e.g. ["22/tcp->8122", "22", "tcp", "8122"] - if len(match) != 4 { - return nil, fmt.Errorf("invalid port binding: %v", portBinding) - } - - hostPort, err := strconv.Atoi(match[3]) - if err != nil { - return nil, fmt.Errorf("invalid port binding: %v", portBinding) - } - containerPort, err := strconv.Atoi(match[1]) - if err != nil { - return nil, fmt.Errorf("invalid port binding: %v", portBinding) - } - - portPublish := docker.PortPublish{ - ContainerPort: uint(containerPort), - HostPort: uint(hostPort), - } - PortPublishes = append(PortPublishes, portPublish) - } - - return PortPublishes, nil -} - -func (op *ShellOperator) ContainerGetPortBinding(container string, containerPort uint) (hostPort uint, err error) { - portBindings, err := op.ContainerListPortPublishes(container) - if err != nil { - return 0, err - } - - for _, portBinding := range portBindings { - if portBinding.ContainerPort == containerPort { - return portBinding.HostPort, nil - } - } - - return 0, fmt.Errorf("container %v does not have port binding for port %v", container, containerPort) -} diff --git a/pkg/util/docker/dockersh/operator_test.go b/pkg/util/docker/dockersh/operator_test.go deleted file mode 100644 index f3af9005f..000000000 --- a/pkg/util/docker/dockersh/operator_test.go +++ /dev/null @@ -1,97 +0,0 @@ -package dockersh - -import ( - "path/filepath" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/pkg/util/docker" -) - -var _ = Describe("BuildContainerRunCommand method", func() { - var opts *docker.RunOptions - - When(" the options are invalid", func() { - BeforeEach(func() { - opts = &docker.RunOptions{} - }) - - It("should return an error", func() { - _, err := BuildContainerRunCommand(opts) - Expect(err).To(HaveOccurred()) - }) - }) - - When(" the options are valid(e.g. gitlab-ce)", func() { - BeforeEach(func() { - buildOpts := func() *docker.RunOptions { - opts := &docker.RunOptions{} - opts.ImageName = "gitlab/gitlab-ce" - opts.ImageTag = "rc" - opts.Hostname = "gitlab.example.com" - opts.ContainerName = "gitlab" - opts.RestartAlways = true - - portPublishes := []docker.PortPublish{ - {HostPort: 8122, ContainerPort: 22}, - {HostPort: 8180, ContainerPort: 80}, - {HostPort: 8443, ContainerPort: 443}, - } - opts.PortPublishes = portPublishes - - gitLabHome := "/srv/gitlab" - - opts.Volumes = []docker.Volume{ - {HostPath: filepath.Join(gitLabHome, "config"), ContainerPath: "/etc/gitlab"}, - {HostPath: filepath.Join(gitLabHome, "data"), ContainerPath: "/var/opt/gitlab"}, - {HostPath: filepath.Join(gitLabHome, "logs"), ContainerPath: "/var/log/gitlab"}, - } - - opts.RunParams = []string{"--shm-size 256m"} - - return opts - } - - opts = buildOpts() - }) - - It("should return the correct command", func() { - cmdBuild, err := BuildContainerRunCommand(opts) - Expect(err).NotTo(HaveOccurred()) - cmdExpect := "docker run --detach --hostname gitlab.example.com" + - " --publish 8122:22 --publish 8180:80 --publish 8443:443" + - " --name gitlab --restart always" + - " --volume /srv/gitlab/config:/etc/gitlab" + - " --volume /srv/gitlab/data:/var/opt/gitlab" + - " --volume /srv/gitlab/logs:/var/log/gitlab" + - " --shm-size 256m gitlab/gitlab-ce:rc" - Expect(cmdBuild).To(Equal(cmdExpect)) - }) - - }) -}) - -var _ = Describe("build[] PortPublish func ", func() { - var portBindings []string - - BeforeEach(func() { - portBindings = []string{ - "22/tcp->8122", - "443/tcp->8443", - "80/tcp->8180", - } - }) - - When(" the options are valid", func() { - It("should return the correct port publishes", func() { - publishes, err := buildPortPublishes(portBindings) - Expect(err).NotTo(HaveOccurred()) - Expect(publishes).To(Equal([]docker.PortPublish{ - {HostPort: 8122, ContainerPort: 22}, - {HostPort: 8443, ContainerPort: 443}, - {HostPort: 8180, ContainerPort: 80}, - })) - }) - }) -}) diff --git a/pkg/util/docker/option.go b/pkg/util/docker/option.go deleted file mode 100644 index 4b4a8d8fb..000000000 --- a/pkg/util/docker/option.go +++ /dev/null @@ -1,68 +0,0 @@ -package docker - -import ( - "fmt" - "strings" - - "go.uber.org/multierr" -) - -// RunOptions is used to pass options to ContainerRunWithOptions -type ( - RunOptions struct { - ImageName string - ImageTag string - Hostname string - ContainerName string - PortPublishes []PortPublish - Volumes Volumes - RestartAlways bool - RunParams []string - } - - Volume struct { - HostPath string - ContainerPath string - } - Volumes []Volume - - PortPublish struct { - HostPort uint - ContainerPort uint - } -) - -func (opts *RunOptions) Validate() error { - var errs []error - if strings.TrimSpace(opts.ImageName) == "" { - errs = append(errs, fmt.Errorf("image name is required")) - } - if strings.TrimSpace(opts.ImageTag) == "" { - errs = append(errs, fmt.Errorf("image tag is required")) - } - if strings.TrimSpace(opts.ContainerName) == "" { - errs = append(errs, fmt.Errorf("container name is required")) - } - for _, volume := range opts.Volumes { - if volume.HostPath == "" { - errs = append(errs, fmt.Errorf("HostPath can not be empty")) - } - if volume.ContainerPath == "" { - errs = append(errs, fmt.Errorf("ContainerPath can not be empty")) - } - } - - return multierr.Combine(errs...) -} - -func CombineImageNameAndTag(imageName, tag string) string { - return imageName + ":" + tag -} - -func (volumes Volumes) ExtractHostPaths() []string { - hostPaths := make([]string, len(volumes)) - for i, volume := range volumes { - hostPaths[i] = volume.HostPath - } - return hostPaths -} diff --git a/pkg/util/docker/option_test.go b/pkg/util/docker/option_test.go deleted file mode 100644 index c9fc5f43d..000000000 --- a/pkg/util/docker/option_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package docker - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -var _ = Describe("Option", func() { - Describe("IfVolumesDiffer func", func() { - var ( - volumes1 = []string{"/srv/gitlab/config", "/srv/gitlab/data", "/srv/gitlab/logs"} - Volumes1ChangeOrder = []string{"/srv/gitlab/data", "/srv/gitlab/logs", "/srv/gitlab/config"} - volumes1Missing = []string{"/srv/gitlab/data", "/srv/gitlab/logs"} - volumes2 = []string{"totally/different/path"} - ) - - var ( - volumesSrc, volumesDest []string - differ bool - ) - - JustBeforeEach(func() { - differ = IfVolumesDiffer(volumesSrc, volumesDest) - }) - - When("the volumes are the same but the order is changed", func() { - BeforeEach(func() { - volumesSrc = volumes1 - volumesDest = Volumes1ChangeOrder - }) - - It("should return false", func() { - Expect(differ).To(BeFalse()) - }) - }) - - When("the volumes are different(missing)", func() { - BeforeEach(func() { - volumesSrc = volumes1 - volumesDest = volumes1Missing - }) - - It("should return true", func() { - Expect(differ).To(BeTrue()) - }) - }) - - When("the volumes are totally different", func() { - BeforeEach(func() { - volumesSrc = volumes1 - volumesDest = volumes2 - }) - - It("should return true", func() { - Expect(differ).To(BeTrue()) - }) - }) - }) -}) diff --git a/pkg/util/downloader/downloader.go b/pkg/util/downloader/downloader.go deleted file mode 100644 index be01d601c..000000000 --- a/pkg/util/downloader/downloader.go +++ /dev/null @@ -1,184 +0,0 @@ -package downloader - -import ( - "fmt" - "io" - "net/http" - "os" - "path/filepath" - "strconv" - "time" - - "github.com/cheggaaa/pb" - - "github.com/devstream-io/devstream/pkg/util/file" - "github.com/devstream-io/devstream/pkg/util/log" -) - -type Downloader struct { - EnableProgressBar bool - client *http.Client -} - -func New() *Downloader { - return &Downloader{ - client: &http.Client{}, - } -} - -func (d *Downloader) WithProgressBar() *Downloader { - d.EnableProgressBar = true - return d -} - -func (d *Downloader) WithClient(client *http.Client) *Downloader { - d.client = client - return d -} - -// Download a file from the URL to the target path -// if filename is "", use the remote filename at local. -func (d *Downloader) Download(url, filename, targetDir string) (size int64, err error) { - // handle filename and target dir - filename, err = parseFilenameAndCreateTargetDir(url, filename, targetDir) - if err != nil { - return 0, err - } - - // get http response - resp, err := d.getHttpResponse(url) - if err != nil { - return 0, err - } - defer func(body io.ReadCloser) { - err := body.Close() - if err != nil { - log.Errorf("Close response body failed: %s", err) - } - }(resp.Body) - - pluginTmpLocation := filepath.Join(targetDir, filename+".tmp") - pluginLocation := filepath.Join(targetDir, filename) - - // download to tmp file - log.Infof("Downloading: [%s] ...", filename) - downFile, err := os.Create(pluginTmpLocation) - if err != nil { - return 0, err - } - defer func() { - err := downFile.Close() - if err != nil { - log.Debugf("download create file failed: %s", err) - } - err = file.RemoveFileIfExists(pluginTmpLocation) - if err != nil { - log.Debugf("download create file failed: %s", err) - } - }() - - if d.EnableProgressBar { - // create progress bar when reading response body - size, err = SetUpProgressBar(resp, downFile) - } else { - // just copy response body to file - size, err = io.Copy(downFile, resp.Body) - } - - if err != nil { - return 0, err - } - - // rename, tmp file to real file - if err = os.Rename(pluginTmpLocation, pluginLocation); err != nil { - return 0, err - } - return size, nil -} - -func parseFilenameAndCreateTargetDir(url, filename, targetDir string) (finalFilename string, err error) { - log.Debugf("Downloading url is: %s.", url) - log.Debugf("Target dir: %s.", targetDir) - if url == "" { - return "", fmt.Errorf("url must not be empty: %s", url) - } - // get filename from local or remote - if filename != "" { - // use local filename - // when filename is "/", ".", "..", the filepath.Base will return "/", ".", ".." - finalFilename = filepath.Base(filename) - if finalFilename == "/" || finalFilename == "." || finalFilename == ".." { - return "", fmt.Errorf("filename must not be dir") - } - } else { - // use remote filename - // when url is empty filepath.Base(url) will return "." - finalFilename = filepath.Base(url) - if finalFilename == "." { - return "", fmt.Errorf("failed to get the filename from url: %s", url) - } - } - - if err := os.MkdirAll(targetDir, 0755); err != nil { - return "", err - } - - return finalFilename, nil -} - -func (d *Downloader) getHttpResponse(url string) (*http.Response, error) { - resp, err := d.client.Get(url) - - // check response error - if err != nil { - log.Debugf("Download from url failed: %s", err) - return nil, err - } - - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("Download file from url failed: %+v", resp) - } - - return resp, nil -} - -// setUpProgressBar create bar and setup -func SetUpProgressBar(resp *http.Response, downFile io.Writer) (int64, error) { - //get size - i, _ := strconv.Atoi(resp.Header.Get("Content-Length")) - sourceSiz := int64(i) - source := resp.Body - - //create a bar and set param - bar := pb.New(int(sourceSiz)).SetRefreshRate(time.Millisecond * 10).SetUnits(pb.U_BYTES).SetWidth(100) - bar.ShowSpeed = true - bar.ShowTimeLeft = true - bar.ShowFinalTime = true - bar.SetWidth(80) - bar.Start() - defer bar.Finish() - writer := io.MultiWriter(downFile, bar) - return io.Copy(writer, source) -} - -func FetchContentFromURL(url string) ([]byte, error) { - resp, err := http.Get(url) - - // check response error - if err != nil { - log.Debugf("Download from url failed: %s", err) - return nil, err - } - defer func(body io.ReadCloser) { - err := body.Close() - if err != nil { - log.Errorf("Close response body failed: %s", err) - } - }(resp.Body) - - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("Download file from url failed: %+v", resp) - } - - return io.ReadAll(resp.Body) -} diff --git a/pkg/util/downloader/downloader_suite_test.go b/pkg/util/downloader/downloader_suite_test.go deleted file mode 100644 index b91c0873a..000000000 --- a/pkg/util/downloader/downloader_suite_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package downloader_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestPlanmanager(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Downloader Suite") -} diff --git a/pkg/util/downloader/downloader_test.go b/pkg/util/downloader/downloader_test.go deleted file mode 100644 index 2beac380c..000000000 --- a/pkg/util/downloader/downloader_test.go +++ /dev/null @@ -1,154 +0,0 @@ -package downloader_test - -import ( - "fmt" - "net/http" - "os" - "path" - "path/filepath" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "github.com/onsi/gomega/ghttp" - - "github.com/devstream-io/devstream/pkg/util/downloader" -) - -var _ = Describe("Downloader", func() { - var ( - s *ghttp.Server - reqPath, failReqPath, tempDir, url string - testContent []byte - ) - BeforeEach(func() { - tempDir = GinkgoT().TempDir() - reqPath = "/test_plugin.so" - failReqPath = "/not_exist.so" - testContent = []byte("test Content") - s = ghttp.NewServer() - url = fmt.Sprintf("%s%s", s.URL(), reqPath) - }) - AfterEach(func() { - s.Close() - }) - Context("Downloader test", func() { - When("input params is wrong", func() { - It("returns an error when url is empty", func() { - size, err := downloader.New().Download("", ".", tempDir) - Expect(err).To(HaveOccurred()) - Expect(size).To(Equal(int64(0))) - }) - - It("returns an error when filename is [.]", func() { - size, err := downloader.New().Download(url, ".", tempDir) - Expect(err).To(HaveOccurred()) - Expect(size).To(Equal(int64(0))) - }) - - It("returns an error when filename is dir", func() { - size, err := downloader.New().Download(url, "/", tempDir) - Expect(err).To(HaveOccurred()) - Expect(size).To(Equal(int64(0))) - }) - - It("returns an error when the targetDir is empty", func() { - size, err := downloader.New().Download(url, "download.txt", "") - Expect(err).To(HaveOccurred()) - Expect(size).To(Equal(int64(0))) - }) - }) - When("remote request error", func() { - BeforeEach(func() { - s.RouteToHandler("GET", failReqPath, ghttp.CombineHandlers( - ghttp.VerifyRequest("GET", failReqPath), - ghttp.RespondWithJSONEncoded(http.StatusNotFound, nil), - )) - - }) - It("returns an error when the url is not right", func() { - errorURL := path.Join(s.URL(), failReqPath) - size, err := downloader.New().Download(errorURL, "download.txt", tempDir) - Expect(err).To(HaveOccurred()) - Expect(size).To(Equal(int64(0))) - }) - }) - When("remote success", func() { - BeforeEach(func() { - s.RouteToHandler("GET", reqPath, ghttp.CombineHandlers( - ghttp.VerifyRequest("GET", reqPath), - ghttp.RespondWith(http.StatusOK, string(testContent)), - )) - }) - When("filename is empty", func() { - It("should returns an error ", func() { - size, err := downloader.New().Download(url, "", tempDir) - Expect(err).NotTo(HaveOccurred()) - Expect(size).NotTo(Equal(int64(0))) - }) - }) - When("fileName is right", func() { - It("should get fileName with content", func() { - fileName := "testFile" - size, err := downloader.New().Download(url, fileName, tempDir) - Expect(err).NotTo(HaveOccurred()) - Expect(size).NotTo(Equal(int64(0))) - fileContent, err := os.ReadFile(filepath.Join(tempDir, fileName)) - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(fileContent).Should(Equal(testContent)) - }) - }) - }) - }) -}) - -var _ = Describe("FetchContentFromURL func", func() { - var ( - server *ghttp.Server - testPath, remoteContent string - ) - - BeforeEach(func() { - testPath = "/testPath" - server = ghttp.NewServer() - }) - - When("server return error code", func() { - BeforeEach(func() { - server.AppendHandlers( - ghttp.CombineHandlers( - ghttp.VerifyRequest("GET", testPath), - ghttp.RespondWith(http.StatusNotFound, ""), - ), - ) - - }) - It("should return err", func() { - reqURL := fmt.Sprintf("%s%s", server.URL(), testPath) - _, err := downloader.FetchContentFromURL(reqURL) - Expect(err).Error().Should(HaveOccurred()) - }) - }) - - When("server return success", func() { - BeforeEach(func() { - remoteContent = "download content" - server.AppendHandlers( - ghttp.CombineHandlers( - ghttp.VerifyRequest("GET", testPath), - ghttp.RespondWith(http.StatusOK, remoteContent), - ), - ) - }) - - It("download the correct content", func() { - reqURL := fmt.Sprintf("%s%s", server.URL(), testPath) - content, err := downloader.FetchContentFromURL(reqURL) - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(string(content)).Should(Equal(remoteContent)) - }) - }) - - AfterEach(func() { - server.Close() - }) -}) diff --git a/pkg/util/downloader/resource.go b/pkg/util/downloader/resource.go deleted file mode 100644 index b91e85808..000000000 --- a/pkg/util/downloader/resource.go +++ /dev/null @@ -1,156 +0,0 @@ -package downloader - -import ( - "fmt" - "os" - "strings" - - "github.com/devstream-io/devstream/pkg/util/file" - "github.com/devstream-io/devstream/pkg/util/log" - - "github.com/hashicorp/go-cleanhttp" - getter "github.com/hashicorp/go-getter" -) - -type ( - // ResourceLocation represent location of resource, url/localPath/gitPath for example - ResourceLocation string - // resourceCache is used to cache ResourceLocation/tempResourcePath map - resourceCache map[ResourceLocation]string -) - -var ( - // locationCache is used to cache resource - locationCache = resourceCache{} -) - -// config detectors|decompressors|getters for resource getter -func (l ResourceLocation) Download() (string, error) { - // 1. check if has cache for ResourceLocation - cachedLocation, exist := locationCache[l] - if exist { - return cachedLocation, nil - } - log.Debugf("start to get resource files [%s]...", l) - - // 2. get template Path where download resource - resouceClient, err := newResourceClient(l.formatResourceSource()) - if err != nil { - log.Debugf("create resourceClient failed: %+v", err) - } - if resouceClient.checkResourceExist() { - return resouceClient.Src, nil - } - tempResourcePath, err := resouceClient.download() - if err != nil { - return "", fmt.Errorf("get resource files %s failed: %w", l, err) - } - - // 3. set cache for ResourceLocation - locationCache[l] = tempResourcePath - return tempResourcePath, nil -} - -// for github repo, go-getter only support github.com/owner/repo fomat -func (l ResourceLocation) formatResourceSource() string { - const githubReplacePrefix = "https://github.com" - locationString := string(l) - if strings.HasPrefix(locationString, githubReplacePrefix) { - return strings.Replace(locationString, "https://", "", 1) - } - return locationString -} - -// resouceClient is used to download resource -type resouceClient struct { - *getter.Client -} - -// downloadToDst will download any resource from src location into dst location -func (c *resouceClient) download() (string, error) { - // 1. create template dir by pattern - const tempDirNamePattern = "download_getter_" - dst, err := file.CreateTempDir(tempDirNamePattern) - if err != nil { - log.Debugf("download getter: get destination failed: %+v", err) - } - - // 2. download resource to tempDir - log.Debugf("download getter: start fetching %s to %s", c.Src, dst) - c.setDst(dst) - err = c.Get() - if err != nil { - log.Debugf("download getter: fetch resource failed: %+v", err) - return "", err - } - log.Debugf("download getter: fetch %s to %s success", c.Src, dst) - return dst, nil -} - -func newResourceClient(source string) (*resouceClient, error) { - // 1. config detectors/decompressors/getters for client - var ( - // getterHTTPGetter is used to get resource from http - getterHTTPGetter = &getter.HttpGetter{ - Client: cleanhttp.DefaultClient(), - Netrc: true, - } - // detect the type of source to download - goGetterDetectors = []getter.Detector{ - new(getter.GitHubDetector), - new(getter.GitLabDetector), - new(getter.GitDetector), - new(getter.FileDetector), - new(getter.BitBucketDetector), - } - // decompressors used when encounter compressed file - goGetterDecompressors = map[string]getter.Decompressor{ - "gz": new(getter.GzipDecompressor), - "zip": new(getter.ZipDecompressor), - "tar.gz": new(getter.TarGzipDecompressor), - "tar.xz": new(getter.TarXzDecompressor), - } - // these func is used to get resource - goGetterGetters = map[string]getter.Getter{ - "file": new(getter.FileGetter), - "git": new(getter.GitGetter), - "http": getterHTTPGetter, - "https": getterHTTPGetter, - } - ) - // 2. get current work dir - workDir, err := os.Getwd() - if err != nil { - log.Debugf("download getter: get pwd failed: %+v", err) - return nil, err - } - return &resouceClient{ - &getter.Client{ - Pwd: workDir, - Detectors: goGetterDetectors, - Decompressors: goGetterDecompressors, - Getters: goGetterGetters, - Insecure: true, - DisableSymlinks: true, - Mode: getter.ClientModeAny, - Src: source, - }, - }, nil -} - -// check source is local files -// if source is just local file or directory, just return source -func (c *resouceClient) checkResourceExist() bool { - // get full address of resourceAddress - fullAddress, err := getter.Detect(c.Src, c.Pwd, c.Detectors) - if err != nil { - log.Debugf("download getter: detect source failed: %+v", err) - return false - } - log.Debugf("download getter: get %s => %s", c.Src, fullAddress) - return strings.HasPrefix(fullAddress, "file://") -} - -func (c *resouceClient) setDst(dst string) { - c.Client.Dst = dst -} diff --git a/pkg/util/downloader/resource_test.go b/pkg/util/downloader/resource_test.go deleted file mode 100644 index b710b1a61..000000000 --- a/pkg/util/downloader/resource_test.go +++ /dev/null @@ -1,92 +0,0 @@ -package downloader_test - -import ( - "fmt" - "net/http" - "os" - "path" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/onsi/gomega/ghttp" - - "github.com/devstream-io/devstream/pkg/util/downloader" -) - -var _ = Describe("ResourceClient struct", func() { - var ( - source string - resourceLocation downloader.ResourceLocation - ) - Context("GetWithGoGetter method", func() { - When("source is local files or directory", func() { - BeforeEach(func() { - source = GinkgoT().TempDir() - resourceLocation = downloader.ResourceLocation(source) - }) - It("should return local source path directly", func() { - dstPath, err := resourceLocation.Download() - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(dstPath).Should(Equal(source)) - }) - }) - When("download resource from web", func() { - var ( - s *ghttp.Server - reqPath string - ) - BeforeEach(func() { - s = ghttp.NewServer() - }) - When("resource return error", func() { - BeforeEach(func() { - reqPath = path.Join(s.URL(), "not_exist_resource") - resourceLocation = downloader.ResourceLocation(reqPath) - }) - It("should return err", func() { - _, err := resourceLocation.Download() - Expect(err).Error().Should(HaveOccurred()) - Expect(err.Error()).Should(ContainSubstring(fmt.Sprintf("get resource files %s failed", reqPath))) - }) - }) - When("resource return normal", func() { - var ( - returnContent string - ) - BeforeEach(func() { - returnContent = "test getter" - pathName := "/exist_resource" - reqPath = fmt.Sprintf("%s%s", s.URL(), pathName) - - s.RouteToHandler("HEAD", pathName, func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, "ok") - }) - s.RouteToHandler("GET", pathName, ghttp.CombineHandlers( - ghttp.VerifyRequest("GET", pathName), - ghttp.RespondWith(http.StatusOK, returnContent), - )) - resourceLocation = downloader.ResourceLocation(reqPath) - }) - When("destination is not setted", func() { - It("should create temp dir and download file", func() { - dstPath, err := resourceLocation.Download() - Expect(err).Error().ShouldNot(HaveOccurred()) - files, err := os.ReadDir(dstPath) - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(len(files)).Should(Equal(1)) - file := files[0] - Expect(file.Name()).Should(Equal("exist_resource")) - filePath := path.Join(dstPath, file.Name()) - content, err := os.ReadFile(filePath) - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(string(content)).Should(Equal(returnContent)) - }) - }) - }) - AfterEach(func() { - s.Close() - }) - }) - }) -}) diff --git a/pkg/util/file/dir.go b/pkg/util/file/dir.go deleted file mode 100644 index 26e8f20c8..000000000 --- a/pkg/util/file/dir.go +++ /dev/null @@ -1,94 +0,0 @@ -package file - -import ( - "io/fs" - "os" - "path/filepath" - - "github.com/devstream-io/devstream/pkg/util/log" -) - -// DirFileFilterFunc is used to filter files when walk directory -// if this func return false, this file's content will not return -type DirFileFilterFunc func(filePath string, isDir bool) bool - -// DirFileContentFunc is used to get file content then return this content -type DirFileContentFunc func(filePath string) ([]byte, error) - -// DirFileNameFunc is used to make current filename to become map key -// srcPath is the basePath of file, for sometimes you may want to get relativePath of filePath -type DirFileNameFunc func(filePath, srcPath string) string - -// GetFileMapByWalkDir will walk in directory return contentMap -func GetFileMapByWalkDir( - srcPath string, filterFunc DirFileFilterFunc, fileNameFunc DirFileNameFunc, processFunc DirFileContentFunc, -) (map[string][]byte, error) { - contentMap := make(map[string][]byte) - if err := filepath.Walk(srcPath, func(path string, info fs.FileInfo, err error) error { - if err != nil { - log.Debugf("Walk error: %s.", err) - return err - } - if !filterFunc(path, info.IsDir()) { - log.Debugf("Walk file is filtered: %s.", path) - return nil - } - - // if file ends-with tpl, render this file, else copy this file directly - dstFileName := fileNameFunc(path, srcPath) - // if process file failed, return error - content, err := processFunc(path) - if err != nil { - return err - } - contentMap[dstFileName] = content - return nil - }); err != nil { - log.Debugf("Walk Dir %s failed: %+v", srcPath, err) - return nil, err - } - return contentMap, nil -} - -// GetFileMap return map of fileName and content -// if srcPath is a directory, it will invoke GetFileMapByWalkDir to get content map -// if srcPath is a file, it will use fileNameFunc and fileContentFunc to create a map -func GetFileMap( - srcPath string, filterFunc DirFileFilterFunc, fileNameFunc DirFileNameFunc, fileContentFunc DirFileContentFunc, -) (map[string][]byte, error) { - pathInfo, err := os.Stat(srcPath) - if err != nil { - log.Debugf("dir: get path info failed: %+v", err) - return nil, err - } - if pathInfo.IsDir() { - return GetFileMapByWalkDir( - srcPath, filterFunc, - fileNameFunc, fileContentFunc, - ) - } - content, err := fileContentFunc(srcPath) - if err != nil { - log.Debugf("dir: process file content failed: %+v", err) - return nil, err - } - fileName := fileNameFunc(srcPath, filepath.Dir(srcPath)) - return map[string][]byte{ - fileName: []byte(content), - }, nil -} - -func CreateTempDir(dirPattern string) (string, error) { - tempDir, err := os.MkdirTemp("", dirPattern) - if err != nil { - log.Debugf("create tempDir %s failed: %+v", dirPattern, err) - return "", err - } - return tempDir, err -} - -// DirFileFilterDefaultFunc is used for GetFileMap -// it will return false if isDir is true -func DirFileFilterDefaultFunc(filePath string, isDir bool) bool { - return !isDir -} diff --git a/pkg/util/file/dir_test.go b/pkg/util/file/dir_test.go deleted file mode 100644 index 44fd6a523..000000000 --- a/pkg/util/file/dir_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package file_test - -import ( - "errors" - "os" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/pkg/util/file" -) - -var _ = Describe("GetFileMapByWalkDir func", func() { - var ( - tempDir, exepectKey string - testContent []byte - mockFilterSuccessFunc file.DirFileFilterFunc - mockFilterFailedFunc file.DirFileFilterFunc - mockGetFileNameFunc file.DirFileNameFunc - mockProcessFailedFunc file.DirFileContentFunc - mockProcessSuccessFunc file.DirFileContentFunc - ) - BeforeEach(func() { - // create temp file for test - tempDir = GinkgoT().TempDir() - testContent = []byte("test content") - exepectKey = "exepectKey" - tempFile, err := os.CreateTemp(tempDir, "testFile") - Expect(err).Error().ShouldNot(HaveOccurred()) - err = os.WriteFile(tempFile.Name(), testContent, 0755) - Expect(err).Error().ShouldNot(HaveOccurred()) - // mock func to run - mockFilterFailedFunc = func(filePath string, isDir bool) bool { - return false - } - mockFilterSuccessFunc = func(filePath string, isDir bool) bool { - return true - } - mockGetFileNameFunc = func(workDir, filePath string) string { - return exepectKey - } - mockProcessFailedFunc = func(filePath string) ([]byte, error) { - return []byte{}, errors.New("test err") - } - mockProcessSuccessFunc = func(filePath string) ([]byte, error) { - return testContent, nil - } - - }) - When("dir not exist", func() { - It("should return error", func() { - _, err := file.GetFileMapByWalkDir("not_exist_dir", mockFilterSuccessFunc, mockGetFileNameFunc, mockProcessSuccessFunc) - Expect(err).Error().Should(HaveOccurred()) - }) - }) - When("filter return false", func() { - It("should return empty", func() { - result, err := file.GetFileMapByWalkDir(tempDir, mockFilterFailedFunc, mockGetFileNameFunc, mockProcessSuccessFunc) - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(len(result)).Should(Equal(0)) - }) - }) - When("processFunc return false", func() { - It("should return empty", func() { - _, err := file.GetFileMapByWalkDir(tempDir, mockFilterSuccessFunc, mockGetFileNameFunc, mockProcessFailedFunc) - Expect(err).Error().Should(HaveOccurred()) - }) - }) - When("all func work normal", func() { - It("should return map with fileName and content", func() { - result, err := file.GetFileMapByWalkDir(tempDir, mockFilterSuccessFunc, mockGetFileNameFunc, mockProcessSuccessFunc) - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(len(result)).Should(Equal(1)) - val, ok := result[exepectKey] - Expect(ok).Should(BeTrue()) - Expect(val).Should(Equal(testContent)) - }) - }) -}) diff --git a/pkg/util/file/file.go b/pkg/util/file/file.go deleted file mode 100644 index c20edaa97..000000000 --- a/pkg/util/file/file.go +++ /dev/null @@ -1,100 +0,0 @@ -package file - -import ( - "fmt" - "io" - "os" - "path/filepath" - - "github.com/devstream-io/devstream/pkg/util/log" -) - -func CopyFile(srcFile, dstFile string) (err error) { - // prepare source file - sFile, err := os.Open(srcFile) - if err != nil { - return err - } - defer func() { - if closeErr := sFile.Close(); closeErr != nil { - log.Errorf("Failed to close file %s: %s", srcFile, closeErr) - if err == nil { - err = closeErr - } - } - }() - - // create destination file - dFile, err := os.Create(dstFile) - if err != nil { - return err - } - defer func() { - if closeErr := dFile.Close(); closeErr != nil { - log.Errorf("Failed to close file %s: %s", dstFile, closeErr) - if err == nil { - err = closeErr - } - } - }() - - // copy and sync - if _, err = io.Copy(dFile, sFile); err != nil { - return nil - } - return dFile.Sync() -} - -// GenerateAbsFilePath return all the path with a given file name -func GenerateAbsFilePath(baseDir, file string) (string, error) { - file = filepath.Join(baseDir, file) - - fileExist := func(path string) bool { - if _, err := os.Stat(file); err != nil { - log.Errorf("File %s not exists. Error: %s", file, err) - return false - } - return true - } - - absFilePath, err := filepath.Abs(file) - if err != nil { - log.Errorf(`Failed to get absolute path fo "%s".`, file) - return "", err - } - log.Debugf("Abs path is %s.", absFilePath) - if fileExist(absFilePath) { - return absFilePath, nil - } else { - return "", fmt.Errorf("file %s not exists", absFilePath) - } -} - -// GetFileAbsDirPath will return abs dir path for file -func GetFileAbsDirPath(fileLoc string) (string, error) { - fileAbs, err := filepath.Abs(fileLoc) - if err != nil { - return "", fmt.Errorf("%s not exists", fileLoc) - } - return filepath.Dir(fileAbs), nil -} - -// GetFileAbsDirPathOrDirItself will return abs dir path, -// for file: return its parent directory -// for directory: return dir itself -func GetFileAbsDirPathOrDirItself(fileLoc string) (string, error) { - fileAbs, err := filepath.Abs(fileLoc) - if err != nil { - return "", fmt.Errorf("%s not exists", fileLoc) - } - file, err := os.Stat(fileAbs) - if err != nil { - return "", fmt.Errorf("%s not exists", fileLoc) - } - // if it is a directory, return itself - if file.IsDir() { - return fileAbs, nil - } - // if it is a file, return its parent directory - return filepath.Dir(fileAbs), nil -} diff --git a/pkg/util/file/file_suite_test.go b/pkg/util/file/file_suite_test.go deleted file mode 100644 index ac2674664..000000000 --- a/pkg/util/file/file_suite_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package file_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestPlanmanager(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Util File Suite") -} diff --git a/pkg/util/file/file_test.go b/pkg/util/file/file_test.go deleted file mode 100644 index 06edaa790..000000000 --- a/pkg/util/file/file_test.go +++ /dev/null @@ -1,112 +0,0 @@ -package file_test - -import ( - "os" - "path/filepath" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/pkg/util/file" -) - -var _ = Describe("CopyFile func", func() { - var ( - tempDir, srcPath, dstPath string - testContent []byte - ) - - BeforeEach(func() { - testContent = []byte("test_content") - tempDir = GinkgoT().TempDir() - srcPath = filepath.Join(tempDir, "src") - dstPath = filepath.Join(tempDir, "dst") - f1, err := os.Create(srcPath) - Expect(err).Error().ShouldNot(HaveOccurred()) - defer f1.Close() - f2, err := os.Create(dstPath) - Expect(err).Error().ShouldNot(HaveOccurred()) - defer f2.Close() - }) - - It("should copy content form src to dst", func() { - err := os.WriteFile(srcPath, testContent, 0666) - Expect(err).Error().ShouldNot(HaveOccurred()) - err = file.CopyFile(srcPath, dstPath) - Expect(err).Error().ShouldNot(HaveOccurred()) - data, err := os.ReadFile(dstPath) - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(data).Should(Equal(testContent)) - }) -}) - -var _ = Describe("GenerateAbsFilePath func", func() { - var baseDir, fileName string - When("file not exist", func() { - BeforeEach(func() { - baseDir = "not_exist" - fileName = "not_exist" - }) - It("should return err", func() { - _, err := file.GenerateAbsFilePath(baseDir, fileName) - Expect(err).Error().Should(HaveOccurred()) - }) - }) - When("file exist", func() { - BeforeEach(func() { - baseDir = GinkgoT().TempDir() - testFile, err := os.CreateTemp(baseDir, "test") - Expect(err).Error().ShouldNot(HaveOccurred()) - fileName = filepath.Base(testFile.Name()) - }) - It("should return absPath", func() { - path, err := file.GenerateAbsFilePath(baseDir, fileName) - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(path).Should(Equal(filepath.Join(baseDir, fileName))) - }) - }) -}) - -var _ = Describe("GetFileAbsDirPath func", func() { - var baseDir, fileName string - When("file exist", func() { - BeforeEach(func() { - baseDir = GinkgoT().TempDir() - testFile, err := os.CreateTemp(baseDir, "test") - Expect(err).Error().ShouldNot(HaveOccurred()) - fileName = filepath.Base(testFile.Name()) - }) - It("should return absPath", func() { - path, err := file.GetFileAbsDirPath(filepath.Join(baseDir, fileName)) - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(path).Should(Equal(baseDir)) - }) - }) -}) - -var _ = Describe("GetFileAbsDirPathOrDirItself func", func() { - var baseDir, fileName string - When("param if file", func() { - BeforeEach(func() { - baseDir = GinkgoT().TempDir() - testFile, err := os.CreateTemp(baseDir, "test") - Expect(err).Error().ShouldNot(HaveOccurred()) - fileName = filepath.Base(testFile.Name()) - }) - It("should return parent directory of file", func() { - path, err := file.GetFileAbsDirPathOrDirItself(filepath.Join(baseDir, fileName)) - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(path).Should(Equal(baseDir)) - }) - }) - When("param is dir", func() { - BeforeEach(func() { - baseDir = GinkgoT().TempDir() - }) - It("should return dir itself", func() { - path, err := file.GetFileAbsDirPathOrDirItself(baseDir) - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(path).Should(Equal(baseDir)) - }) - }) -}) diff --git a/pkg/util/file/path.go b/pkg/util/file/path.go deleted file mode 100644 index 510d95fe5..000000000 --- a/pkg/util/file/path.go +++ /dev/null @@ -1,25 +0,0 @@ -package file - -import ( - "os" - "regexp" - "strings" - - "github.com/devstream-io/devstream/pkg/util/log" -) - -func ReplaceAppNameInPathStr(filePath, appNamePlaceHolder, appName string) string { - if !strings.Contains(filePath, appNamePlaceHolder) { - return filePath - } - newFilePath := regexp.MustCompile(appNamePlaceHolder).ReplaceAllString(filePath, appName) - log.Debugf("Replace file path place holder. Before: %s, after: %s.", filePath, newFilePath) - return newFilePath -} - -func RemoveFileIfExists(filename string) error { - if _, err := os.Stat(filename); os.IsNotExist(err) { - return nil - } - return os.Remove(filename) -} diff --git a/pkg/util/file/path_test.go b/pkg/util/file/path_test.go deleted file mode 100644 index 522081a3e..000000000 --- a/pkg/util/file/path_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package file_test - -import ( - "fmt" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/pkg/util/file" -) - -var _ = Describe("replaceAppNameInPathStr func", func() { - var ( - placeHolder string - filePath string - appName string - ) - BeforeEach(func() { - placeHolder = "__app__" - appName = "test" - }) - When("filePath not contains placeHolder", func() { - BeforeEach(func() { - filePath = "/app/dev" - }) - It("should return same filePath", func() { - newPath := file.ReplaceAppNameInPathStr(filePath, placeHolder, appName) - Expect(newPath).Should(Equal(filePath)) - }) - }) - When("filPath contains placeHolder", func() { - BeforeEach(func() { - filePath = fmt.Sprintf("app/%s/dev", placeHolder) - }) - It("should replace placeHolder with app name", func() { - newPath := file.ReplaceAppNameInPathStr(filePath, placeHolder, appName) - Expect(newPath).Should(Equal(fmt.Sprintf("app/%s/dev", appName))) - }) - }) -}) diff --git a/pkg/util/file/yaml.go b/pkg/util/file/yaml.go deleted file mode 100644 index 2e80b01fe..000000000 --- a/pkg/util/file/yaml.go +++ /dev/null @@ -1,127 +0,0 @@ -package file - -import ( - "bytes" - "fmt" - "io/fs" - "os" - "path/filepath" - "strings" - - yamlUtil "github.com/goccy/go-yaml" - "github.com/goccy/go-yaml/ast" - - "github.com/devstream-io/devstream/pkg/util/pkgerror" -) - -// YamlSequenceNode is yaml sequenceNode -type YamlSequenceNode struct { - StrOrigin string - StrArray []string -} - -func MergeYamlNode(dst *YamlSequenceNode, src *YamlSequenceNode) *YamlSequenceNode { - if dst == nil { - return src - } else if src == nil { - return dst - } - dst.StrOrigin = fmt.Sprintf("%s\n%s", dst.StrOrigin, src.StrOrigin) - dst.StrArray = append(dst.StrArray, src.StrArray...) - return dst -} - -// IsEmpty check node fields are empty -func (n *YamlSequenceNode) IsEmpty() bool { - return n.StrOrigin == "" && len(n.StrArray) == 0 -} - -// GetYamlNodeArrayByPath get element from yaml content by yaml path -// return string array for elements -func GetYamlNodeArrayByPath(content []byte, path string) (*YamlSequenceNode, error) { - // 1. get ast node from yaml content - node, err := getYamlAstNode(content, path) - if err != nil { - if pkgerror.CheckErrorMatchByMessage(err, "node not found") { - return nil, nil - } - return nil, fmt.Errorf("yaml parse path[%s] failed:%w", path, err) - } - // 2. transfer node to sequence node - seqNode, ok := node.(*ast.SequenceNode) - if !ok { - return nil, fmt.Errorf("yaml parse path[%s] is not valid sequenceNode", string(content)) - } - var nodeArray = make([]string, 0) - for _, sn := range seqNode.Values { - nodeArray = append(nodeArray, sn.String()) - } - y := &YamlSequenceNode{ - StrOrigin: node.String(), - StrArray: nodeArray, - } - return y, nil -} - -// GetYamlNodeStrByPath get element from yaml content by yaml path -// return string format of node -func GetYamlNodeStrByPath(content []byte, path string) (string, error) { - // 1. get ast node from yaml content - node, err := getYamlAstNode(content, path) - if err != nil { - if pkgerror.CheckErrorMatchByMessage(err, "node not found") { - return "", nil - } - return "", err - } - return node.String(), nil -} - -func getYamlAstNode(content []byte, path string) (ast.Node, error) { - appsYamlPath, err := yamlUtil.PathString(path) - if err != nil { - return nil, fmt.Errorf("yaml generate path failed: %w", err) - } - node, err := appsYamlPath.ReadNode(bytes.NewBuffer(content)) - if err != nil { - return nil, fmt.Errorf("yaml read node failed: %w", err) - } - return node, nil -} - -// ReadYamls reads file or files from dir whose suffix is yaml or yml -// and returns the content of the files without "---" separator -func ReadYamls(path string) ([]byte, error) { - stat, err := os.Stat(path) - if err != nil { - return nil, err - } - var contents []byte - if stat.IsDir() { - filterYaml := func(path string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - if d.IsDir() { - return nil - } - if filepath.Ext(path) == ".yaml" || filepath.Ext(path) == ".yml" { - content, err := os.ReadFile(path) - if err != nil { - return err - } - contents = append(contents, content...) - } - return nil - } - err = filepath.WalkDir(path, filterYaml) - } else { - contents, err = os.ReadFile(path) - } - - if err != nil { - return nil, err - } - - return []byte(strings.ReplaceAll(string(contents), "\n---\n", "\n")), nil -} diff --git a/pkg/util/file/yaml_test.go b/pkg/util/file/yaml_test.go deleted file mode 100644 index a74f1dce7..000000000 --- a/pkg/util/file/yaml_test.go +++ /dev/null @@ -1,284 +0,0 @@ -package file_test - -import ( - "fmt" - "os" - "path/filepath" - "strings" - - mapset "github.com/deckarep/golang-set/v2" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/pkg/util/file" -) - -var _ = Describe("YamlSequenceNode struct", func() { - var n *file.YamlSequenceNode - Context("IsEmpty method", func() { - When("StrOrigin is not empty", func() { - BeforeEach(func() { - n = &file.YamlSequenceNode{ - StrOrigin: "test", - } - }) - It("should return false", func() { - Expect(n.IsEmpty()).Should(BeFalse()) - }) - }) - When("StrArray is not empty", func() { - BeforeEach(func() { - n = &file.YamlSequenceNode{ - StrArray: []string{"test"}, - } - }) - It("should return false", func() { - Expect(n.IsEmpty()).Should(BeFalse()) - }) - }) - When("StrArray is empty", func() { - BeforeEach(func() { - n = &file.YamlSequenceNode{} - }) - It("should return true", func() { - Expect(n.IsEmpty()).Should(BeTrue()) - }) - }) - }) -}) - -var _ = Describe("GetYamlNodeArrayByPath", func() { - var ( - dst, src *file.YamlSequenceNode - ) - When("src is nil", func() { - BeforeEach(func() { - dst = &file.YamlSequenceNode{ - StrOrigin: "test_dst", - } - src = nil - }) - It("should return dst content", func() { - Expect(file.MergeYamlNode(dst, src)).Should(Equal(dst)) - }) - }) - When("dst is nil", func() { - BeforeEach(func() { - src = &file.YamlSequenceNode{ - StrOrigin: "test_src", - } - dst = nil - }) - It("should return src content", func() { - Expect(file.MergeYamlNode(dst, src)).Should(Equal(src)) - }) - }) - When("dst and src have contents", func() { - BeforeEach(func() { - src = &file.YamlSequenceNode{ - StrOrigin: "test_src", - StrArray: []string{"test_src_array"}, - } - dst = &file.YamlSequenceNode{ - StrOrigin: "test_dst", - StrArray: []string{"test_dst_array"}, - } - }) - It("should merge content", func() { - result := file.MergeYamlNode(dst, src) - Expect(result.StrOrigin).Should(Equal("test_dst\ntest_src")) - Expect(result.StrArray).Should(Equal([]string{ - "test_dst_array", "test_src_array", - })) - }) - }) -}) - -var _ = Describe("GetYamlNodeArrayByPath", func() { - var ( - yamlPath string - testData []byte - ) - BeforeEach(func() { - testData = []byte(` -tests: - - name: plugin1 - instanceID: default - options: - key1: [[ var1 ]] - - name: plugin2 - instanceID: ins2 - options: - key1: value1 - key2: [[ var2 ]]`) - }) - When("yaml path is not valid", func() { - BeforeEach(func() { - yamlPath = "not_valid_path" - }) - It("should return error", func() { - _, err := file.GetYamlNodeArrayByPath(testData, yamlPath) - Expect(err).Error().Should(HaveOccurred()) - Expect(err.Error()).Should(ContainSubstring("invalid path string")) - }) - }) - When("yaml path not exist", func() { - BeforeEach(func() { - yamlPath = "$.field" - }) - It("should return nil", func() { - node, err := file.GetYamlNodeArrayByPath(testData, yamlPath) - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(node).Should(BeNil()) - }) - }) - When("node is valid sequenceNode", func() { - var expectStr string - BeforeEach(func() { - yamlPath = "$.tests[*]" - expectStr = ` - name: plugin1 - instanceID: default - options: - key1: [[var1]] - - name: plugin2 - instanceID: ins2 - options: - key1: value1 - key2: [[var2]]` - }) - It("should return sequenceNode", func() { - node, err := file.GetYamlNodeArrayByPath(testData, yamlPath) - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(node).ShouldNot(BeNil()) - Expect(expectStr).Should(Equal(node.StrOrigin)) - nodeArray := node.StrArray - Expect(len(nodeArray)).Should(Equal(2)) - Expect(nodeArray[0]).Should(Equal(" name: plugin1\n instanceID: default\n options:\n key1: [[var1]]")) - Expect(nodeArray[1]).Should(Equal(" name: plugin2\n instanceID: ins2\n options:\n key1: value1\n key2: [[var2]]")) - }) - }) - When("yaml data array is not valid", func() { - BeforeEach(func() { - testData = []byte(` -tests: - - name: plugin1 - instanceID: default - options: - key1: ggg`) - }) - It("should return error", func() { - _, err := file.GetYamlNodeArrayByPath(testData, yamlPath) - Expect(err).Error().ShouldNot(HaveOccurred()) - }) - }) - When("node is not sequence", func() { - BeforeEach(func() { - testData = []byte(`tests: - name: plugin1 - instanceID: default - options: - key1: [[ var1 ]]`) - yamlPath = "$.tests.name" - }) - It("should return error", func() { - _, err := file.GetYamlNodeArrayByPath(testData, yamlPath) - Expect(err).Error().Should(HaveOccurred()) - Expect(err.Error()).Should(ContainSubstring("is not valid sequenceNode")) - }) - }) -}) - -var _ = Describe("GetYamlNodeStrByPath", func() { - var ( - testData []byte - yamlPath string - ) - BeforeEach(func() { - yamlPath = "$.tests" - testData = []byte(` -tests: - name: plugin1 - instanceID: default - options: - key1: [[ var ]]`) - }) - It("should return error", func() { - node, err := file.GetYamlNodeStrByPath(testData, yamlPath) - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(node).Should(Equal(" name: plugin1\n instanceID: default\n options:\n key1: [[var]]")) - }) -}) - -var _ = Describe("ReadYamls func", func() { - var contents = []string{ - "test_content1\n---\ntest_content1-1\n", - "test_content2\n", - "test_content3\n", - "test_content4\n---\n", - } - var contentsWithoutSeprator = []string{ - "test_content1", - "test_content1-1", - "test_content2", - "test_content3", - "test_content4", - } - - var ( - tempDir, filePath string - ) - - JustAfterEach(func() { - dataReadBytes, err := file.ReadYamls(filePath) - Expect(err).Error().ShouldNot(HaveOccurred()) - dataReadStrSlice := strings.Split(strings.TrimSpace(string(dataReadBytes)), "\n") - Expect(mapset.NewSet(dataReadStrSlice...). - Equal(mapset.NewSet(contentsWithoutSeprator...))). - Should(BeTrue(), - fmt.Sprintf("dataRead: %v\noriginContent: %v", dataReadStrSlice, contentsWithoutSeprator)) - }) - - Context("read from file", func() { - BeforeEach(func() { - tempDir = GinkgoT().TempDir() - filePath = filepath.Join(tempDir, "test.yaml") - f, err := os.Create(filePath) - Expect(err).Error().ShouldNot(HaveOccurred()) - defer f.Close() - for _, content := range contents { - _, err := f.WriteString(content) - Expect(err).Error().ShouldNot(HaveOccurred()) - } - }) - It("should return all contents", func() { - filePath = filepath.Join(tempDir, "test.yaml") - }) - }) - - Context("read from dir", func() { - BeforeEach(func() { - tempDir = GinkgoT().TempDir() - for i, content := range contents[:len(contents)-1] { - filePath = filepath.Join(tempDir, fmt.Sprintf("test-%d.yaml", i)) - f, err := os.Create(filePath) - Expect(err).Error().ShouldNot(HaveOccurred()) - defer f.Close() - _, err = f.WriteString(content) - Expect(err).Error().ShouldNot(HaveOccurred()) - } - // test multilevel dir - const subDir = "subdir/subsubdir" - filePath = filepath.Join(tempDir, subDir, "test.yml") - err := os.MkdirAll(filepath.Dir(filePath), 0755) - Expect(err).Error().ShouldNot(HaveOccurred()) - f, err := os.Create(filePath) - Expect(err).Error().ShouldNot(HaveOccurred()) - defer f.Close() - _, err = f.WriteString(contents[len(contents)-1]) - Expect(err).Error().ShouldNot(HaveOccurred()) - }) - It("should return all contents", func() { - filePath = tempDir - }) - }) -}) diff --git a/pkg/util/helm/helm.go b/pkg/util/helm/helm.go deleted file mode 100644 index 03db945e5..000000000 --- a/pkg/util/helm/helm.go +++ /dev/null @@ -1,167 +0,0 @@ -package helm - -import ( - "context" - "os" - "path/filepath" - "strings" - "time" - - helmclient "github.com/mittwald/go-helm-client" - "github.com/spf13/viper" - "helm.sh/helm/v3/pkg/repo" - - "github.com/devstream-io/devstream/pkg/util/log" -) - -var ( - repositoryCache = filepath.Join(os.TempDir(), ".helmcache") - repositoryConfig = filepath.Join(os.TempDir(), ".helmrepo") -) - -// Helm is helm implementation -type Helm struct { - *repo.Entry - *helmclient.ChartSpec - helmclient.Client -} - -type Option func(*Helm) - -// NewHelm creates a new Helm -func NewHelm(param *HelmParam, option ...Option) (*Helm, error) { - isDebugMode := viper.GetBool("debug") - if isDebugMode { - log.Info("Helm is running in debug mode.") - } - - hClient, err := helmclient.New( - &helmclient.Options{ - Namespace: param.Chart.Namespace, - RepositoryCache: repositoryCache, - RepositoryConfig: repositoryConfig, - Debug: isDebugMode, - }, - ) - if err != nil { - return nil, err - } - - entry := &repo.Entry{ - Name: param.Repo.Name, - URL: param.Repo.URL, - Username: "", - Password: "", - CertFile: "", - KeyFile: "", - CAFile: "", - InsecureSkipTLSverify: false, - PassCredentialsAll: false, - } - atomic := true - if !*param.Chart.Wait { - atomic = false - } - tmout, err := time.ParseDuration(param.Chart.Timeout) - if err != nil { - return nil, err - } - chartSpec := &helmclient.ChartSpec{ - ReleaseName: param.Chart.ReleaseName, - ChartName: param.Chart.ChartName, - Namespace: param.Chart.Namespace, - ValuesYaml: param.Chart.ValuesYaml, - Version: param.Chart.Version, - CreateNamespace: false, - DisableHooks: false, - Replace: true, - Wait: *param.Chart.Wait, - DependencyUpdate: false, - Timeout: tmout, - GenerateName: false, - NameTemplate: "", - Atomic: atomic, - SkipCRDs: false, - UpgradeCRDs: *param.Chart.UpgradeCRDs, - SubNotes: false, - Force: false, - ResetValues: false, - ReuseValues: false, - Recreate: false, - MaxHistory: 0, - CleanupOnFail: false, - DryRun: false, - } - if param.Chart.ChartPath != "" { - chartSpec.ChartName = param.Chart.ChartPath - if err = cacheChartPackage(param.Chart.ChartPath); err != nil { - return nil, err - } - } - - h := &Helm{ - Entry: entry, - ChartSpec: chartSpec, - Client: hClient, - } - - for _, op := range option { - op(h) - } - - if param.Chart.ChartPath == "" { - if err = h.AddOrUpdateChartRepo(*entry); err != nil { - return nil, err - } - } - - return h, nil -} - -func WithEntry(entry *repo.Entry) Option { - return func(r *Helm) { - r.Entry = entry - } -} - -func WithChartSpec(spec *helmclient.ChartSpec) Option { - return func(r *Helm) { - r.ChartSpec = spec - } -} - -func WithClient(client helmclient.Client) Option { - return func(r *Helm) { - r.Client = client - } -} - -func (h *Helm) AddOrUpdateChartRepo(entry repo.Entry) error { - return h.Client.AddOrUpdateChartRepo(entry) -} - -func (h *Helm) InstallOrUpgradeChart() error { - _, err := h.Client.InstallOrUpgradeChart(context.TODO(), h.ChartSpec) - return err -} - -func (h *Helm) UninstallHelmChartRelease() (err error) { - if err = h.Client.UninstallReleaseByName(h.ChartSpec.ReleaseName); err != nil { - if strings.Contains(err.Error(), "not found") { - log.Warn("Release is not found, maybe it has been deleted.") - return nil - } - return err - } - return nil -} - -// GetAnnotationName will return label key for service created by helm -func GetAnnotationName() string { - return "meta.helm.sh/release-name" -} - -// GetLabelName will return label key for service created by helm -func GetLabelName() string { - return "app.kubernetes.io/instance" -} diff --git a/pkg/util/helm/helm_operation_test.go b/pkg/util/helm/helm_operation_test.go deleted file mode 100644 index 9b12291a4..000000000 --- a/pkg/util/helm/helm_operation_test.go +++ /dev/null @@ -1,161 +0,0 @@ -package helm_test - -import ( - "fmt" - "time" - - helmclient "github.com/mittwald/go-helm-client" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "helm.sh/helm/v3/pkg/repo" - - "github.com/devstream-io/devstream/pkg/util/helm" -) - -var _ = Describe("InstallOrUpgradeChart func", func() { - It("should work noraml", func() { - atomic := true - if !*helmParam.Chart.Wait { - atomic = false - } - tmout, err := time.ParseDuration(helmParam.Chart.Timeout) - Expect(err).ShouldNot(HaveOccurred()) - chartSpec := &helmclient.ChartSpec{ - ReleaseName: helmParam.Chart.ReleaseName, - ChartName: helmParam.Chart.ChartName, - Namespace: helmParam.Chart.Namespace, - ValuesYaml: helmParam.Chart.ValuesYaml, - Version: helmParam.Chart.Version, - CreateNamespace: false, - DisableHooks: false, - Replace: true, - Wait: *helmParam.Chart.Wait, - DependencyUpdate: false, - Timeout: tmout, - GenerateName: false, - NameTemplate: "", - Atomic: atomic, - SkipCRDs: false, - UpgradeCRDs: *helmParam.Chart.UpgradeCRDs, - SubNotes: false, - Force: false, - ResetValues: false, - ReuseValues: false, - Recreate: false, - MaxHistory: 0, - CleanupOnFail: false, - DryRun: false, - } - - h, err := helm.NewHelm(helmParam, helm.WithChartSpec(chartSpec), helm.WithClient(&mockClient{})) - Expect(err).ShouldNot(HaveOccurred()) - err = h.InstallOrUpgradeChart() - Expect(err).ShouldNot(HaveOccurred()) - }) -}) - -var _ = Describe("AddOrUpdateChartRepo func", func() { - It("should work noraml", func() { - entry := &repo.Entry{ - Name: helmParam.Repo.Name, - URL: helmParam.Repo.URL, - Username: "", - Password: "", - CertFile: "", - KeyFile: "", - CAFile: "", - InsecureSkipTLSverify: false, - PassCredentialsAll: false, - } - atomic := true - if !*helmParam.Chart.Wait { - atomic = false - } - tmout, err := time.ParseDuration(helmParam.Chart.Timeout) - Expect(err).ShouldNot(HaveOccurred()) - chartSpec := &helmclient.ChartSpec{ - ReleaseName: helmParam.Chart.ReleaseName, - ChartName: helmParam.Chart.ChartName, - Namespace: helmParam.Chart.Namespace, - ValuesYaml: helmParam.Chart.ValuesYaml, - Version: helmParam.Chart.Version, - CreateNamespace: false, - DisableHooks: false, - Replace: true, - Wait: *helmParam.Chart.Wait, - DependencyUpdate: false, - Timeout: tmout, - GenerateName: false, - NameTemplate: "", - Atomic: atomic, - SkipCRDs: false, - UpgradeCRDs: *helmParam.Chart.UpgradeCRDs, - SubNotes: false, - Force: false, - ResetValues: false, - ReuseValues: false, - Recreate: false, - MaxHistory: 0, - CleanupOnFail: false, - DryRun: false, - } - - h, err := helm.NewHelm(helmParam, helm.WithEntry(entry), helm.WithChartSpec(chartSpec), helm.WithClient(&mockClient{})) - Expect(err).ShouldNot(HaveOccurred()) - err = h.AddOrUpdateChartRepo(*entry) - Expect(err).ShouldNot(HaveOccurred()) - }) -}) - -var _ = Describe("UninstallHelmChartRelease func", func() { - It("should work", func() { - atomic := true - if !*helmParam.Chart.Wait { - atomic = false - } - tmout, err := time.ParseDuration(helmParam.Chart.Timeout) - Expect(err).ShouldNot(HaveOccurred()) - chartSpec := &helmclient.ChartSpec{ - ReleaseName: helmParam.Chart.ReleaseName, - ChartName: helmParam.Chart.ChartName, - Namespace: helmParam.Chart.Namespace, - ValuesYaml: helmParam.Chart.ValuesYaml, - Version: helmParam.Chart.Version, - CreateNamespace: false, - DisableHooks: false, - Replace: true, - Wait: *helmParam.Chart.Wait, - DependencyUpdate: false, - Timeout: tmout, - GenerateName: false, - NameTemplate: "", - Atomic: atomic, - SkipCRDs: false, - UpgradeCRDs: *helmParam.Chart.UpgradeCRDs, - SubNotes: false, - Force: false, - ResetValues: false, - ReuseValues: false, - Recreate: false, - MaxHistory: 0, - CleanupOnFail: false, - DryRun: false, - } - // base - h, err := helm.NewHelm(helmParam, helm.WithChartSpec(chartSpec), helm.WithClient(&mockClient{})) - Expect(err).ShouldNot(HaveOccurred()) - - err = h.UninstallHelmChartRelease() - Expect(err).ShouldNot(HaveOccurred()) - - // mock error - h, err = helm.NewHelm(helmParam, helm.WithChartSpec(chartSpec), helm.WithClient(&mockClient{ - UninstallReleaseByNameError: fmt.Errorf("data error"), - })) - Expect(err).ShouldNot(HaveOccurred()) - - err = h.UninstallHelmChartRelease() - Expect(err).Should(HaveOccurred()) - Expect(err.Error()).Should(Equal("data error")) - }) -}) diff --git a/pkg/util/helm/helm_suite_test.go b/pkg/util/helm/helm_suite_test.go deleted file mode 100644 index e9129a74c..000000000 --- a/pkg/util/helm/helm_suite_test.go +++ /dev/null @@ -1,57 +0,0 @@ -package helm_test - -import ( - "context" - "testing" - - helmclient "github.com/mittwald/go-helm-client" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "helm.sh/helm/v3/pkg/release" - "helm.sh/helm/v3/pkg/repo" - - "github.com/devstream-io/devstream/pkg/util/helm" - "github.com/devstream-io/devstream/pkg/util/types" -) - -func TestHelm(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Pkg Util Helm Test Suite") -} - -type mockClient struct { - helmclient.Client - AddOrUpdateChartRepoError error - UninstallReleaseByNameError error - InstallOrUpgradeChartError error -} - -func (c *mockClient) AddOrUpdateChartRepo(enrty repo.Entry) error { - - return c.AddOrUpdateChartRepoError -} - -func (c *mockClient) UninstallReleaseByName(name string) error { - return c.UninstallReleaseByNameError -} - -func (c *mockClient) InstallOrUpgradeChart(ctx context.Context, spec *helmclient.ChartSpec) (*release.Release, error) { - if c.InstallOrUpgradeChartError != nil { - return nil, c.InstallOrUpgradeChartError - } - var mockedRelease = release.Release{Name: "test"} - return &mockedRelease, nil -} - -var helmParam = &helm.HelmParam{ - helm.Repo{ - Name: "helm", - URL: "test1", - }, - helm.Chart{ - ReleaseName: "helm:v1.0.0", - Timeout: "1m", - Wait: types.Bool(false), - UpgradeCRDs: types.Bool(false), - }, -} diff --git a/pkg/util/helm/helm_test.go b/pkg/util/helm/helm_test.go deleted file mode 100644 index c45f1e5f5..000000000 --- a/pkg/util/helm/helm_test.go +++ /dev/null @@ -1,106 +0,0 @@ -package helm_test - -import ( - "fmt" - "time" - - helmclient "github.com/mittwald/go-helm-client" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "helm.sh/helm/v3/pkg/repo" - - "github.com/devstream-io/devstream/pkg/util/helm" -) - -var _ = Describe("NewHelm func", func() { - var ( - client helmclient.Client - ) - When("params are right", func() { - BeforeEach(func() { - client = &mockClient{} - }) - It("should work noraml", func() { - got, err := helm.NewHelm(helmParam, helm.WithClient(client)) - Expect(err).ShouldNot(HaveOccurred()) - Expect(got).ShouldNot(BeNil()) - }) - }) - When("params are wrong", func() { - BeforeEach(func() { - client = &mockClient{ - AddOrUpdateChartRepoError: fmt.Errorf("test error"), - } - }) - It("should work noraml", func() { - got, err := helm.NewHelm(helmParam, helm.WithClient(client)) - Expect(err).Should(HaveOccurred()) - Expect(err.Error()).Should(Equal("test error")) - Expect(got).Should(BeNil()) - }) - }) - When("has option", func() { - var ( - entry *repo.Entry - atomic bool - spec *helmclient.ChartSpec - ) - BeforeEach(func() { - - entry = &repo.Entry{ - Name: helmParam.Repo.Name, - URL: helmParam.Repo.URL, - Username: "", - Password: "", - CertFile: "", - KeyFile: "", - CAFile: "", - InsecureSkipTLSverify: false, - PassCredentialsAll: false, - } - atomic = true - if !*helmParam.Chart.Wait { - atomic = false - } - timeout, err := time.ParseDuration(helmParam.Chart.Timeout) - Expect(err).ShouldNot(HaveOccurred()) - spec = &helmclient.ChartSpec{ - ReleaseName: helmParam.Chart.ReleaseName, - ChartName: helmParam.Chart.ChartName, - Namespace: helmParam.Chart.Namespace, - ValuesYaml: helmParam.Chart.ValuesYaml, - Version: helmParam.Chart.Version, - CreateNamespace: false, - DisableHooks: false, - Replace: true, - Wait: *helmParam.Chart.Wait, - DependencyUpdate: false, - Timeout: timeout, - GenerateName: false, - NameTemplate: "", - Atomic: atomic, - SkipCRDs: false, - UpgradeCRDs: *helmParam.Chart.UpgradeCRDs, - SubNotes: false, - Force: false, - ResetValues: false, - ReuseValues: false, - Recreate: false, - MaxHistory: 0, - CleanupOnFail: false, - DryRun: false, - } - client = &mockClient{} - }) - It("should work normal", func() { - got, err := helm.NewHelm(helmParam, helm.WithClient(client)) - Expect(err).ShouldNot(HaveOccurred()) - want := &helm.Helm{ - Entry: entry, - ChartSpec: spec, - Client: client, - } - Expect(got).Should(Equal(want)) - }) - }) -}) diff --git a/pkg/util/helm/local.go b/pkg/util/helm/local.go deleted file mode 100644 index bccef692a..000000000 --- a/pkg/util/helm/local.go +++ /dev/null @@ -1,66 +0,0 @@ -package helm - -import ( - "fmt" - "os" - "path/filepath" - - "github.com/devstream-io/devstream/pkg/util/file" - - "github.com/devstream-io/devstream/pkg/util/log" - "github.com/devstream-io/devstream/pkg/util/md5" -) - -func cacheChartPackage(chartPath string) error { - if chartPath == "" { - log.Debugf("The option chartPath == \"\".") - return nil - } - - // source file checks - sFile, err := os.Stat(chartPath) - if err != nil { - return fmt.Errorf("chart <%s> doesn't exist", chartPath) - } - if !sFile.Mode().IsRegular() { - return fmt.Errorf("the chart file <%s> is non-regular (%q)", chartPath, sFile.Mode().String()) - } - - // destination chart path checks - dFilePath := filepath.Join(repositoryCache, chartPath) - log.Debugf("The destination chart path is <%s>.", dFilePath) - dFile, err := os.Stat(dFilePath) - if err != nil { // return err if err != nil and err != "NotExist" - if !os.IsNotExist(err) { - return fmt.Errorf("stat file failed: %w", err) - } - } else { // err == nil -> file exists -> check if the source file equals destination file - if !(dFile.Mode().IsRegular()) { - return fmt.Errorf("the destination file <%s> is non-regular (%q)", dFilePath, dFile.Mode().String()) - } - if os.SameFile(sFile, dFile) { - log.Debugf("The source chart package and destination chart package are same.") - return nil - } - // check md5 - equal, err := md5.FilesMD5Equal(chartPath, dFilePath) - if err != nil { - return fmt.Errorf("calc md5 failed: %w", err) - } - if equal { - log.Infof("The chart package already exists in the cache directory.") - return nil - } - // remove the destination file if its name equals to source file but their contents don't equal - if err = os.RemoveAll(dFilePath); err != nil { - return fmt.Errorf("remove destination file failed: %w", err) - } - } - - // destination chart path is empty, then create it. - log.Debugf("Prepare to copy <%s> to <%s>.", chartPath, dFilePath) - if err = os.MkdirAll(repositoryCache, 0755); err != nil { - return err - } - return file.CopyFile(chartPath, dFilePath) -} diff --git a/pkg/util/helm/param.go b/pkg/util/helm/param.go deleted file mode 100644 index f6160b6c2..000000000 --- a/pkg/util/helm/param.go +++ /dev/null @@ -1,42 +0,0 @@ -package helm - -import "github.com/devstream-io/devstream/pkg/util/types" - -// HelmParam is the struct for parameters with helm style. -type HelmParam struct { - Repo Repo - Chart Chart -} - -// Repo is the struct containing details of a git repository. -// TODO(daniel-hutao): make the Repo equals to repo.Entry -type Repo struct { - // if Name or URL equals to "", then Chart.ChartPath must be set - Name string `mapstructure:"name"` - URL string `mapstructure:"url"` -} - -// Chart is the struct containing details of a helm chart. -// TODO(daniel-hutao): make the Chart equals to helmclient.ChartSpec -type Chart struct { - // if ChartPath equals to "", then Repo.Name and Repo.URL must be set - ChartPath string `mapstructure:"chartPath"` - ChartName string `mapstructure:"chartName"` - Version string `mapstructure:"version"` - ReleaseName string `mapstructure:"releaseName"` - Namespace string `mapstructure:"namespace"` - Wait *bool `mapstructure:"wait"` - Timeout string `mapstructure:"timeout"` // such as "1.5h" or "2h45m", valid time units are "s", "m", "h" - UpgradeCRDs *bool `mapstructure:"upgradeCRDs"` - // ValuesYaml is the values.yaml content. - // use string instead of map[string]interface{} - ValuesYaml string `validate:"omitempty,yaml" mapstructure:"valuesYaml"` -} - -func (repo *Repo) FillDefaultValue(defaultRepo *Repo) { - types.FillStructDefaultValue(repo, defaultRepo) -} - -func (chart *Chart) FillDefaultValue(defaultChart *Chart) { - types.FillStructDefaultValue(chart, defaultChart) -} diff --git a/pkg/util/interact/readUserInput.go b/pkg/util/interact/readUserInput.go deleted file mode 100644 index f1c137559..000000000 --- a/pkg/util/interact/readUserInput.go +++ /dev/null @@ -1,35 +0,0 @@ -package interact - -import ( - "fmt" - "os" - - "github.com/tcnksm/go-input" - - "github.com/devstream-io/devstream/pkg/util/log" -) - -// AskUserIfContinue asks the user if he wants to continue -// default is false -func AskUserIfContinue(query string) (continued bool) { - ui := &input.UI{ - Writer: os.Stdout, - Reader: os.Stdin, - } - - userInput, err := ui.Ask(query, &input.Options{ - Required: true, - Default: "n", - Loop: true, - ValidateFunc: func(s string) error { - if s != "y" && s != "n" { - return fmt.Errorf("input must be y or n") - } - return nil - }, - }) - if err != nil { - log.Fatal(err) - } - return userInput == "y" -} diff --git a/pkg/util/jenkins/auth.go b/pkg/util/jenkins/auth.go deleted file mode 100644 index af10fabee..000000000 --- a/pkg/util/jenkins/auth.go +++ /dev/null @@ -1,118 +0,0 @@ -package jenkins - -import ( - "encoding/xml" - "fmt" - "net/http" - "strings" - - "github.com/bndr/gojenkins" - - "github.com/devstream-io/devstream/pkg/util/log" -) - -type BasicAuth struct { - Username string - Password string - Token string -} - -func (a *BasicAuth) CheckNameMatch(userName string) bool { - return userName == "" || userName == a.Username -} - -func (a *BasicAuth) usePassWordAuth() bool { - return len(a.Username) > 0 && len(a.Password) > 0 -} - -type setBearerToken struct { - rt http.RoundTripper - token string -} - -func (t *setBearerToken) transport() http.RoundTripper { - if t.rt != nil { - return t.rt - } - return http.DefaultTransport -} - -func (t *setBearerToken) RoundTrip(r *http.Request) (*http.Response, error) { - r.Header.Set("Authorization", fmt.Sprintf("Bearer %s", t.token)) - return t.transport().RoundTrip(r) -} - -type GitlabCredentials struct { - XMLName xml.Name `xml:"com.dabsquared.gitlabjenkins.connection.GitLabApiTokenImpl"` - ID string `xml:"id"` - Scope string `xml:"scope"` - Description string `xml:"description"` - APIToken string `xml:"apiToken"` -} - -func (j *jenkins) CreateGiltabCredential(id, gitlabToken string) error { - cred := GitlabCredentials{ - ID: id, - Scope: credentialScope, - APIToken: gitlabToken, - Description: id, - } - return j.createCredential(id, cred) -} - -func (j *jenkins) CreateSSHKeyCredential(id, userName, privateKey string) error { - cred := gojenkins.SSHCredentials{ - ID: id, - Scope: credentialScope, - Username: userName, - PrivateKeySource: &gojenkins.PrivateKey{ - Value: privateKey, - Class: gojenkins.KeySourceDirectEntryType, - }, - Description: id, - } - return j.createCredential(id, cred) -} - -func (j *jenkins) CreatePasswordCredential(id, userName, password string) error { - cred := gojenkins.UsernameCredentials{ - ID: id, - Scope: credentialScope, - Username: userName, - Password: password, - Description: id, - } - return j.createCredential(id, cred) -} - -func (j *jenkins) CreateSecretCredential(id, secretText string) error { - cred := gojenkins.StringCredentials{ - ID: id, - Scope: credentialScope, - Secret: secretText, - Description: id, - } - return j.createCredential(id, cred) - -} - -func (j *jenkins) createCredential(id string, cred interface{}) error { - cm := &gojenkins.CredentialsManager{ - J: &j.Jenkins, - } - err := cm.Add(j.ctx, domain, cred) - if err != nil { - if strings.Contains(err.Error(), "already exists") { - log.Debugf("jenkins credential %s exist, try to update it", id) - return cm.Update(j.ctx, domain, id, cred) - } - return fmt.Errorf("could not create credential: %v", err) - } - - // get credential to validate creation - getCred := map[string]string{} - if err = cm.GetSingle(j.ctx, domain, id, getCred); err != nil { - return fmt.Errorf("could not get credential: %v", err) - } - return nil -} diff --git a/pkg/util/jenkins/auth_test.go b/pkg/util/jenkins/auth_test.go deleted file mode 100644 index a5ee284f1..000000000 --- a/pkg/util/jenkins/auth_test.go +++ /dev/null @@ -1,196 +0,0 @@ -package jenkins - -import ( - "fmt" - "net/http" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "github.com/onsi/gomega/ghttp" -) - -var _ = Describe("Basic Auth", func() { - var ( - auth *BasicAuth - userName string - ) - Context("CheckNameMatch method", func() { - When("name is matched", func() { - BeforeEach(func() { - auth = &BasicAuth{ - Username: userName, - } - }) - It("should return true", func() { - Expect(auth.CheckNameMatch(userName)).Should(BeTrue()) - }) - }) - }) - Context("usePassWordAuth method", func() { - When("user name and password are not empty", func() { - BeforeEach(func() { - auth = &BasicAuth{ - Username: "test", - Password: "test", - } - }) - It("should return true", func() { - Expect(auth.usePassWordAuth()).Should(BeTrue()) - }) - }) - When("password is empty", func() { - BeforeEach(func() { - auth = &BasicAuth{ - Username: "test", - } - }) - It("should return false", func() { - Expect(auth.usePassWordAuth()).Should(BeFalse()) - }) - }) - }) -}) - -var _ = Describe("jenkins auth methods", func() { - var ( - s *ghttp.Server - j JenkinsAPI - credName, credXmlStr, credStatusPath, createReqPath string - err error - ) - BeforeEach(func() { - s = ghttp.NewServer() - s.RouteToHandler("GET", "/api/json", func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, "ok") - }) - s.RouteToHandler("GET", "/crumbIssuer/api/json/api/json", func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, "ok") - }) - opts := &JenkinsConfigOption{ - URL: s.URL(), - Namespace: "test", - BasicAuth: &BasicAuth{ - Username: "test_user", - Password: "test_password", - }, - } - j, err = NewClient(opts) - Expect(err).ShouldNot(HaveOccurred()) - createReqPath = "/credentials/store/system/domain/_/createCredentials" - credXmlStr = ` -GLOBAL -sonarqubeTokenCredential -sonarqubeTokenCredential - - - -` - - }) - Context("CreateGiltabCredential method", func() { - var token string - BeforeEach(func() { - credName = "test_gitlab_cred" - token = "test_token" - credStatusPath = fmt.Sprintf("/credentials/store/system/domain/_/credential/%s/config.xml/", credName) - expectReqBody := fmt.Sprintf(`%sGLOBAL%s%s`, credName, credName, token) - s.AppendHandlers( - ghttp.CombineHandlers( - ghttp.VerifyRequest("POST", createReqPath), - ghttp.VerifyBody([]byte(expectReqBody)), - ghttp.RespondWith(http.StatusOK, "ok"), - ), - ghttp.CombineHandlers( - ghttp.VerifyRequest("GET", credStatusPath), - ghttp.RespondWith(http.StatusOK, credXmlStr), - ), - ) - }) - It("should work normal", func() { - err := j.CreateGiltabCredential(credName, token) - Expect(err).Error().ShouldNot(HaveOccurred()) - }) - }) - Context("CreateSecretCredential method", func() { - When("cred already exist", func() { - var secretText string - BeforeEach(func() { - credName = "test_secret_cred" - secretText = "test_secret" - credStatusPath = fmt.Sprintf("/credentials/store/system/domain/_/credential/%s/config.xml", credName) - expectReqBody := fmt.Sprintf(`%sGLOBAL%s%s`, credName, credName, secretText) - s.AppendHandlers( - ghttp.CombineHandlers( - ghttp.VerifyRequest("POST", createReqPath), - ghttp.VerifyBody([]byte(expectReqBody)), - ghttp.RespondWith(http.StatusConflict, "conflict"), - ), - ghttp.CombineHandlers( - ghttp.VerifyRequest("POST", credStatusPath), - ghttp.VerifyBody([]byte(expectReqBody)), - ghttp.RespondWith(http.StatusOK, "ok"), - ), - ) - }) - It("should update cred", func() { - err := j.CreateSecretCredential(credName, secretText) - Expect(err).Error().ShouldNot(HaveOccurred()) - }) - }) - }) - - Context("CreatePasswordCredential method", func() { - var userName, password string - When("jenkins return error", func() { - BeforeEach(func() { - credName = "test_pass_cred" - userName = "testUser" - password = "testPass" - expectReqBody := fmt.Sprintf(`%sGLOBAL%s%s%s`, credName, credName, userName, password) - s.AppendHandlers( - ghttp.CombineHandlers( - ghttp.VerifyRequest("POST", createReqPath), - ghttp.VerifyBody([]byte(expectReqBody)), - ghttp.RespondWith(http.StatusBadGateway, "gateway error"), - ), - ) - }) - It("should return error", func() { - err := j.CreatePasswordCredential(credName, userName, password) - Expect(err).Error().Should(HaveOccurred()) - }) - - }) - }) - - Context("CreateSSHKeyCredential method", func() { - When("get cred failed", func() { - var sshKey, userName string - BeforeEach(func() { - credName = "test_sshKey_cred" - sshKey = "test_ssh_key" - userName = "test_ssh_user" - credStatusPath = fmt.Sprintf("/credentials/store/system/domain/_/credential/%s/config.xml/", credName) - expectReqBody := fmt.Sprintf(`%sGLOBAL%s%s%s`, credName, userName, credName, sshKey) - s.AppendHandlers( - ghttp.CombineHandlers( - ghttp.VerifyRequest("POST", createReqPath), - ghttp.VerifyBody([]byte(expectReqBody)), - ghttp.RespondWith(http.StatusOK, "ok"), - ), - ghttp.CombineHandlers( - ghttp.VerifyRequest("GET", credStatusPath), - ghttp.RespondWith(http.StatusBadGateway, "bad gateway"), - ), - ) - }) - It("should return error", func() { - err := j.CreateSSHKeyCredential(credName, userName, sshKey) - Expect(err).Error().Should(HaveOccurred()) - }) - }) - }) - AfterEach(func() { - s.Close() - }) -}) diff --git a/pkg/util/jenkins/dingtalk.go b/pkg/util/jenkins/dingtalk.go deleted file mode 100644 index 97122922b..000000000 --- a/pkg/util/jenkins/dingtalk.go +++ /dev/null @@ -1,42 +0,0 @@ -package jenkins - -import ( - "context" - "encoding/json" - "fmt" - "io" - - "github.com/devstream-io/devstream/pkg/util/jenkins/dingtalk" -) - -// ApplyDingTalkBot will override the setting of DingTalk bot list in Jenkins. -// The name of this plugin is "dingding-notifications". -func (j *jenkins) ApplyDingTalkBot(config dingtalk.BotConfig) error { - // build config - innerConfig, err := dingtalk.BuildDingTalkConfig(config) - if err != nil { - return err - } - - // build request - configJson, err := json.Marshal(innerConfig) - if err != nil { - return fmt.Errorf("marshal dingtalk config failed: %v", err) - } - params := map[string]string{ - "json": string(configJson), - } - - // send request - ctx := context.Background() - res, err := j.Requester.Post(ctx, "/manage/dingtalk/configure", nil, nil, params) - - if err != nil { - return fmt.Errorf("apply dingtalk bot failed: %v", err) - } - if res.StatusCode != 200 { - body, _ := io.ReadAll(res.Body) - return fmt.Errorf("apply dingtalk bot failed: status code: %d, response body: %v", res.StatusCode, string(body)) - } - return nil -} diff --git a/pkg/util/jenkins/dingtalk/build.go b/pkg/util/jenkins/dingtalk/build.go deleted file mode 100644 index 2f311a83f..000000000 --- a/pkg/util/jenkins/dingtalk/build.go +++ /dev/null @@ -1,81 +0,0 @@ -package dingtalk - -var DefaultNoticeOccasions = []string{ - "START", - "ABORTED", - "FAILURE", - "SUCCESS", - "UNSTABLE", - "NOT_BUILT", -} - -type ( - BotConfig struct { - NoticeOccasions []string - Verbose bool - ProxyConfig ProxyConfig - RobotConfigs []BotInfoConfig - } - - BotInfoConfig struct { - ID string - Name string - Webhook string - SecurityType string - SecurityValue string - } -) - -func BuildDingTalkConfig(config BotConfig) (InnerBotConfig, error) { - // set default notice occasions and proxy config - if len(config.NoticeOccasions) == 0 { - config.NoticeOccasions = DefaultNoticeOccasions - } - if config.ProxyConfig.Type == "" { - config.ProxyConfig = ProxyConfig{ - Type: "DIRECT", - Host: "", - Port: "0", - } - } - - // build global config - innerConfig := InnerBotConfig{ - NoticeOccasions: config.NoticeOccasions, - Verbose: config.Verbose, - ProxyConfig: config.ProxyConfig, - CoreApply: "true", - } - - // build robot detail config - for _, bot := range config.RobotConfigs { - // set security type and value - securityPolicyConfigs := []SecurityPolicyConfigs{ - { - Value: "", - Type: SecurityTypeKey, - Desc: SecurityTypeKeyChinese, - }, - { - Value: "", - Type: SecurityTypeSecret, - Desc: SecurityTypeSecretChinese, - }, - } - switch bot.SecurityType { - case SecurityTypeKey, SecurityTypeKeyChinese: - securityPolicyConfigs[0].Value = bot.SecurityValue - case SecurityTypeSecret, SecurityTypeSecretChinese: - securityPolicyConfigs[1].Value = bot.SecurityValue - } - - innerConfig.RobotConfigs = append(innerConfig.RobotConfigs, RobotConfigs{ - ID: bot.ID, - Name: bot.Name, - Webhook: bot.Webhook, - SecurityPolicyConfigs: securityPolicyConfigs, - }) - } - - return innerConfig, nil -} diff --git a/pkg/util/jenkins/dingtalk/type.go b/pkg/util/jenkins/dingtalk/type.go deleted file mode 100644 index dee25beab..000000000 --- a/pkg/util/jenkins/dingtalk/type.go +++ /dev/null @@ -1,35 +0,0 @@ -package dingtalk - -type ( - InnerBotConfig struct { - NoticeOccasions []string `json:"noticeOccasions"` - Verbose bool `json:"verbose"` - ProxyConfig ProxyConfig `json:"proxyConfig"` - RobotConfigs []RobotConfigs `json:"robotConfigs"` - CoreApply string `json:"core:apply"` - } - - ProxyConfig struct { - Type string `json:"type"` - Host string `json:"host"` - Port string `json:"port"` - } - SecurityPolicyConfigs struct { - Value string `json:"value"` - Type string `json:"type"` - Desc string `json:"desc"` - } - RobotConfigs struct { - ID string `json:"id"` - Name string `json:"name"` - Webhook string `json:"webhook"` - SecurityPolicyConfigs []SecurityPolicyConfigs `json:"securityPolicyConfigs"` - } -) - -const ( - SecurityTypeKey = "KEY" - SecurityTypeKeyChinese = "关键字" - SecurityTypeSecret = "SECRET" - SecurityTypeSecretChinese = "加密" -) diff --git a/pkg/util/jenkins/dingtalk_test.go b/pkg/util/jenkins/dingtalk_test.go deleted file mode 100644 index 2978abbfc..000000000 --- a/pkg/util/jenkins/dingtalk_test.go +++ /dev/null @@ -1,83 +0,0 @@ -package jenkins - -import ( - "fmt" - "net/http" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "github.com/onsi/gomega/ghttp" - - "github.com/devstream-io/devstream/pkg/util/jenkins/dingtalk" -) - -var _ = Describe("jenkins dingtalk methods", func() { - var ( - s *ghttp.Server - j JenkinsAPI - err error - dingtalkConfig *dingtalk.BotConfig - ) - BeforeEach(func() { - s = ghttp.NewServer() - s.RouteToHandler("GET", "/api/json", func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, "ok") - }) - s.RouteToHandler("GET", "/crumbIssuer/api/json/api/json", func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, "ok") - }) - opts := &JenkinsConfigOption{ - URL: s.URL(), - Namespace: "test", - BasicAuth: &BasicAuth{ - Username: "test_user", - Password: "test_password", - }, - } - j, err = NewClient(opts) - Expect(err).ShouldNot(HaveOccurred()) - dingtalkConfig = &dingtalk.BotConfig{ - RobotConfigs: []dingtalk.BotInfoConfig{ - { - ID: "test", - Name: "test", - Webhook: "test", - }, - }, - } - }) - Context("ApplyDingTalkBot method", func() { - When("apply success", func() { - BeforeEach(func() { - s.AppendHandlers( - ghttp.CombineHandlers( - ghttp.VerifyRequest("POST", "/manage/dingtalk/configure"), - ghttp.RespondWith(http.StatusOK, "ok"), - ), - ) - }) - It("should work normal", func() { - err := j.ApplyDingTalkBot(*dingtalkConfig) - Expect(err).Error().ShouldNot(HaveOccurred()) - }) - }) - When("apply failed", func() { - BeforeEach(func() { - s.AppendHandlers( - ghttp.CombineHandlers( - ghttp.VerifyRequest("POST", "/manage/dingtalk/configure"), - ghttp.RespondWith(http.StatusBadGateway, "bad gateway"), - ), - ) - }) - It("should return error", func() { - err := j.ApplyDingTalkBot(*dingtalkConfig) - Expect(err).Error().Should(HaveOccurred()) - }) - }) - - }) - AfterEach(func() { - s.Close() - }) -}) diff --git a/pkg/util/jenkins/jenkins.go b/pkg/util/jenkins/jenkins.go deleted file mode 100644 index 4062e0b95..000000000 --- a/pkg/util/jenkins/jenkins.go +++ /dev/null @@ -1,102 +0,0 @@ -package jenkins - -import ( - "context" - "net/http" - "net/http/cookiejar" - "strings" - "time" - - "github.com/bndr/gojenkins" - "github.com/pkg/errors" - - "github.com/devstream-io/devstream/pkg/util/jenkins/dingtalk" -) - -const ( - domain = "_" - credentialScope = "GLOBAL" -) - -type jenkins struct { - gojenkins.Jenkins - ctx context.Context - BasicInfo *JenkinsConfigOption -} - -type JenkinsConfigOption struct { - URL string - Namespace string - EnableRestart bool - Offline bool - BasicAuth *BasicAuth -} - -type JenkinsAPI interface { - ExecuteScript(script string) (string, error) - GetFolderJob(jobName, jobFolder string) (*gojenkins.Job, error) - DeleteJob(ctx context.Context, name string) (bool, error) - InstallPluginsIfNotExists(plugin []*JenkinsPlugin) error - CreateGiltabCredential(id, token string) error - CreateSSHKeyCredential(id, userName, privateKey string) error - CreateSecretCredential(id, secretText string) error - CreatePasswordCredential(id, userName, password string) error - ConfigCascForRepo(repoCascConfig *RepoCascConfig) error - ApplyDingTalkBot(config dingtalk.BotConfig) error - GetBasicInfo() *JenkinsConfigOption -} - -func NewClient(configOption *JenkinsConfigOption) (JenkinsAPI, error) { - url := strings.TrimSuffix(configOption.URL, "/") - jenkinsClient := &jenkins{} - jenkinsClient.Server = url - - var basicAuth *gojenkins.BasicAuth - jar, err := cookiejar.New(nil) - if err != nil { - return nil, errors.Wrap(err, "couldn't create a cookie jar") - } - - httpClient := &http.Client{ - Jar: jar, - Timeout: 10 * time.Second, - } - - basicAuthInfo := configOption.BasicAuth - if basicAuthInfo.usePassWordAuth() { - basicAuth = &gojenkins.BasicAuth{ - Username: basicAuthInfo.Username, Password: basicAuthInfo.Password, - } - } else { - httpClient.Transport = &setBearerToken{token: basicAuthInfo.Token, rt: httpClient.Transport} - } - - jenkinsClient.Requester = &gojenkins.Requester{ - Base: url, - SslVerify: true, - Client: httpClient, - BasicAuth: basicAuth, - } - if _, err := jenkinsClient.Init(context.TODO()); err != nil { - return nil, errors.Wrap(err, "couldn't init Jenkins API client") - } - - status, err := jenkinsClient.Poll(context.TODO()) - if err != nil { - return nil, errors.Wrap(err, "couldn't poll data from Jenkins API") - } - if status != http.StatusOK { - return nil, errors.Errorf("couldn't poll data from Jenkins API, invalid status code returned: %d", status) - } - jenkinsClient.ctx = context.TODO() - jenkinsClient.BasicInfo = configOption - return jenkinsClient, nil -} - -func (j *jenkins) GetBasicInfo() *JenkinsConfigOption { - return j.BasicInfo -} - -func (o *JenkinsConfigOption) IsOffline() bool { - return o.Offline -} diff --git a/pkg/util/jenkins/jenkins_mock.go b/pkg/util/jenkins/jenkins_mock.go deleted file mode 100644 index 67faf29eb..000000000 --- a/pkg/util/jenkins/jenkins_mock.go +++ /dev/null @@ -1,94 +0,0 @@ -package jenkins - -import ( - "context" - - "github.com/bndr/gojenkins" - - "github.com/devstream-io/devstream/pkg/util/jenkins/dingtalk" -) - -type MockClient struct { - ExecuteScriptError error - GetFolderJobError error - GetFolderJobValue *gojenkins.Job - InstallPluginsIfNotExistsError error - ConfigCascForRepoError error - DeleteJobError error - ApplyDingTalkBotError error - CreatePasswordCredentialError error - CreateSSHKeyCredentialError error - CreateGiltabCredentialError error - CreateSecretCredentialError error - BasicInfo *JenkinsConfigOption -} - -func (m *MockClient) ExecuteScript(string) (string, error) { - if m.ExecuteScriptError != nil { - return "", m.ExecuteScriptError - } - return "", nil -} -func (m *MockClient) GetFolderJob(string, string) (*gojenkins.Job, error) { - if m.GetFolderJobError != nil { - return nil, m.GetFolderJobError - } - if m.GetFolderJobValue != nil { - return m.GetFolderJobValue, nil - } - return nil, nil -} -func (m *MockClient) DeleteJob(context.Context, string) (bool, error) { - if m.DeleteJobError != nil { - return false, m.DeleteJobError - } - return true, nil -} -func (m *MockClient) InstallPluginsIfNotExists([]*JenkinsPlugin) error { - if m.InstallPluginsIfNotExistsError != nil { - return m.InstallPluginsIfNotExistsError - } - return nil -} -func (m *MockClient) CreateGiltabCredential(string, string) error { - if m.CreateGiltabCredentialError != nil { - return m.CreateGiltabCredentialError - } - return nil -} -func (m *MockClient) CreateSecretCredential(string, string) error { - if m.CreateSecretCredentialError != nil { - return m.CreateSecretCredentialError - } - return nil -} -func (m *MockClient) ConfigCascForRepo(*RepoCascConfig) error { - if m.ConfigCascForRepoError != nil { - return m.ConfigCascForRepoError - } - return nil -} -func (m *MockClient) ApplyDingTalkBot(dingtalk.BotConfig) error { - if m.ApplyDingTalkBotError != nil { - return m.ApplyDingTalkBotError - } - return nil -} -func (m *MockClient) CreateSSHKeyCredential(id, userName, privateKey string) error { - if m.CreateSSHKeyCredentialError != nil { - return m.CreateSSHKeyCredentialError - } - return nil -} -func (m *MockClient) CreatePasswordCredential(id, userName, privateKey string) error { - if m.CreatePasswordCredentialError != nil { - return m.CreatePasswordCredentialError - } - return nil -} - -func (m *MockClient) GetBasicInfo() *JenkinsConfigOption { - return &JenkinsConfigOption{ - URL: "http://mock.exmaple.com", - } -} diff --git a/pkg/util/jenkins/jenkins_suite_test.go b/pkg/util/jenkins/jenkins_suite_test.go deleted file mode 100644 index bc3a17ed5..000000000 --- a/pkg/util/jenkins/jenkins_suite_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package jenkins_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestPlanmanager(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Util jenkins Suite") -} diff --git a/pkg/util/jenkins/jenkins_test.go b/pkg/util/jenkins/jenkins_test.go deleted file mode 100644 index c951acd7f..000000000 --- a/pkg/util/jenkins/jenkins_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package jenkins - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -var _ = Describe("jenkins basic method", func() { - var ( - j *jenkins - url, namespace string - ) - BeforeEach(func() { - url = "testurl.com" - namespace = "test_namespace" - j = &jenkins{ - BasicInfo: &JenkinsConfigOption{ - URL: url, - Namespace: namespace, - }, - } - }) - It("should return basicInfo", func() { - basicInfo := j.GetBasicInfo() - Expect(basicInfo.URL).Should(Equal(url)) - Expect(basicInfo.Namespace).Should(Equal(namespace)) - }) -}) diff --git a/pkg/util/jenkins/job.go b/pkg/util/jenkins/job.go deleted file mode 100644 index 6d8e5d75e..000000000 --- a/pkg/util/jenkins/job.go +++ /dev/null @@ -1,70 +0,0 @@ -package jenkins - -import ( - "context" - _ "embed" - - "github.com/bndr/gojenkins" - "github.com/pkg/errors" - - "github.com/devstream-io/devstream/pkg/util/template" -) - -var ( - errorNotFound = errors.New("404") -) - -//go:embed tpl/seedjob.tpl.groovy -var jobGroovyScript string - -// JobScriptRenderInfo is used to render jenkins job groovy script -type JobScriptRenderInfo struct { - // jenkins related info - FolderName string - JobName string - // repo related info - RepoCredentialsId string - Branch string - RepoType string - RepoURL string - RepoName string - RepoOwner string - RepositoryURL string - SecretToken string - GitlabConnection string -} - -// JenkinsFileRenderInfo is used to render Jenkinsfile -type JenkinsFileRenderInfo struct { - AppName string `mapstructure:"AppName"` - // imageRepo variables - ImageRepositoryURL string `mapstructure:"ImageRepositoryURL"` - ImageAuthSecretName string `mapstructure:"ImageAuthSecretName"` - // dingtalk variables - DingtalkRobotID string `mapstructure:"DingtalkRobotID"` - DingtalkAtUser string `mapstructure:"DingtalkAtUser"` - // sonarqube variables - SonarqubeEnable bool `mapstructure:"SonarqubeEnable"` - // custom variables - Custom map[string]interface{} `mapstructure:"Custom"` -} - -func (jenkins *jenkins) GetFolderJob(jobName string, jobFolder string) (*gojenkins.Job, error) { - if jobFolder != "" { - return jenkins.GetJob(context.Background(), jobName, jobFolder) - } - return jenkins.GetJob(context.Background(), jobName) -} - -func BuildRenderedScript(vars any) (string, error) { - return template.NewRenderClient( - &template.TemplateOption{Name: "jenkins-script-template"}, template.ContentGetter, - ).Render(jobGroovyScript, vars) -} - -func IsNotFoundError(err error) bool { - if err != nil { - return err.Error() == errorNotFound.Error() - } - return false -} diff --git a/pkg/util/jenkins/job_test.go b/pkg/util/jenkins/job_test.go deleted file mode 100644 index b6549b6e6..000000000 --- a/pkg/util/jenkins/job_test.go +++ /dev/null @@ -1,111 +0,0 @@ -package jenkins - -import ( - "fmt" - "net/http" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "github.com/onsi/gomega/ghttp" -) - -var _ = Describe("jenkins job method", func() { - var ( - s *ghttp.Server - jobName, jobFolder, reqPath string - j JenkinsAPI - err error - ) - - BeforeEach(func() { - s = ghttp.NewServer() - s.RouteToHandler("GET", "/api/json", func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, "ok") - }) - s.RouteToHandler("GET", "/crumbIssuer/api/json/api/json", func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, "ok") - }) - opts := &JenkinsConfigOption{ - URL: s.URL(), - Namespace: "test", - BasicAuth: &BasicAuth{ - Username: "test_user", - Password: "test_password", - }, - } - j, err = NewClient(opts) - Expect(err).ShouldNot(HaveOccurred()) - }) - - Context("GetFolderJob method", func() { - When("jobFolder is empty", func() { - BeforeEach(func() { - jobName = "test_no_folder" - jobFolder = "" - reqPath = fmt.Sprintf("/job/%s/api/json", jobName) - s.AppendHandlers( - ghttp.CombineHandlers( - ghttp.VerifyRequest("GET", reqPath), - ghttp.RespondWith(http.StatusOK, "ok"), - ), - ) - - }) - It("should get job", func() { - _, err := j.GetFolderJob(jobName, jobFolder) - Expect(err).Error().ShouldNot(HaveOccurred()) - }) - }) - - When("jobFolder is setted", func() { - BeforeEach(func() { - jobName = "test_job" - jobFolder = "folder" - reqPath = fmt.Sprintf("/job/%s/job/%s/api/json", jobFolder, jobName) - s.AppendHandlers( - ghttp.CombineHandlers( - ghttp.VerifyRequest("GET", reqPath), - ghttp.RespondWith(http.StatusOK, "ok"), - ), - ) - - }) - It("should get job", func() { - _, err := j.GetFolderJob(jobName, jobFolder) - Expect(err).Error().ShouldNot(HaveOccurred()) - }) - }) - }) -}) - -var _ = Describe("BuildRenderedScript func", func() { - var j *JobScriptRenderInfo - It("should work normal", func() { - j = &JobScriptRenderInfo{ - FolderName: "gg", - RepoType: "test", - } - _, err := BuildRenderedScript(j) - Expect(err).ShouldNot(HaveOccurred()) - }) -}) - -var _ = Describe("IsNotFoundError func", func() { - var err error - When("It is not found err", func() { - BeforeEach(func() { - err = errorNotFound - }) - It("should return true", func() { - Expect(IsNotFoundError(err)).Should(BeTrue()) - }) - }) - When("It is other err", func() { - BeforeEach(func() { - err = fmt.Errorf("test error") - }) - It("should return false", func() { - Expect(IsNotFoundError(err)).Should(BeFalse()) - }) - }) -}) diff --git a/pkg/util/jenkins/plugins.go b/pkg/util/jenkins/plugins.go deleted file mode 100644 index 68ad1439b..000000000 --- a/pkg/util/jenkins/plugins.go +++ /dev/null @@ -1,139 +0,0 @@ -package jenkins - -import ( - "context" - _ "embed" - "fmt" - "net/http" - "regexp" - "strings" - "time" - - "github.com/pkg/errors" - - "github.com/devstream-io/devstream/pkg/util/log" - "github.com/devstream-io/devstream/pkg/util/template" -) - -var ( - namePattern = regexp.MustCompile(`^[0-9a-zA-Z\-_]+$`) - // time to wait jenkins restart - jenkinsRestartRetryTime = 6 -) - -type JenkinsPlugin struct { - Name string - Version string -} - -var basicPlugins = []*JenkinsPlugin{ - { - Name: "kubernetes", - Version: "3600.v144b_cd192ca_a_", - }, - { - Name: "git", - Version: "4.11.3", - }, - { - Name: "configuration-as-code", - Version: "1512.vb_79d418d5fc8", - }, - { - Name: "workflow-aggregator", - Version: "581.v0c46fa_697ffd", - }, - { - Name: "build-user-vars-plugin", - Version: "1.9", - }, -} - -//go:embed tpl/plugins.tpl.groovy -var pluginsGroovyScript string - -func (j *jenkins) InstallPluginsIfNotExists(installPlugins []*JenkinsPlugin) error { - plugins := append(installPlugins, basicPlugins...) - toInstallPlugins := j.getToInstallPluginList(plugins) - if len(toInstallPlugins) == 0 { - return nil - } - - enableRestart := j.BasicInfo.EnableRestart - pluginInstallScript, err := template.NewRenderClient(&template.TemplateOption{ - Name: "jenkins-plugins-template", - }, template.ContentGetter).Render(pluginsGroovyScript, map[string]interface{}{ - "JenkinsPlugins": transferPluginSliceToTplString(toInstallPlugins), - "EnableRestart": enableRestart, - }) - if err != nil { - log.Debugf("jenkins render plugins failed:%s", err) - return err - } - log.Info("jenkins start to install plugins...") - _, err = j.ExecuteScript(pluginInstallScript) - - // this execute will restart jenkins, so it will return error - // we just ignore this error to wait for jenkins to restart - if err != nil { - log.Debugf("jenkins start restart...") - } - log.Debug("jenkins restart to make plugin valid") - // wait jenkins to restart - if enableRestart { - return j.waitJenkinsRestart(toInstallPlugins) - } else { - return errors.New("installed new plugins need to restart jenkins") - } -} - -func (j *jenkins) waitJenkinsRestart(toInstallPlugins []*JenkinsPlugin) error { - tryTime := 1 - for { - waitTime := tryTime * 20 - // wait 20, 40, 60, 80, 100 seconds for jenkins to restart - time.Sleep(time.Duration(waitTime) * time.Second) - log.Infof("wait %d seconds for jenkins plugin install...", waitTime) - status, err := j.Poll(context.TODO()) - if err == nil && status == http.StatusOK && len(j.getToInstallPluginList(toInstallPlugins)) == 0 { - return nil - } - tryTime++ - if tryTime > jenkinsRestartRetryTime { - return errors.New("jenkins restart exceed time") - } - } -} - -func (j *jenkins) getToInstallPluginList(pluginList []*JenkinsPlugin) []*JenkinsPlugin { - toInstallPlugins := make([]*JenkinsPlugin, 0) - for _, plugin := range pluginList { - // check plugin name - if ok := namePattern.MatchString(plugin.Name); !ok { - log.Warnf("invalid plugin name '%s', must follow pattern '%s'", plugin.Name, namePattern.String()) - continue - } - // check jenkins has this plugin - installedPlugin, err := j.HasPlugin(j.ctx, plugin.Name) - if err != nil { - log.Warnf("jenkins plugin failed to check plugin %s: %s", plugin.Name, err) - continue - } - - if installedPlugin == nil { - log.Debugf("jenkins plugin %s wait to be installed", plugin.Name) - toInstallPlugins = append(toInstallPlugins, plugin) - } else { - log.Debugf("jenkins plugin %s has installed", installedPlugin.ShortName) - } - } - return toInstallPlugins -} - -func transferPluginSliceToTplString(plugins []*JenkinsPlugin) string { - pluginNames := make([]string, 0) - for _, pluginDetail := range plugins { - pluginNames = append(pluginNames, fmt.Sprintf("%s:%s", pluginDetail.Name, pluginDetail.Version)) - } - return strings.Join(pluginNames, ",") -} diff --git a/pkg/util/jenkins/plugins_test.go b/pkg/util/jenkins/plugins_test.go deleted file mode 100644 index 140e8da9d..000000000 --- a/pkg/util/jenkins/plugins_test.go +++ /dev/null @@ -1,65 +0,0 @@ -package jenkins - -import ( - "fmt" - "net/http" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "github.com/onsi/gomega/ghttp" -) - -var _ = Describe("jenins plugin methods", func() { - var ( - p []*JenkinsPlugin - s *ghttp.Server - j JenkinsAPI - err error - ) - BeforeEach(func() { - p = []*JenkinsPlugin{ - { - Name: "test", - Version: "test_version", - }, - } - s = ghttp.NewServer() - s.RouteToHandler("GET", "/api/json", func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, "ok") - }) - s.RouteToHandler("GET", "/crumbIssuer/api/json/api/json", func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, "ok") - }) - s.RouteToHandler("GET", "/pluginManager/api/json", func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, "ok") - }) - opts := &JenkinsConfigOption{ - URL: s.URL(), - Namespace: "test", - BasicAuth: &BasicAuth{ - Username: "test_user", - Password: "test_password", - }, - EnableRestart: false, - } - j, err = NewClient(opts) - Expect(err).ShouldNot(HaveOccurred()) - }) - Context("InstallPluginsIfNotExists method", func() { - When("jenkins restart enable is false", func() { - BeforeEach(func() { - s.AppendHandlers( - ghttp.CombineHandlers( - ghttp.VerifyRequest("POST", "/scriptText"), - ghttp.RespondWith(http.StatusOK, "ok"), - ), - ) - }) - It("should return error", func() { - err := j.InstallPluginsIfNotExists(p) - Expect(err).Should(HaveOccurred()) - Expect(err.Error()).Should(Equal("installed new plugins need to restart jenkins")) - }) - }) - }) -}) diff --git a/pkg/util/jenkins/script.go b/pkg/util/jenkins/script.go deleted file mode 100644 index 49e7489b5..000000000 --- a/pkg/util/jenkins/script.go +++ /dev/null @@ -1,120 +0,0 @@ -package jenkins - -import ( - _ "embed" - - "bytes" - "fmt" - "net/http" - "net/url" - "strings" - "time" - - "github.com/bndr/gojenkins" - - "github.com/devstream-io/devstream/pkg/util/log" - "github.com/devstream-io/devstream/pkg/util/template" -) - -//go:embed tpl/casc.tpl.groovy -var cascGroovyScript string - -//go:embed tpl/repo-casc.tpl.yaml -var repoCascScript string - -type scriptError struct { - output string - errorMsg string -} - -type RepoCascConfig struct { - // common variables - RepoType string - CredentialID string - Offline bool - // gitlab variables - GitLabConnectionName string - GitlabURL string - // github variables - JenkinsURL string - SecretToken string - // sonarqube variables - SonarqubeURL string - SonarqubeName string - SonarTokenCredentialID string -} - -// all jenkins client return error format is host: error detail -// Error method wrap this type of error to only return error detail -func (e *scriptError) Error() string { - errDetailMessage := e.errorMsg - if strings.Contains(errDetailMessage, ":") { - errDetailMessageArray := strings.Split(errDetailMessage, ":") - errDetailMessage = strings.TrimSpace(errDetailMessageArray[len(errDetailMessageArray)-1]) - } - return fmt.Sprintf("execute groovy script failed: %s(%s)", errDetailMessage, e.output) -} - -func (jenkins *jenkins) ExecuteScript(script string) (string, error) { - now := time.Now().Unix() - verifier := fmt.Sprintf("verifier-%d", now) - output := "" - fullScript := fmt.Sprintf("%s\nprint println('%s')", script, verifier) - - data := url.Values{} - data.Set("script", fullScript) - - ar := gojenkins.NewAPIRequest("POST", "/scriptText", bytes.NewBufferString(data.Encode())) - if err := jenkins.Requester.SetCrumb(jenkins.ctx, ar); err != nil { - return output, err - } - ar.SetHeader("Content-Type", "application/x-www-form-urlencoded") - ar.Suffix = "" - - r, err := jenkins.Requester.Do(jenkins.ctx, ar, &output, nil) - if err != nil { - return "", &scriptError{ - output: output, - errorMsg: err.Error(), - } - } - defer r.Body.Close() - - if r.StatusCode != http.StatusOK { - return output, &scriptError{ - output: output, - errorMsg: fmt.Sprintf("invalid status code '%d'", r.StatusCode), - } - } - - if !strings.Contains(output, verifier) { - return output, &scriptError{ - output: output, - errorMsg: "script verifier error", - } - } - - return output, nil -} - -func (jenkins *jenkins) ConfigCascForRepo(repoCascConfig *RepoCascConfig) error { - log.Info("jenkins start config casc...") - cascConfig, err := template.NewRenderClient(&template.TemplateOption{ - Name: "jenkins-repo-casc"}, template.ContentGetter, - ).Render(repoCascScript, repoCascConfig) - if err != nil { - log.Debugf("jenkins preinstall credentials failed: %s", err) - return err - } - groovyCascScript, err := template.NewRenderClient(&template.TemplateOption{ - Name: "jenkins-casc", - }, template.ContentGetter).Render(cascGroovyScript, map[string]string{ - "CascConfig": cascConfig, - }) - if err != nil { - log.Debugf("jenkins render casc failed: %s", err) - return err - } - _, err = jenkins.ExecuteScript(groovyCascScript) - return err -} diff --git a/pkg/util/jenkins/script_test.go b/pkg/util/jenkins/script_test.go deleted file mode 100644 index 383495fe5..000000000 --- a/pkg/util/jenkins/script_test.go +++ /dev/null @@ -1,92 +0,0 @@ -package jenkins - -import ( - "fmt" - "net/http" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "github.com/onsi/gomega/ghttp" -) - -var _ = Describe("scriptError struct", func() { - var ( - e *scriptError - ) - BeforeEach(func() { - e = &scriptError{} - }) - Context("Error method", func() { - When("errorMsg has colon", func() { - BeforeEach(func() { - e.errorMsg = "test: test with colon" - }) - It("should return err msg", func() { - result := e.Error() - Expect(result).Should(Equal("execute groovy script failed: test with colon()")) - }) - }) - When("errorMsg not has colon", func() { - BeforeEach(func() { - e.errorMsg = "this is test" - }) - It("should return script execute err msg", func() { - result := e.Error() - Expect(result).Should(Equal(fmt.Sprintf("execute groovy script failed: %s()", e.errorMsg))) - }) - }) - }) -}) - -var _ = Describe("jenkins script method", func() { - var ( - s *ghttp.Server - j JenkinsAPI - err error - ) - BeforeEach(func() { - s = ghttp.NewServer() - s.RouteToHandler("GET", "/api/json", func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, "ok") - }) - s.RouteToHandler("GET", "/crumbIssuer/api/json/api/json", func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, "ok") - }) - opts := &JenkinsConfigOption{ - URL: s.URL(), - Namespace: "test", - BasicAuth: &BasicAuth{ - Username: "test_user", - Password: "test_password", - }, - } - j, err = NewClient(opts) - Expect(err).ShouldNot(HaveOccurred()) - }) - Context("ConfigCascForRepo method", func() { - var ( - cascConfig *RepoCascConfig - ) - When("response text not contain verifier", func() { - BeforeEach(func() { - cascConfig = &RepoCascConfig{ - RepoType: "github", - CredentialID: "credId", - JenkinsURL: "testURL", - SecretToken: "secretToken", - } - s.AppendHandlers( - ghttp.CombineHandlers( - ghttp.VerifyRequest("POST", "/scriptText"), - ghttp.RespondWith(http.StatusOK, "ok"), - ), - ) - }) - It("should return err", func() { - err = j.ConfigCascForRepo(cascConfig) - Expect(err).Error().Should(HaveOccurred()) - Expect(err.Error()).Should(Equal("execute groovy script failed: script verifier error(ok)")) - }) - }) - }) -}) diff --git a/pkg/util/jenkins/tpl/casc.tpl.groovy b/pkg/util/jenkins/tpl/casc.tpl.groovy deleted file mode 100644 index ca829c4a7..000000000 --- a/pkg/util/jenkins/tpl/casc.tpl.groovy +++ /dev/null @@ -1,8 +0,0 @@ -String[] configContent = ['''[[ .CascConfig ]]'''] -def configSb = new StringBuffer() -for (int i=0; i 1. init variables -Jenkins jenkinsInstance = Jenkins.instance -String repoType = "[[ .RepoType ]]" -String jobName = "[[ .JobName ]]"; -String folderName = "[[ .FolderName ]]" -String jenkinsFileName = "Jenkinsfile" -String repoCredentialId = "[[ .RepoCredentialsId ]]" -// --> 2. init jobRef -Object jobRef = createJobWithFolder(jenkinsInstance, jobName, folderName, repoType) -// set job display name -jobRef.setDisplayName("[[ .JobName ]]") - - - -[[ if eq .RepoType "gitlab" ]] -// --> 3. config gitlab related config -// config scm for Jenkinsfile -UserRemoteConfig userRemoteConfig = new UserRemoteConfig("[[ .RepositoryURL ]]", jobName, null, repoCredentialId) - -branches = newArrayList(new BranchSpec("*/[[ .Branch ]]")) -doGenerateSubmoduleConfigurations = false -submoduleCfg = null -browser = null -gitTool = null -extensions = [] -GitSCM scm = new GitSCM([userRemoteConfig], branches, doGenerateSubmoduleConfigurations, submoduleCfg, browser, gitTool, extensions) - -FlowDefinition flowDefinition = (FlowDefinition) new CpsScmFlowDefinition(scm, jenkinsFileName) -jobRef.setDefinition(flowDefinition) - -// config gitlab trigger -def gitlabTrigger = new GitLabPushTrigger() -gitlabTrigger.setSecretToken("[[ .SecretToken ]]") -gitlabTrigger.setTriggerOnPush(true) -gitlabTrigger.setTriggerOnMergeRequest(true) -gitlabTrigger.setBranchFilterType(BranchFilterType.RegexBasedFilter) -gitlabTrigger.setSourceBranchRegex(".*") -gitlabTrigger.setTargetBranchRegex("[[ .Branch ]]") - -jobRef.addTrigger(gitlabTrigger) -def gitlabConnection = new GitLabConnectionProperty("[[ .GitlabConnection ]]") -jobRef.addProperty(gitlabConnection) -[[ end ]] - -[[ if eq .RepoType "github" ]] -// --> 3. config github related config -jobRef.getProjectFactory().setScriptPath(jenkinsFileName) -GitHubSCMSource githubSource = new GitHubSCMSource("[[ .RepoOwner ]]", "[[ .RepoName ]]", "[[ .RepoURL ]]", true) -githubSource.setCredentialsId(repoCredentialId) -githubSource.setBuildOriginBranch(true) -githubSource.setBuildOriginPRMerge(true) -githubSource.setBuildForkPRMerge(false) -BranchSource branchSource = new BranchSource(githubSource) -PersistedList sources = jobRef.getSourcesList() -sources.clear() -sources.add(branchSource) -[[ end ]] - -// create job -jobRef.save() - -def createJobWithFolder(Jenkins jenkins, String jobName, String folderName, String repoType) { - Object folderPath = null - if (folderName != "") { - def folder = jenkins.getItem(folderName) - // create folder if it not exist - if (folder == null) { - folder = jenkins.createProject(Folder.class, folderName) - } - folderPath = folder - } else { - folderPath = jenkins - } - job = folderPath.getItem(jobName) - if (job != null) { - return job - } - def jobType = WorkflowJob - if (repoType == "github") { - jobType = WorkflowMultiBranchProject - } - return folderPath.createProject(jobType, jobName) -} diff --git a/pkg/util/k8s/argocd.go b/pkg/util/k8s/argocd.go deleted file mode 100644 index 0db772a61..000000000 --- a/pkg/util/k8s/argocd.go +++ /dev/null @@ -1,49 +0,0 @@ -package k8s - -import ( - "context" - - argocdv1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" - "github.com/argoproj/gitops-engine/pkg/health" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func (c *Client) ListArgocdApplications(namespace string) ([]argocdv1alpha1.Application, error) { - appList, err := c.argocd.ArgoprojV1alpha1().Applications(namespace). - List(context.TODO(), metav1.ListOptions{}) - if err != nil { - return nil, err - } - return appList.Items, nil -} - -func (c *Client) GetArgocdApplication(namespace, name string) (*argocdv1alpha1.Application, error) { - return c.argocd.ArgoprojV1alpha1().Applications(namespace). - Get(context.TODO(), name, metav1.GetOptions{}) -} - -func (c *Client) IsArgocdApplicationReady(application *argocdv1alpha1.Application) bool { - return application.Status.Health.Status == health.HealthStatusHealthy -} - -func (c *Client) DescribeArgocdApp(app *argocdv1alpha1.Application) map[string]interface{} { - res := make(map[string]interface{}) - - res["app"] = map[string]interface{}{ - "name": app.Name, - "namespace": app.Namespace, - } - - res["src"] = map[string]interface{}{ - "repoURL": app.Spec.Source.RepoURL, - "path": app.Spec.Source.Path, - "valueFile": app.Spec.Source.Helm.ValueFiles[0], - } - - res["dest"] = map[string]interface{}{ - "server": app.Spec.Destination.Server, - "namespace": app.Spec.Destination.Namespace, - } - - return res -} diff --git a/pkg/util/k8s/configmap.go b/pkg/util/k8s/configmap.go deleted file mode 100644 index 97b9ceeae..000000000 --- a/pkg/util/k8s/configmap.go +++ /dev/null @@ -1,24 +0,0 @@ -package k8s - -import ( - "context" - - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - corev1 "k8s.io/client-go/applyconfigurations/core/v1" -) - -func (c *Client) ApplyConfigMap(name, namespace string, data, labels map[string]string) (*v1.ConfigMap, error) { - configMap := corev1.ConfigMap(name, namespace). - WithLabels(labels). - WithData(data). - WithImmutable(false) - applyOptions := metav1.ApplyOptions{ - FieldManager: "DevStream", - } - return c.clientset.CoreV1().ConfigMaps(namespace).Apply(context.Background(), configMap, applyOptions) -} - -func (c *Client) GetConfigMap(name, namespace string) (*v1.ConfigMap, error) { - return c.clientset.CoreV1().ConfigMaps(namespace).Get(context.Background(), name, metav1.GetOptions{}) -} diff --git a/pkg/util/k8s/configmap_test.go b/pkg/util/k8s/configmap_test.go deleted file mode 100644 index eadf2626d..000000000 --- a/pkg/util/k8s/configmap_test.go +++ /dev/null @@ -1,92 +0,0 @@ -package k8s - -import ( - "context" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/kubernetes/fake" -) - -var _ = Describe("configmap methods", func() { - var ( - client *Client - configmapName, namespace string - testConfigMap []runtime.Object - labels, data map[string]string - ) - BeforeEach(func() { - configmapName = "test_configmap" - namespace = "test" - labels = map[string]string{ - "usage": "test", - } - data = map[string]string{ - "field": "test", - } - client = &Client{} - client.clientset = fake.NewSimpleClientset() - }) - Context("ApplyConfigMap method", func() { - When("configmap exist", func() { - BeforeEach(func() { - testConfigMap = []runtime.Object{&v1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: configmapName, - Namespace: namespace, - Labels: labels, - }, - Data: data, - }} - client.clientset = fake.NewSimpleClientset(testConfigMap...) - }) - It("should update configmap", func() { - currentConfigMap, err := client.clientset.CoreV1().ConfigMaps( - namespace).Get(context.Background(), configmapName, metav1.GetOptions{}) - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(currentConfigMap.Data).Should(Equal(data)) - Expect(currentConfigMap.ObjectMeta.Labels).Should(Equal(labels)) - data["field"] = "apply_config" - _, err = client.ApplyConfigMap(configmapName, namespace, data, labels) - Expect(err).Error().Should(HaveOccurred()) - }) - }) - }) - Context("GetConfigMap method", func() { - When("configmap not exist", func() { - BeforeEach(func() { - configmapName = "not_exist_config" - }) - It("should return not found error", func() { - _, err := client.GetConfigMap(namespace, configmapName) - Expect(err).Error().Should(HaveOccurred()) - Expect(errors.IsNotFound(err)).Should(BeTrue()) - }) - }) - When("configmap is exist", func() { - BeforeEach(func() { - testConfigMap = []runtime.Object{&v1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: configmapName, - Namespace: namespace, - Labels: labels, - }, - Data: data, - }} - client.clientset = fake.NewSimpleClientset(testConfigMap...) - }) - It("should get correct configmap", func() { - currentConfigMap, err := client.GetConfigMap(configmapName, namespace) - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(currentConfigMap.Data).Should(Equal(data)) - Expect(currentConfigMap.ObjectMeta.Labels).Should(Equal(labels)) - }) - }) - }) -}) diff --git a/pkg/util/k8s/k8s.go b/pkg/util/k8s/k8s.go deleted file mode 100644 index dc1fd0f76..000000000 --- a/pkg/util/k8s/k8s.go +++ /dev/null @@ -1,120 +0,0 @@ -package k8s - -import ( - "fmt" - "os" - "path/filepath" - - argocdv1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" - argocdclient "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/tools/clientcmd" - "k8s.io/client-go/util/homedir" - - "github.com/devstream-io/devstream/pkg/util/log" -) - -type K8sAPI interface { - // secret API - GetSecret(namespace, name string) (map[string]string, error) - ApplySecret(name, namespace string, data map[string][]byte, labels map[string]string) (*corev1.Secret, error) - // service API - CreateService(namespace string, service *corev1.Service) error - DeleteService(namespace, serviceName string) error - GetService(namespace, name string) (*corev1.Service, error) - // storage API - CreatePersistentVolume(option *PVOption) error - DeletePersistentVolume(pvName string) error - CreatePersistentVolumeClaim(opt *PVCOption) error - DeletePersistentVolumeClaim(namespace, pvcName string) error - // resource API - GetResourceStatus(nameSpace string, anFilter, labelFilter map[string]string) (*AllResourceStatus, error) - ListDeploymentsWithLabel(namespace string, labelFilter map[string]string) ([]appsv1.Deployment, error) - GetDeployment(namespace, name string) (*appsv1.Deployment, error) - CreateDeployment(namespace string, deployment *appsv1.Deployment) error - WaitForDeploymentReady(retry int, namespace, deployName string) error - DeleteDeployment(namespace, deployName string) error - ListDaemonsetsWithLabel(namespace string, labeFilter map[string]string) ([]appsv1.DaemonSet, error) - GetStatefulset(namespace, name string) (*appsv1.StatefulSet, error) - // namespace API - UpsertNameSpace(nameSpace string) error - GetNamespace(namespace string) (*corev1.Namespace, error) - IsDevstreamNS(namespace string) (bool, error) - CreateNamespace(namespace string) error - DeleteNamespace(namespace string) error - IsNamespaceExists(namespace string) (bool, error) - // configmap API - ApplyConfigMap(name, namespace string, data, labels map[string]string) (*corev1.ConfigMap, error) - GetConfigMap(name, namespace string) (*corev1.ConfigMap, error) - // argocd API - ListArgocdApplications(namespace string) ([]argocdv1alpha1.Application, error) - GetArgocdApplication(namespace, name string) (*argocdv1alpha1.Application, error) - IsArgocdApplicationReady(application *argocdv1alpha1.Application) bool - DescribeArgocdApp(app *argocdv1alpha1.Application) map[string]interface{} -} - -type Client struct { - clientset kubernetes.Interface - // maybe it is not proper to put argocd client in the "k8s client" - argocd *argocdclient.Clientset -} - -var fakeClient *Client - -func NewClient() (K8sAPI, error) { - // if UseFakeClient() is called, return the fake client. - if fakeClient != nil { - return fakeClient, nil - } - - // TL;DR: Don't use viper.GetString("xxx") in the `util/xxx` package. - // Don't use `kubeconfig := viper.GetString("kubeconfig")` here, - // it will fail without calling `viper.BindEnv("github_token")` first. - // os.Getenv() function is more clear and reasonable here. - kubeconfig := os.Getenv("KUBECONFIG") - if kubeconfig == "" { - kubeconfig = os.Getenv("kubeconfig") - } - if kubeconfig != "" { - log.Debugf("Got the kubeconfig from env: %s.", kubeconfig) - } else { - log.Debugf("Failed to get the kubecondig from env. Prepare to get it from home dir.") - homePath := homedir.HomeDir() - if homePath == "" { - return nil, fmt.Errorf("failed to get the home directory") - } - - kubeconfig = filepath.Join(homePath, ".kube", "config") - } - - config, err := clientcmd.BuildConfigFromFlags("", kubeconfig) - if err != nil { - return nil, err - } - - clientset, err := kubernetes.NewForConfig(config) - if err != nil { - return nil, err - } - - argocdClientset, err := argocdclient.NewForConfig(config) - if err != nil { - return nil, err - } - - return &Client{ - clientset: clientset, - argocd: argocdClientset, - }, nil -} - -// UseFakeClient is used for testing, -// if this function is called, NewClient() will return the fake client. -func UseFakeClient(k8sClient kubernetes.Interface, argoClient *argocdclient.Clientset) { - fakeClient = &Client{ - clientset: k8sClient, - argocd: argoClient, - } -} diff --git a/pkg/util/k8s/k8s_mock.go b/pkg/util/k8s/k8s_mock.go deleted file mode 100644 index 720d5fd37..000000000 --- a/pkg/util/k8s/k8s_mock.go +++ /dev/null @@ -1,117 +0,0 @@ -package k8s - -import ( - argocdv1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" -) - -type MockClient struct { - GetSecretError error - GetSecretValue map[string]string -} - -func (m *MockClient) GetSecret(namespace, name string) (map[string]string, error) { - if m.GetSecretError != nil { - return nil, m.GetSecretError - } - return m.GetSecretValue, nil -} -func (m *MockClient) ApplySecret(name, namespace string, data map[string][]byte, labels map[string]string) (*corev1.Secret, error) { - return nil, nil -} -func (m *MockClient) CreateService(namespace string, service *corev1.Service) error { - return nil -} -func (m *MockClient) DeleteService(namespace, serviceName string) error { - return nil -} -func (m *MockClient) GetService(namespace, name string) (*corev1.Service, error) { - return nil, nil -} -func (m *MockClient) CreatePersistentVolume(option *PVOption) error { - return nil -} -func (m *MockClient) DeletePersistentVolume(pvName string) error { - return nil -} -func (m *MockClient) CreatePersistentVolumeClaim(opt *PVCOption) error { - return nil -} -func (m *MockClient) DeletePersistentVolumeClaim(namespace, pvcName string) error { - return nil -} -func (m *MockClient) GetResourceStatus(nameSpace string, anFilter, labelFilter map[string]string) (*AllResourceStatus, error) { - return nil, nil -} -func (m *MockClient) ListDeploymentsWithLabel(namespace string, labelFilter map[string]string) ([]appsv1.Deployment, error) { - return nil, nil -} -func (m *MockClient) GetDeployment(namespace, name string) (*appsv1.Deployment, error) { - return nil, nil -} -func (m *MockClient) CreateDeployment(namespace string, deployment *appsv1.Deployment) error { - return nil -} -func (m *MockClient) WaitForDeploymentReady(retry int, namespace, deployName string) error { - return nil -} -func (m *MockClient) DeleteDeployment(namespace, deployName string) error { - return nil -} - -func (m *MockClient) ListDaemonsetsWithLabel(namespace string, labeFilter map[string]string) ([]appsv1.DaemonSet, error) { - return nil, nil -} - -func (m *MockClient) GetStatefulset(namespace, name string) (*appsv1.StatefulSet, error) { - return nil, nil -} - -func (m *MockClient) UpsertNameSpace(nameSpace string) error { - return nil -} - -func (m *MockClient) GetNamespace(namespace string) (*corev1.Namespace, error) { - return nil, nil -} - -func (m *MockClient) IsDevstreamNS(namespace string) (bool, error) { - return false, nil -} - -func (m *MockClient) CreateNamespace(namespace string) error { - return nil -} - -func (m *MockClient) DeleteNamespace(namespace string) error { - return nil -} - -func (m *MockClient) IsNamespaceExists(namespace string) (bool, error) { - return false, nil -} - -func (m *MockClient) ApplyConfigMap(name, namespace string, data, labels map[string]string) (*corev1.ConfigMap, error) { - return nil, nil -} - -func (m *MockClient) GetConfigMap(name, namespace string) (*corev1.ConfigMap, error) { - return nil, nil -} - -func (m *MockClient) ListArgocdApplications(namespace string) ([]argocdv1alpha1.Application, error) { - return nil, nil -} - -func (m *MockClient) GetArgocdApplication(namespace, name string) (*argocdv1alpha1.Application, error) { - return nil, nil -} - -func (m *MockClient) IsArgocdApplicationReady(application *argocdv1alpha1.Application) bool { - return false -} - -func (m *MockClient) DescribeArgocdApp(app *argocdv1alpha1.Application) map[string]interface{} { - return nil -} diff --git a/pkg/util/k8s/k8s_suite_test.go b/pkg/util/k8s/k8s_suite_test.go deleted file mode 100644 index 0def07921..000000000 --- a/pkg/util/k8s/k8s_suite_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package k8s_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestPlanmanager(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "K8S Suite") -} diff --git a/pkg/util/k8s/namespace.go b/pkg/util/k8s/namespace.go deleted file mode 100644 index b63288198..000000000 --- a/pkg/util/k8s/namespace.go +++ /dev/null @@ -1,93 +0,0 @@ -package k8s - -import ( - "context" - "fmt" - - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/devstream-io/devstream/pkg/util/log" -) - -func (c *Client) UpsertNameSpace(nameSpace string) error { - // check if namespace exist - exist, err := c.IsNamespaceExists(nameSpace) - if err != nil { - log.Debugf("Failed to check the namespace exist: %s.", nameSpace) - return err - } - if !exist { - return c.CreateNamespace(nameSpace) - } - log.Debugf("The namespace %s has been existed.", nameSpace) - return nil -} - -func (c *Client) GetNamespace(namespace string) (*corev1.Namespace, error) { - return c.clientset.CoreV1().Namespaces().Get(context.TODO(), namespace, metav1.GetOptions{}) -} - -// Check whether the given namespace is created by dtm -// If the given namespace has label "created_by=DevStream", we'll control it. -// 1. The specified namespace is created by dtm, then it should be deleted -// when errors are encountered during creation or `dtm delete`. -// 2. The specified namespace is controlled by user, maybe they want to deploy plugins in -// an existing namespace or other situations, then we should not delete this namespace. -func (c *Client) IsDevstreamNS(namespace string) (bool, error) { - nsList, err := c.clientset.CoreV1().Namespaces().List( - context.TODO(), metav1.ListOptions{LabelSelector: "created_by=DevStream"}, - ) - if err != nil { - // not exist - if errors.IsNotFound(err) { - return false, nil - } - return false, err - } - - for _, ns := range nsList.Items { - // exist - if ns.ObjectMeta.Name == namespace { - return true, nil - } - } - return false, nil -} - -func (c *Client) CreateNamespace(namespace string) error { - ns := &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: namespace, - Labels: map[string]string{ - "created_by": "DevStream", - }, - }, - } - _, err := c.clientset.CoreV1().Namespaces().Create(context.TODO(), ns, metav1.CreateOptions{}) - return err -} - -func (c *Client) DeleteNamespace(namespace string) error { - if namespace == "default" || namespace == "kube-system" { - return fmt.Errorf("you can't delete the default or kube-system namespace") - } - - gracePeriodSeconds := int64(0) - return c.clientset.CoreV1().Namespaces().Delete( - context.TODO(), namespace, metav1.DeleteOptions{GracePeriodSeconds: &gracePeriodSeconds}) -} - -func (c *Client) IsNamespaceExists(namespace string) (bool, error) { - _, err := c.GetNamespace(namespace) - if err != nil && !errors.IsNotFound(err) { - return false, err - } - // not exist - if err != nil { - return false, nil - } - // exist - return true, nil -} diff --git a/pkg/util/k8s/namespace_test.go b/pkg/util/k8s/namespace_test.go deleted file mode 100644 index 31edc1124..000000000 --- a/pkg/util/k8s/namespace_test.go +++ /dev/null @@ -1,158 +0,0 @@ -package k8s - -import ( - "context" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/kubernetes/fake" -) - -var _ = Describe("namespace methods", func() { - var ( - client *Client - namespace string - devstreamLabel map[string]string - ) - BeforeEach(func() { - namespace = "test" - client = &Client{} - client.clientset = fake.NewSimpleClientset() - devstreamLabel = map[string]string{ - "created_by": "DevStream", - } - }) - Context("UpsertNameSpace method", func() { - When("namespace not exist", func() { - BeforeEach(func() { - namespace = "not_exist" - }) - It("should create namespace", func() { - err := client.UpsertNameSpace(namespace) - Expect(err).Error().ShouldNot(HaveOccurred()) - namespaceData, err := client.clientset.CoreV1().Namespaces().Get( - context.TODO(), namespace, metav1.GetOptions{}, - ) - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(namespaceData.Name).Should(Equal(namespace)) - }) - }) - When("namespace exist", func() { - BeforeEach(func() { - testNameSpace := []runtime.Object{&corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: namespace, - }, - }} - client.clientset = fake.NewSimpleClientset(testNameSpace...) - }) - It("should return nil error", func() { - err := client.UpsertNameSpace(namespace) - Expect(err).Error().ShouldNot(HaveOccurred()) - }) - }) - }) - Context("IsDevstreamNS method", func() { - BeforeEach(func() { - testNameSpace := []runtime.Object{ - &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: namespace, - }, - }, - &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test1", - Labels: devstreamLabel, - }, - }, - &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test2", - Labels: devstreamLabel, - }, - }, - } - client.clientset = fake.NewSimpleClientset(testNameSpace...) - }) - - It("should check is devstream namespace", func() { - result, err := client.IsDevstreamNS("test1") - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(result).Should(BeTrue()) - result, err = client.IsDevstreamNS(namespace) - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(result).Should(BeFalse()) - }) - }) - Context("CreateNamespace method", func() { - It("should create namespace with devstream label", func() { - err := client.CreateNamespace(namespace) - Expect(err).Error().ShouldNot(HaveOccurred()) - namespaceData, err := client.clientset.CoreV1().Namespaces().Get( - context.TODO(), namespace, metav1.GetOptions{}, - ) - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(namespaceData.Name).Should(Equal(namespace)) - labels := namespaceData.Labels - createUser, ok := labels["created_by"] - Expect(ok).Should(BeTrue()) - Expect(createUser).Should(Equal("DevStream")) - }) - }) - Context("DeleteNamespace method", func() { - When("namespace is default", func() { - BeforeEach(func() { - namespace = "default" - }) - It("should reutrn err", func() { - err := client.DeleteNamespace(namespace) - Expect(err).Error().Should(HaveOccurred()) - }) - }) - When("namespace is valid", func() { - BeforeEach(func() { - testNameSpace := []runtime.Object{&corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: namespace, - }, - }} - client.clientset = fake.NewSimpleClientset(testNameSpace...) - }) - It("should return nil error", func() { - err := client.DeleteNamespace(namespace) - Expect(err).Error().ShouldNot(HaveOccurred()) - }) - }) - }) - Context("IsNamespaceExists method", func() { - BeforeEach(func() { - testNameSpace := []runtime.Object{&corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: namespace, - }, - }} - client.clientset = fake.NewSimpleClientset(testNameSpace...) - }) - When("namespace not exist", func() { - BeforeEach(func() { - namespace = "not_exist" - }) - It("should return false with no error", func() { - exist, err := client.IsNamespaceExists(namespace) - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(exist).Should(BeFalse()) - }) - }) - When("namespace exist", func() { - It("should return true with no error", func() { - exist, err := client.IsNamespaceExists(namespace) - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(exist).Should(BeTrue()) - }) - }) - }) -}) diff --git a/pkg/util/k8s/secret.go b/pkg/util/k8s/secret.go deleted file mode 100644 index 89ab93bee..000000000 --- a/pkg/util/k8s/secret.go +++ /dev/null @@ -1,37 +0,0 @@ -package k8s - -import ( - "context" - - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - v1 "k8s.io/client-go/applyconfigurations/core/v1" -) - -func (c *Client) GetSecret(namespace, name string) (map[string]string, error) { - secretMap := make(map[string]string) - secret, err := c.clientset.CoreV1().Secrets(namespace).Get(context.TODO(), name, metav1.GetOptions{}) - if err != nil { - return nil, err - } - for k, v := range secret.Data { - // decode with base64 - secretMap[k] = string(v) - } - return secretMap, nil -} - -func (c *Client) ApplySecret(name, namespace string, data map[string][]byte, labels map[string]string) (*corev1.Secret, error) { - secret := v1.Secret(name, namespace). - WithLabels(labels). - WithData(data). - WithImmutable(false) - - applyOptions := metav1.ApplyOptions{ - FieldManager: "DevStream", - } - - return c.clientset.CoreV1(). - Secrets(namespace). - Apply(context.Background(), secret, applyOptions) -} diff --git a/pkg/util/k8s/secret_test.go b/pkg/util/k8s/secret_test.go deleted file mode 100644 index 25b7d6d7f..000000000 --- a/pkg/util/k8s/secret_test.go +++ /dev/null @@ -1,53 +0,0 @@ -package k8s - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/kubernetes/fake" -) - -var _ = Describe("k8s secret methods", func() { - var ( - client *Client - namespace, secretName, secretKey, secretVal string - secretData map[string][]byte - ) - BeforeEach(func() { - client = &Client{} - namespace = "test" - secretName = "secret_name" - client.clientset = fake.NewSimpleClientset() - secretKey = "testSecret" - secretVal = "this is a test" - secretData = map[string][]byte{ - secretKey: []byte(secretVal), - } - }) - Context("GetSecret method", func() { - BeforeEach(func() { - testResources := []runtime.Object{ - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: secretName, - Namespace: namespace, - }, - Data: secretData, - }, - } - client.clientset = fake.NewSimpleClientset(testResources...) - }) - When("return secret", func() { - It("should return map", func() { - secret, err := client.GetSecret(namespace, secretName) - Expect(err).ShouldNot(HaveOccurred()) - val, ok := secret[secretKey] - Expect(ok).Should(BeTrue()) - Expect(val).Should(Equal(string(secretVal))) - }) - }) - }) -}) diff --git a/pkg/util/k8s/service.go b/pkg/util/k8s/service.go deleted file mode 100644 index eceb119c9..000000000 --- a/pkg/util/k8s/service.go +++ /dev/null @@ -1,29 +0,0 @@ -package k8s - -import ( - "context" - - corev1 "k8s.io/api/core/v1" - kerr "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/devstream-io/devstream/pkg/util/log" -) - -func (c *Client) GetService(namespace, name string) (*corev1.Service, error) { - return c.clientset.CoreV1().Services(namespace).Get(context.TODO(), name, metav1.GetOptions{}) -} - -func (c *Client) DeleteService(namespace, serviceName string) error { - return c.clientset.CoreV1().Services(namespace). - Delete(context.TODO(), serviceName, metav1.DeleteOptions{}) -} - -func (c *Client) CreateService(namespace string, service *corev1.Service) error { - _, err := c.clientset.CoreV1().Services(namespace).Create(context.TODO(), service, metav1.CreateOptions{}) - if kerr.IsAlreadyExists(err) { - log.Infof("The Service %s is already exists.", service.Name) - return nil - } - return err -} diff --git a/pkg/util/k8s/service_test.go b/pkg/util/k8s/service_test.go deleted file mode 100644 index ea5ead406..000000000 --- a/pkg/util/k8s/service_test.go +++ /dev/null @@ -1,90 +0,0 @@ -package k8s - -import ( - "context" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/kubernetes/fake" - - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -var _ = Describe("service methods", func() { - var ( - client *Client - serviceName, namespace string - ) - BeforeEach(func() { - serviceName = "testService" - namespace = "test" - client = &Client{} - client.clientset = fake.NewSimpleClientset() - }) - Context("GetService method", func() { - BeforeEach(func() { - testNameSpace := []runtime.Object{ - &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: serviceName, - Namespace: namespace, - }, - }, - } - client.clientset = fake.NewSimpleClientset(testNameSpace...) - }) - - It("should return service", func() { - service, err := client.GetService(namespace, serviceName) - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(service.Name).Should(Equal(serviceName)) - }) - }) - Context("DeleteService method", func() { - When("service not exist", func() { - BeforeEach(func() { - serviceName = "not_exist" - }) - It("should return error", func() { - err := client.DeleteService(namespace, serviceName) - Expect(err).Error().Should(HaveOccurred()) - }) - }) - When("service exist", func() { - BeforeEach(func() { - testNameSpace := []runtime.Object{ - &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: serviceName, - Namespace: namespace, - }, - }, - } - client.clientset = fake.NewSimpleClientset(testNameSpace...) - }) - - It("should delete service", func() { - err := client.DeleteService(namespace, serviceName) - Expect(err).Error().ShouldNot(HaveOccurred()) - _, err = client.clientset.CoreV1().Services(namespace).Get(context.TODO(), serviceName, metav1.GetOptions{}) - Expect(err).Error().Should(HaveOccurred()) - }) - }) - }) - Context("CreateService method", func() { - It("should work normal", func() { - service := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: serviceName, - Namespace: namespace, - }, - } - err := client.CreateService(namespace, service) - Expect(err).Error().ShouldNot(HaveOccurred()) - _, err = client.clientset.CoreV1().Services(namespace).Get(context.TODO(), serviceName, metav1.GetOptions{}) - Expect(err).Error().ShouldNot(HaveOccurred()) - }) - }) -}) diff --git a/pkg/util/k8s/state.go b/pkg/util/k8s/state.go deleted file mode 100644 index 6b18b81c6..000000000 --- a/pkg/util/k8s/state.go +++ /dev/null @@ -1,110 +0,0 @@ -package k8s - -import ( - "bytes" - - "gopkg.in/yaml.v3" - - "github.com/devstream-io/devstream/pkg/util/log" -) - -type ResourceStatus struct { - Name string - Ready bool -} - -type AllResourceStatus struct { - Deployment []ResourceStatus - StatefulSet []ResourceStatus - DaemonSet []ResourceStatus -} - -// GetResourceStatus get all resource state by input nameSpace and filtermap -func (c *Client) GetResourceStatus(nameSpace string, anFilter, labelFilter map[string]string) (*AllResourceStatus, error) { - stateMap := &AllResourceStatus{} - // 1. list deploy resource - dps, err := c.ListDeploymentsWithLabel(nameSpace, labelFilter) - if err != nil { - log.Debugf("Failed to list deployments: %s.", err) - return stateMap, err - } - - for _, dp := range dps { - matchFilterd := filterByAnnotation(dp.GetAnnotations(), anFilter) - if !matchFilterd { - log.Infof("Found unknown statefulSet: %s.", dp.GetName()) - continue - } - dpName := dp.GetName() - ready := isDeploymentReady(&dp) - stateMap.Deployment = append(stateMap.Deployment, ResourceStatus{dpName, ready}) - log.Debugf("The deployment %s is %t.", dp.GetName(), ready) - } - - // 2. list statefulsets resource - sts, err := c.ListStatefulsetsWithLabel(nameSpace, labelFilter) - if err != nil { - log.Debugf("Failed to list statefulsets: %s.", err) - return stateMap, err - } - - for _, ss := range sts { - matchFilterd := filterByAnnotation(ss.GetAnnotations(), anFilter) - if !matchFilterd { - log.Infof("Found unknown statefulSet: %s.", ss.GetName()) - continue - } - - ready := isStatefulsetReady(&ss) - ssName := ss.GetName() - stateMap.StatefulSet = append(stateMap.StatefulSet, ResourceStatus{ssName, ready}) - log.Debugf("The statefulset %s is %t.", ss.GetName(), ready) - } - - // 3. list daemonset resource - dss, err := c.ListDaemonsetsWithLabel(nameSpace, labelFilter) - if err != nil { - log.Debugf("Failed to list daemonsets: %s.", err) - return stateMap, err - } - - for _, ds := range dss { - matchFilterd := filterByAnnotation(ds.GetAnnotations(), anFilter) - if !matchFilterd { - log.Infof("Found unknown statefulSet: %s.", ds.GetName()) - continue - } - - ready := isDaemonsetReady(&ds) - dsName := ds.GetName() - stateMap.DaemonSet = append(stateMap.DaemonSet, ResourceStatus{dsName, ready}) - log.Debugf("The daemonset %s is %t.", ds.GetName(), ready) - } - return stateMap, nil -} - -func filterByAnnotation(anInfo map[string]string, anFilter map[string]string) bool { - for k, v := range anFilter { - anVal, exist := anInfo[k] - if !exist || anVal != v { - return false - } - } - return true -} - -func (s *AllResourceStatus) ToStringInterfaceMap() (map[string]interface{}, error) { - var buf bytes.Buffer - encoder := yaml.NewEncoder(&buf) - defer encoder.Close() - encoder.SetIndent(2) - err := encoder.Encode(s) - if err != nil { - return nil, err - } - wfs := buf.String() - - return map[string]interface{}{ - "workflows": wfs, - }, nil -} diff --git a/pkg/util/k8s/state_test.go b/pkg/util/k8s/state_test.go deleted file mode 100644 index 73f7010b8..000000000 --- a/pkg/util/k8s/state_test.go +++ /dev/null @@ -1,164 +0,0 @@ -package k8s - -import ( - "fmt" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/kubernetes/fake" - "k8s.io/utils/pointer" -) - -var _ = Describe("k8s state methods", func() { - var ( - client *Client - labelFilter map[string]string - anFilter map[string]string - namespace, testFilterKey, testFilterVal, testAnKey, testAnVal, successName, faildName string - ) - BeforeEach(func() { - client = &Client{} - namespace = "test" - testFilterKey = "filter_key" - testFilterVal = "filter_val" - testAnKey = "an_key" - testAnVal = "an_val" - successName = "test_success" - faildName = "test_fail" - anFilter = map[string]string{ - testAnKey: testAnVal, - } - labelFilter = map[string]string{ - testFilterKey: testFilterVal, - } - podTemplate := corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: labelFilter, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "test", - Image: "test", - }, - }, - }, - } - - depSpec := appsv1.DeploymentSpec{ - Replicas: pointer.Int32Ptr(int32(1)), - Selector: &metav1.LabelSelector{MatchLabels: labelFilter}, - Template: podTemplate, - } - dsSpec := appsv1.DaemonSetSpec{ - Template: podTemplate, - } - stsSpec := appsv1.StatefulSetSpec{ - Replicas: pointer.Int32Ptr(int32(1)), - Selector: &metav1.LabelSelector{MatchLabels: labelFilter}, - Template: podTemplate, - } - - testResources := []runtime.Object{ - &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: successName, - Labels: labelFilter, - Annotations: anFilter, - Namespace: namespace, - }, - Spec: depSpec, - }, - &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: faildName, - Labels: map[string]string{testFilterKey: "not_val"}, - Namespace: namespace, - }, - Spec: depSpec, - }, - &appsv1.DaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: successName, - Labels: labelFilter, - Namespace: namespace, - Annotations: anFilter, - }, - Spec: dsSpec, - }, - &appsv1.DaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: faildName, - Labels: labelFilter, - Annotations: map[string]string{testAnKey: "not_exist"}, - Namespace: namespace, - }, - Spec: dsSpec, - }, - &appsv1.StatefulSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: successName, - Labels: labelFilter, - Annotations: anFilter, - Namespace: namespace, - }, - Spec: stsSpec, - }, - &appsv1.StatefulSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: faildName, - Labels: labelFilter, - Namespace: namespace, - }, - Spec: stsSpec, - }, - } - client.clientset = fake.NewSimpleClientset(testResources...) - }) - Context("GetResourceStatus method", func() { - It("should work normal", func() { - allStatus, err := client.GetResourceStatus(namespace, anFilter, labelFilter) - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(len(allStatus.DaemonSet)).Should(Equal(1)) - Expect(allStatus.DaemonSet[0].Name).Should(Equal(successName)) - Expect(len(allStatus.Deployment)).Should(Equal(1)) - Expect(allStatus.Deployment[0].Name).Should(Equal(successName)) - Expect(len(allStatus.StatefulSet)).Should(Equal(1)) - Expect(allStatus.StatefulSet[0].Name).Should(Equal(successName)) - }) - }) -}) - -var _ = Describe("AllResourceStatus struct", func() { - var ( - depName string - depReady bool - s *AllResourceStatus - ) - Context("ToStringInterfaceMap method", func() { - BeforeEach(func() { - depName = "test_dep" - depReady = true - s = &AllResourceStatus{ - Deployment: []ResourceStatus{ - { - Name: "test_dep", - Ready: depReady, - }, - }, - } - }) - It("should work", func() { - m, err := s.ToStringInterfaceMap() - Expect(err).ShouldNot(HaveOccurred()) - expectVal := fmt.Sprintf("deployment:\n - name: %s\n ready: %t\nstatefulset: []\ndaemonset: []\n", depName, depReady) - Expect(m).Should(Equal(map[string]interface{}{ - "workflows": expectVal, - })) - }) - }) -}) diff --git a/pkg/util/k8s/storage.go b/pkg/util/k8s/storage.go deleted file mode 100644 index f4381e2b2..000000000 --- a/pkg/util/k8s/storage.go +++ /dev/null @@ -1,120 +0,0 @@ -package k8s - -import ( - "context" - - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/devstream-io/devstream/pkg/util/log" -) - -type PVOption struct { - Name string - StorageClassName string - - // ReadWriteOnce PersistentVolumeAccessMode = "ReadWriteOnce" - // ReadOnlyMany PersistentVolumeAccessMode = "ReadOnlyMany" - // ReadWriteMany PersistentVolumeAccessMode = "ReadWriteMany" - // ReadWriteOncePod PersistentVolumeAccessMode = "ReadWriteOncePod" - AccessMode []corev1.PersistentVolumeAccessMode - - // ::= Ki | Mi | Gi | Ti | Pi | Ei - // (International System of units; See: https://physics.nist.gov/cuu/Units/binary.html) - // ::= m | "" | k | M | G | T | P | E - // (Note that 1024 = 1Ki but 1000 = 1k; I didn't choose the capitalization.) - // eg: 10Gi 200Mi - Capacity string - - //PersistentVolumeReclaimRecycle PersistentVolumeReclaimPolicy = "Recycle" - //PersistentVolumeReclaimDelete PersistentVolumeReclaimPolicy = "Delete" - //PersistentVolumeReclaimRetain PersistentVolumeReclaimPolicy = "Retain" - PersistentVolumeReclaimPolicy corev1.PersistentVolumeReclaimPolicy - HostPath string -} - -type PVCOption struct { - Name string - NameSpace string - StorageClassName string - - AccessMode []corev1.PersistentVolumeAccessMode - - Requirement corev1.ResourceRequirements -} - -func (c *Client) CreatePersistentVolume(option *PVOption) error { - quantity, err := resource.ParseQuantity(option.Capacity) - if err != nil { - log.Errorf("Failed to parse the Capacity string: %s.", err) - return err - } - - pv := &corev1.PersistentVolume{ - ObjectMeta: metav1.ObjectMeta{ - Name: option.Name, - }, - Spec: corev1.PersistentVolumeSpec{ - StorageClassName: option.StorageClassName, - AccessModes: option.AccessMode, - Capacity: map[corev1.ResourceName]resource.Quantity{ - corev1.ResourceStorage: quantity, - }, - PersistentVolumeReclaimPolicy: option.PersistentVolumeReclaimPolicy, - PersistentVolumeSource: corev1.PersistentVolumeSource{ - HostPath: &corev1.HostPathVolumeSource{ - Path: option.HostPath, - }, - }, - }, - } - - _, err = c.clientset.CoreV1().PersistentVolumes().Create(context.TODO(), pv, metav1.CreateOptions{}) - if err != nil { - log.Errorf("Failed to create PersistentVolume < %s >: %s.", pv.Name, err) - return err - } - - log.Debugf("The PersistentVolume < %s > has created.", pv.Name) - return nil -} - -func (c *Client) DeletePersistentVolume(pvName string) error { - if err := c.clientset.CoreV1().PersistentVolumes(). - Delete(context.TODO(), pvName, metav1.DeleteOptions{}); err != nil { - return err - } - return nil -} - -func (c *Client) CreatePersistentVolumeClaim(opt *PVCOption) error { - pvc := &corev1.PersistentVolumeClaim{ - ObjectMeta: metav1.ObjectMeta{ - Name: opt.Name, - }, - Spec: corev1.PersistentVolumeClaimSpec{ - AccessModes: opt.AccessMode, - Resources: opt.Requirement, - StorageClassName: &opt.StorageClassName, - }, - } - - log.Debugf("The PersistentVolumeCliam option for creation is %v", pvc) - _, err := c.clientset.CoreV1().PersistentVolumeClaims(opt.NameSpace).Create(context.TODO(), pvc, metav1.CreateOptions{}) - if err != nil { - log.Errorf("Failed to create PersistentVolumeClaim < %s >: %s.", pvc.Name, err) - return err - } - - log.Debugf("The PersistentVolumeClaim < %s > has created.", pvc.Name) - return nil -} - -func (c *Client) DeletePersistentVolumeClaim(namespace, pvcName string) error { - if err := c.clientset.CoreV1().PersistentVolumeClaims(namespace). - Delete(context.TODO(), pvcName, metav1.DeleteOptions{}); err != nil { - return err - } - return nil -} diff --git a/pkg/util/k8s/workload.go b/pkg/util/k8s/workload.go deleted file mode 100644 index f69784f87..000000000 --- a/pkg/util/k8s/workload.go +++ /dev/null @@ -1,111 +0,0 @@ -package k8s - -import ( - "context" - "errors" - "time" - - appsv1 "k8s.io/api/apps/v1" - kerr "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" - - "github.com/devstream-io/devstream/pkg/util/log" -) - -func (c *Client) ListDeploymentsWithLabel(namespace string, labelFilter map[string]string) ([]appsv1.Deployment, error) { - dpList, err := c.clientset.AppsV1().Deployments(namespace).List(context.TODO(), generateLabelFilterOption(labelFilter)) - if err != nil { - return nil, err - } - return dpList.Items, nil -} - -func (c *Client) GetDeployment(namespace, name string) (*appsv1.Deployment, error) { - return c.clientset.AppsV1().Deployments(namespace).Get(context.TODO(), name, metav1.GetOptions{}) -} - -func (c *Client) CreateDeployment(namespace string, deployment *appsv1.Deployment) error { - if _, err := c.clientset.AppsV1().Deployments(namespace).Create(context.TODO(), deployment, metav1.CreateOptions{}); err != nil { - if !kerr.IsAlreadyExists(err) { - return err - } - log.Infof("The Deployment %s is already exists.", deployment.Name) - } - log.Debugf("The Deployment %s has been created.", deployment.Name) - return nil -} - -// Wait for deployment to be ready after creating -func (c *Client) WaitForDeploymentReady(retry int, namespace, deployName string) error { - deployRunning := false - for i := 0; i < retry; i++ { - var dp *appsv1.Deployment - dp, err := c.GetDeployment(namespace, deployName) - if err != nil { - return err - } - - if isDeploymentReady(dp) { - log.Infof("The deployment %s is ready.", dp.Name) - deployRunning = true - break - } - time.Sleep(5 * time.Second) - log.Debugf("Retry check deployment status %v times", i) - } - - if !deployRunning { - return errors.New("create deployment failed") - } - return nil -} - -func (c *Client) DeleteDeployment(namespace, deployName string) error { - return c.clientset.AppsV1().Deployments(namespace). - Delete(context.TODO(), deployName, metav1.DeleteOptions{}) -} - -func (c *Client) ListDaemonsetsWithLabel(namespace string, labeFilter map[string]string) ([]appsv1.DaemonSet, error) { - dsList, err := c.clientset.AppsV1().DaemonSets(namespace).List(context.TODO(), generateLabelFilterOption(labeFilter)) - if err != nil { - return nil, err - } - return dsList.Items, nil -} - -func (c *Client) GetDaemonset(namespace, name string) (*appsv1.DaemonSet, error) { - return c.clientset.AppsV1().DaemonSets(namespace).Get(context.TODO(), name, metav1.GetOptions{}) -} - -func (c *Client) ListStatefulsetsWithLabel(namespace string, labelFilter map[string]string) ([]appsv1.StatefulSet, error) { - ssList, err := c.clientset.AppsV1().StatefulSets(namespace).List(context.TODO(), generateLabelFilterOption(labelFilter)) - if err != nil { - return nil, err - } - return ssList.Items, nil -} - -func (c *Client) GetStatefulset(namespace, name string) (*appsv1.StatefulSet, error) { - return c.clientset.AppsV1().StatefulSets(namespace).Get(context.TODO(), name, metav1.GetOptions{}) -} - -func generateLabelFilterOption(labelFilter map[string]string) metav1.ListOptions { - labelSelector := metav1.LabelSelector{MatchLabels: labelFilter} - options := metav1.ListOptions{ - LabelSelector: labels.Set(labelSelector.MatchLabels).String(), - } - return options -} - -func isDeploymentReady(deployment *appsv1.Deployment) bool { - return deployment.Status.ReadyReplicas == *deployment.Spec.Replicas -} - -func isDaemonsetReady(daemonset *appsv1.DaemonSet) bool { - return daemonset.Status.NumberReady == daemonset.Status.DesiredNumberScheduled -} - -func isStatefulsetReady(statefulset *appsv1.StatefulSet) bool { - return statefulset.Status.ReadyReplicas == *statefulset.Spec.Replicas -} diff --git a/pkg/util/kubectl/cmd.go b/pkg/util/kubectl/cmd.go deleted file mode 100644 index 9e6146958..000000000 --- a/pkg/util/kubectl/cmd.go +++ /dev/null @@ -1,73 +0,0 @@ -package kubectl - -import ( - "fmt" - "io" - "os" - "os/exec" - "strings" - - "github.com/devstream-io/devstream/pkg/util/log" -) - -const ( - Create string = "create" - Apply string = "apply" - Delete string = "delete" -) - -// KubeCreate runs "kubectl create -f filename" -func KubeCreate(filename string) error { - return kubectlAction(Create, filename) -} - -// KubeCreateFromIOReader generates a temp file from io.Reader and runs "kubectl create -f filename" -func KubeCreateFromIOReader(reader io.Reader) error { - return ioToFileWrapper(reader, KubeCreate) -} - -// KubeApply runs "kubectl apply -f filename" -func KubeApply(filename string) error { - return kubectlAction(Apply, filename) -} - -// KubeApplyFromIOReader generates a temp file from io.Reader and runs "kubectl apply -f filename" -func KubeApplyFromIOReader(reader io.Reader) error { - return ioToFileWrapper(reader, KubeApply) -} - -// KubeDelete runs "kubectl delete -f filename" -func KubeDelete(filename string) error { - return kubectlAction(Delete, filename) -} - -// KubeDeleteFromIOReader generates a temp file from io.Reader and runs "kubectl delete -f filename" -func KubeDeleteFromIOReader(reader io.Reader) error { - return ioToFileWrapper(reader, KubeDelete) -} - -func kubectlAction(action string, filename string) error { - cmd := exec.Command("kubectl", action, "-f", filename) - cOut, err := cmd.CombinedOutput() - if err != nil { - return fmt.Errorf("failed to exec: < %s >.\nExec logs: < %s >. Got error: %w", cmd.String(), string(cOut), err) - } - log.Info(strings.TrimSuffix(string(cOut), "\n")) - return nil -} - -const defaultTempName = "kubectl_temp" - -func ioToFileWrapper(reader io.Reader, f func(filename string) error) error { - tempFile, err := os.CreateTemp("", defaultTempName) - if err != nil { - return err - } - defer tempFile.Close() - - _, err = io.Copy(tempFile, reader) - if err != nil { - return err - } - return f(tempFile.Name()) -} diff --git a/pkg/util/kubectl/cmd_test.go b/pkg/util/kubectl/cmd_test.go deleted file mode 100644 index db8506914..000000000 --- a/pkg/util/kubectl/cmd_test.go +++ /dev/null @@ -1,80 +0,0 @@ -package kubectl - -import ( - "testing" -) - -var fileName = "nginx-deployment.yaml" - -func TestKubeCreate(t *testing.T) { - tests := []struct { - name string - filename string - wantErr bool - }{ - // TODO: Add test cases. - {"base", fileName, true}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := KubeCreate(tt.filename); (err != nil) != tt.wantErr { - t.Errorf("KubeCreate() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func TestKubeApply(t *testing.T) { - tests := []struct { - name string - filename string - wantErr bool - }{ - // TODO: Add test cases. - {"base", fileName, true}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := KubeApply(tt.filename); (err != nil) != tt.wantErr { - t.Errorf("KubeApply() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func TestKubeDelete(t *testing.T) { - tests := []struct { - name string - filename string - wantErr bool - }{ - // TODO: Add test cases. - {"base", fileName, true}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := KubeDelete(tt.filename); (err != nil) != tt.wantErr { - t.Errorf("KubeDelete() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func Test_kubectlAction(t *testing.T) { - tests := []struct { - name string - action string - filename string - wantErr bool - }{ - // TODO: Add test cases. - {"base", Apply, fileName, true}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := kubectlAction(tt.action, tt.filename); (err != nil) != tt.wantErr { - t.Errorf("kubectlAction() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} diff --git a/pkg/util/kubectl/errors.go b/pkg/util/kubectl/errors.go deleted file mode 100644 index b38d7c560..000000000 --- a/pkg/util/kubectl/errors.go +++ /dev/null @@ -1,5 +0,0 @@ -package kubectl - -import "github.com/devstream-io/devstream/pkg/util/pkgerror" - -var ArgocdApplicationNotExist pkgerror.ErrorMessage = "not found" diff --git a/pkg/util/log/log_test.go b/pkg/util/log/log_test.go deleted file mode 100644 index 6029a195e..000000000 --- a/pkg/util/log/log_test.go +++ /dev/null @@ -1,23 +0,0 @@ -package log - -import ( - "testing" -) - -func TestLog(t *testing.T) { - Debug("nice to meet you ", "log") - Info("nice to meet you ", "log") - Warn("nice to meet you ", "log") - Success("nice to meet you ", "log") - Error("nice to meet you ", "log") - // Fatal("nice to meet you ", "log") // fatal calling os.exit will cause test case exit with 1 - Separator("nice to meet you ", "log") - - Debugf("nice to meet you %s", "log") - Infof("nice to meet you %s", "log") - Warnf("nice to meet you %s", "log") - Successf("nice to meet you %s", "log") - Errorf("nice to meet you %s", "log") - // Fatalf("nice to meet you %s", "log") - Separatorf("nice to meet you %s", "log") -} diff --git a/pkg/util/log/logwrapper_test.go b/pkg/util/log/logwrapper_test.go deleted file mode 100644 index a22f79d4d..000000000 --- a/pkg/util/log/logwrapper_test.go +++ /dev/null @@ -1,224 +0,0 @@ -package log - -import ( - "bytes" - "fmt" - "reflect" - "runtime" - "testing" - - "github.com/sirupsen/logrus" - "gopkg.in/gookit/color.v1" -) - -func TestCliLoggerFormatter_Format(t *testing.T) { - emptyEntry := &logrus.Entry{} - entry := &logrus.Entry{ - Level: logrus.ErrorLevel, - Message: "hi log", - } - emptyFormatter := &CliLoggerFormatter{} - formatter := &CliLoggerFormatter{ - prefix: "pp", - showType: "error", - } - tests := []struct { - name string - formatter *CliLoggerFormatter - entry *logrus.Entry - want []byte - wantErr bool - }{ - {"base", emptyFormatter, emptyEntry, createBufferForCliLoggerFormatter(t, emptyFormatter, emptyEntry).Bytes(), false}, - {"base debug", formatter, entry, createBufferForCliLoggerFormatter2(t, formatter, entry).Bytes(), false}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // logrus.SetLevel(logrus.DebugLevel) - got, err := tt.formatter.Format(tt.entry) - if (err != nil) != tt.wantErr { - t.Errorf("CliLoggerFormatter.Format() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("CliLoggerFormatter.Format() = \n%v, want \n%v", got, tt.want) - } - }) - } -} - -func TestCliLoggerFormatter_levelPrintRender(t *testing.T) { - tests := []struct { - name string - formatter *CliLoggerFormatter - want *CliLoggerFormatter - }{ - // TODO: Add test cases. - {"base", &CliLoggerFormatter{}, &CliLoggerFormatter{}}, - {"base debug", - &CliLoggerFormatter{showType: "debug"}, - &CliLoggerFormatter{ - level: logrus.DebugLevel, - showType: "debug", - formatLevelName: color.Blue.Render(DEBUG), - prefix: color.Blue.Render(normal.Debug)}, - }, - {"base info", - &CliLoggerFormatter{showType: "info"}, - &CliLoggerFormatter{ - level: logrus.InfoLevel, - showType: "info", - formatLevelName: color.FgLightBlue.Render(INFO), - prefix: color.FgLightBlue.Render(normal.Info)}, - }, - {"base warn", - &CliLoggerFormatter{showType: "warn"}, - &CliLoggerFormatter{ - level: logrus.WarnLevel, - showType: "warn", - formatLevelName: color.Yellow.Render(WARN), - prefix: color.Yellow.Render(normal.Warn)}, - }, - {"base error", - &CliLoggerFormatter{showType: "error"}, - &CliLoggerFormatter{ - level: logrus.ErrorLevel, - showType: "error", - formatLevelName: color.BgRed.Render(ERROR), - prefix: color.Red.Render(normal.Error)}, - }, - {"base fatal", - &CliLoggerFormatter{showType: "fatal"}, - &CliLoggerFormatter{ - level: logrus.FatalLevel, - showType: "fatal", - formatLevelName: color.BgRed.Render(FATAL), - prefix: color.Red.Render(normal.Fatal)}, - }, - {"base success", - &CliLoggerFormatter{showType: "success"}, - &CliLoggerFormatter{ - level: logrus.InfoLevel, - showType: "success", - formatLevelName: color.Green.Render(SUCCESS), - prefix: color.Green.Render(normal.Success)}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.formatter.levelPrintRender() - if !reflect.DeepEqual(tt.formatter, tt.want) { - t.Errorf("levelPrintRender = \n%v", tt.formatter) - t.Errorf("want = \n%v", tt.want) - } - }) - } -} - -func TestSeparatorFormatter_Format(t *testing.T) { - b := createBufferForSeparatorFormatter(t) - tests := []struct { - name string - s *SeparatorFormatter - entry *logrus.Entry - want []byte - wantErr bool - }{ - // TODO: Add test cases. - {"base", &SeparatorFormatter{}, &logrus.Entry{}, b.Bytes(), false}, - {"base Entry with buffer", &SeparatorFormatter{}, &logrus.Entry{Buffer: &bytes.Buffer{}}, b.Bytes(), false}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := &SeparatorFormatter{} - got, err := s.Format(tt.entry) - if (err != nil) != tt.wantErr { - t.Errorf("SeparatorFormatter.Format() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("SeparatorFormatter.Format() = \n%v", string(got)) - t.Errorf("want = \n%v", tt.want) - } - }) - } -} - -func createBufferForSeparatorFormatter(t *testing.T) *bytes.Buffer { - var b *bytes.Buffer = &bytes.Buffer{} - entry := &logrus.Entry{} - timestamp := entry.Time.Format("2006-01-02 15:04:05") - newLog := fmt.Sprintf("%s %s %s %s\n", - timestamp, - color.Blue.Render(normal.Info), - color.Blue.Render(INFO), - color.Blue.Render(fmt.Sprintf("%s %s %s", "-------------------- [ ", entry.Message, " ] --------------------"))) - - _, err := b.WriteString(newLog) - if err != nil { - t.Error(err) - return nil - } - return b -} - -func createBufferForCliLoggerFormatter(t *testing.T, m *CliLoggerFormatter, entry *logrus.Entry) *bytes.Buffer { - var b *bytes.Buffer = &bytes.Buffer{} - m.levelPrintRender() - - timestamp := entry.Time.Format("2006-01-02 15:04:05") - - newLog := fmt.Sprintf("%s %s %s %s\n", timestamp, m.prefix, m.formatLevelName, entry.Message) - - _, err := b.WriteString(newLog) - if err != nil { - t.Error(err) - return nil - } - return b -} - -func createBufferForCliLoggerFormatter2(t *testing.T, m *CliLoggerFormatter, entry *logrus.Entry) *bytes.Buffer { - var b *bytes.Buffer = &bytes.Buffer{} - m.levelPrintRender() - - timestamp := entry.Time.Format("2006-01-02 15:04:05") - - entry.Message = addCallStackIgnoreLogrus(entry.Message) - - newLog := fmt.Sprintf("%s %s %s %s\n", timestamp, m.prefix, m.formatLevelName, entry.Message) - - _, err := b.WriteString(newLog) - if err != nil { - t.Error(err) - return nil - } - return b -} - -func Test_addCallStackIgnoreLogrus(t *testing.T) { - rawMsg := "hi log" - tests := []struct { - name string - rawMessage string - want string - }{ - // TODO: Add test cases. - {"base", rawMsg, wantMsgOfAddCallStackIgnoreLogrus(rawMsg)}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := addCallStackIgnoreLogrus(tt.rawMessage); got != tt.want { - t.Errorf("addCallStackIgnoreLogrus() = \n%v, \nwant = %v", got, tt.want) - } - }) - } -} - -func wantMsgOfAddCallStackIgnoreLogrus(stackMessage string) string { - retMsg := stackMessage - i := 10 - _, file, line, _ := runtime.Caller(i) - retMsg = retMsg + "\n -- " + file + fmt.Sprintf(" %d", line) - return retMsg -} diff --git a/pkg/util/log/symbol_test.go b/pkg/util/log/symbol_test.go deleted file mode 100644 index e42add31b..000000000 --- a/pkg/util/log/symbol_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package log - -import "testing" - -func TestSymbols_String(t *testing.T) { - - tests := []struct { - name string - symbols Symbols - want string - }{ - // TODO: Add test cases. - {"base", Symbols{}, "Debug: Info: Success: Warning: Error: Fatal: "}, - {"base", Symbols{Debug: "Debug"}, "Debug: Debug Info: Success: Warning: Error: Fatal: "}, - {"base", Symbols{ - Debug: Symbol("λ"), - Info: Symbol("ℹ"), - Success: Symbol("✔"), - Warning: Symbol("⚠"), - Error: Symbol("!!"), - Fatal: Symbol("✖"), - }, "Debug: λ Info: ℹ Success: ✔ Warning: ⚠ Error: !! Fatal: ✖"}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := Symbols{ - Debug: tt.symbols.Debug, - Info: tt.symbols.Info, - Warning: tt.symbols.Warning, - Warn: tt.symbols.Warn, - Error: tt.symbols.Error, - Fatal: tt.symbols.Fatal, - Success: tt.symbols.Success, - } - if got := s.String(); got != tt.want { - t.Errorf("\nSymbols.String() = %v, \nwant = %v", got, tt.want) - } - }) - } -} diff --git a/pkg/util/mapz/concurrentmap/concurrentmap.go b/pkg/util/mapz/concurrentmap/concurrentmap.go deleted file mode 100644 index 433da32ec..000000000 --- a/pkg/util/mapz/concurrentmap/concurrentmap.go +++ /dev/null @@ -1,56 +0,0 @@ -package concurrentmap - -import ( - "fmt" - "reflect" - "sync" -) - -type ConcurrentMap struct { - *sync.Map - KeyType reflect.Type - ValueType reflect.Type -} - -func NewConcurrentMap(keyType, valueType interface{}) *ConcurrentMap { - return &ConcurrentMap{ - Map: &sync.Map{}, - KeyType: reflect.TypeOf(keyType), - ValueType: reflect.TypeOf(valueType), - } -} - -func (cm *ConcurrentMap) Load(key interface{}) (value interface{}, ok bool) { - if reflect.TypeOf(key) == cm.KeyType { - return cm.Map.Load(key) - } - return -} - -func (cm *ConcurrentMap) Store(key, value interface{}) { - if reflect.TypeOf(key) == cm.KeyType && reflect.TypeOf(value) == cm.ValueType { - cm.Map.Store(key, value) - return - } - panic(fmt.Errorf("wrong key or value type: %v, %v", reflect.TypeOf(key), reflect.TypeOf(value))) -} - -func (cm *ConcurrentMap) Delete(key interface{}) { - if reflect.TypeOf(key) == cm.KeyType { - cm.Map.Delete(key) - return - } - panic(fmt.Errorf("wrong key type: %v", reflect.TypeOf(key))) -} - -func (cm *ConcurrentMap) LoadOrStore(key, value interface{}) (actual interface{}, loaded bool) { - if reflect.TypeOf(key) == cm.KeyType && reflect.TypeOf(value) == cm.ValueType { - actual, loaded = cm.Map.LoadOrStore(key, value) - return - } - panic(fmt.Errorf("wrong key or value type: %v, %v", reflect.TypeOf(key), reflect.TypeOf(value))) -} - -func (cm *ConcurrentMap) Range(f func(key, value interface{}) bool) { - cm.Map.Range(f) -} diff --git a/pkg/util/mapz/concurrentmap/concurrentmap_suit_test.go b/pkg/util/mapz/concurrentmap/concurrentmap_suit_test.go deleted file mode 100644 index 19bbc0768..000000000 --- a/pkg/util/mapz/concurrentmap/concurrentmap_suit_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package concurrentmap_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestPlanmanager(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Util Concurrentmap Suite") -} diff --git a/pkg/util/mapz/concurrentmap/concurrentmap_test.go b/pkg/util/mapz/concurrentmap/concurrentmap_test.go deleted file mode 100644 index 14e119c55..000000000 --- a/pkg/util/mapz/concurrentmap/concurrentmap_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package concurrentmap_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/pkg/util/mapz/concurrentmap" -) - -var _ = Describe("Concurrentmap", func() { - Context("CURD", func() { - It("Should has 1 item", func() { - cMap := concurrentmap.NewConcurrentMap("", "") - cMap.Store("key1", "value1") - v1, ok1 := cMap.Load("key1") - Expect(ok1).To(BeTrue()) - Expect(v1.(string)).To(Equal("value1")) - - v2, ok2 := cMap.Load("key2") - Expect(ok2).To(BeFalse()) - Expect(v2).To(BeNil()) - - cMap.Delete("key1") - v3, ok3 := cMap.Load("key1") - Expect(ok3).To(BeFalse()) - Expect(v3).To(BeNil()) - }) - }) -}) diff --git a/pkg/util/mapz/map.go b/pkg/util/mapz/map.go deleted file mode 100644 index 5c6b03680..000000000 --- a/pkg/util/mapz/map.go +++ /dev/null @@ -1,34 +0,0 @@ -package mapz - -import ( - "github.com/mitchellh/mapstructure" - "golang.org/x/exp/maps" -) - -func FillMapWithStrAndError(keys []string, value error) map[string]error { - retMap := make(map[string]error, len(keys)) - if len(keys) == 0 { - return retMap - } - - for _, key := range keys { - retMap[key] = value - } - return retMap -} - -func DecodeStructToMap(structVars any) (map[string]interface{}, error) { - var rawConfigVars map[string]interface{} - if err := mapstructure.Decode(structVars, &rawConfigVars); err != nil { - return nil, err - } - return rawConfigVars, nil -} - -// Merge merge two maps -// if there are same keys in two maps, the key of second one will overwrite the first one -func Merge(m1 map[string]any, m2 map[string]any) map[string]any { - m1Clone := maps.Clone(m1) - maps.Copy(m1Clone, m2) - return m1Clone -} diff --git a/pkg/util/mapz/map_suit_test.go b/pkg/util/mapz/map_suit_test.go deleted file mode 100644 index 75d4e19a5..000000000 --- a/pkg/util/mapz/map_suit_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package mapz - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestPlanmanager(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Util Mapz Suite") -} diff --git a/pkg/util/mapz/map_test.go b/pkg/util/mapz/map_test.go deleted file mode 100644 index 7ce536200..000000000 --- a/pkg/util/mapz/map_test.go +++ /dev/null @@ -1,70 +0,0 @@ -package mapz_test - -import ( - "fmt" - "strconv" - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/pkg/util/mapz" -) - -var _ = Describe("Mapz", func() { - keys := []string{ - "key1", "key2", - } - value := fmt.Errorf("error") - expectMap := map[string]error{ - "key1": value, - "key2": value, - } - retMap1 := mapz.FillMapWithStrAndError(keys, value) - It("should be a map with 2 items", func() { - Expect(retMap1).Should(Equal(expectMap)) - }) - - retMap2 := mapz.FillMapWithStrAndError(nil, value) - It("should be a map with 0 item", func() { - Expect(len(retMap2)).To(Equal(0)) - }) -}) - -func BenchmarkFillMapWithStrAndError(b *testing.B) { - keys_length := 100 - keys := make([]string, 0, keys_length) - for i := 0; i < keys_length; i++ { - keys = append(keys, "key"+strconv.Itoa(i)) - } - value := fmt.Errorf("error") - b.ResetTimer() - for i := 0; i < b.N; i++ { - _ = mapz.FillMapWithStrAndError(keys, value) - } - b.StopTimer() -} - -var _ = Describe("Merge func", func() { - var ( - src1, src2 map[string]any - ) - BeforeEach(func() { - src1 = map[string]any{ - "test1": "test1", - "test": "test", - } - src2 = map[string]any{ - "test1": "cover", - "test2": "test2", - } - }) - It("should merge maps", func() { - allMaps := mapz.Merge(src1, src2) - Expect(allMaps).Should(Equal(map[string]any{ - "test1": "cover", - "test": "test", - "test2": "test2", - })) - }) -}) diff --git a/pkg/util/md5/helper.go b/pkg/util/md5/helper.go deleted file mode 100644 index 2fc24c4c7..000000000 --- a/pkg/util/md5/helper.go +++ /dev/null @@ -1,32 +0,0 @@ -package md5 - -import ( - "crypto/md5" - "fmt" - "io" - "os" -) - -// CalcFileMD5 calculate file md5 -func CalcFileMD5(filename string) (string, error) { - f, err := os.Open(filename) - if nil != err { - return "", err - } - defer f.Close() - - return CalcMD5(f) -} - -func CalcMD5(r io.Reader) (string, error) { - if r == nil { - return "", fmt.Errorf("reader is nil") - } - md5Handle := md5.New() - if _, err := io.Copy(md5Handle, r); nil != err { - return "", err - } - md := md5Handle.Sum(nil) - md5str := fmt.Sprintf("%x", md) - return md5str, nil -} diff --git a/pkg/util/md5/helper_test.go b/pkg/util/md5/helper_test.go deleted file mode 100644 index a2a7547c4..000000000 --- a/pkg/util/md5/helper_test.go +++ /dev/null @@ -1,57 +0,0 @@ -package md5 - -import ( - "io" - "strings" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -var _ = Describe("CalcMD5 func", func() { - var ( - reader io.Reader - md5 string - err error - - emptyReader = strings.NewReader("") - emtpyMd5 = "d41d8cd98f00b204e9800998ecf8427e" - testReader = strings.NewReader("test") - testMd5 = "098f6bcd4621d373cade4e832627b4f6" - errReader io.Reader - ) - - JustBeforeEach(func() { - md5, err = CalcMD5(reader) - }) - - When("reader is empty", func() { - BeforeEach(func() { - reader = emptyReader - }) - It("should return empty md5", func() { - Expect(md5).To(Equal(emtpyMd5)) - Expect(err).ToNot(HaveOccurred()) - }) - }) - - When("reader is not empty", func() { - BeforeEach(func() { - reader = testReader - }) - It("should return md5", func() { - Expect(md5).To(Equal(testMd5)) - Expect(err).ToNot(HaveOccurred()) - }) - }) - - When("reader is invalid", func() { - BeforeEach(func() { - reader = errReader - }) - It("should return error", func() { - Expect(err).To(HaveOccurred()) - Expect(md5).To(BeEmpty()) - }) - }) -}) diff --git a/pkg/util/md5/md5.go b/pkg/util/md5/md5.go deleted file mode 100644 index 797c20460..000000000 --- a/pkg/util/md5/md5.go +++ /dev/null @@ -1,40 +0,0 @@ -package md5 - -import ( - "os" - - "k8s.io/utils/strings" -) - -const md5Length = 32 - -// FileMatchesMD5 checks current PlugIn MD5 matches with .md5 file -func FileMatchesMD5(fileName, md5FileName string) (bool, error) { - currentPlugInMD5, err := CalcFileMD5(fileName) - if err != nil { - return false, err - } - - md5ContentBytes, err := os.ReadFile(md5FileName) - if err != nil { - return false, err - } - // intercept string, md5 code length is 32 - md5Content := strings.ShortenString(string(md5ContentBytes), md5Length) - - return currentPlugInMD5 == md5Content, nil -} - -func FilesMD5Equal(file1, file2 string) (bool, error) { - file1MD5, err := CalcFileMD5(file1) - if err != nil { - return false, err - } - - file2MD5, err := CalcFileMD5(file2) - if err != nil { - return false, err - } - - return file1MD5 == file2MD5, nil -} diff --git a/pkg/util/md5/md5_suite_test.go b/pkg/util/md5/md5_suite_test.go deleted file mode 100644 index 75b9ea26d..000000000 --- a/pkg/util/md5/md5_suite_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package md5_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestMd5(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Md5 Suite") -} diff --git a/pkg/util/md5/md5_test.go b/pkg/util/md5/md5_test.go deleted file mode 100644 index c7c9919aa..000000000 --- a/pkg/util/md5/md5_test.go +++ /dev/null @@ -1,70 +0,0 @@ -package md5 - -import ( - "fmt" - "os" - "testing" - - "github.com/stretchr/testify/assert" -) - -// TestValidatePlugInMD5Exist file exists, md5 info matches with .md5 -func TestValidateFileMatchMD5(t *testing.T) { - fileName := "test1" - - md5FileName, err := createFileAndMD5File(fileName) - assert.NoError(t, err) - - exist, err := FileMatchesMD5(fileName, md5FileName) - assert.NoError(t, err) - assert.True(t, exist) - - os.Remove(fileName) - os.Remove(fileName + ".md5") -} - -// TestValidatePlugInMD5NotExist file exists, but md5 info does not match with .md5 -func TestValidateFileMatchMD5NotExist(t *testing.T) { - fileNameMisMatch := "test1_not_in_md5string.so" - fileName := "test1" - - _, err := os.Create(fileNameMisMatch) - assert.NoError(t, err) - - md5FileName, err := createFileAndMD5File(fileName) - assert.NoError(t, err) - - exist, err := FileMatchesMD5(fileName, md5FileName) - assert.NoError(t, err) - assert.True(t, exist) - - os.Remove(fileName) - os.Remove(fileName + ".md5") - os.Remove(fileNameMisMatch) -} - -func createFileAndMD5File(fileName string) (string, error) { - f1, err := os.Create(fileName) - if err != nil { - return "", err - } - - defer f1.Close() - - md5, err := CalcFileMD5(fileName) - if err != nil { - return "", err - } - md5FileName := fmt.Sprint(fileName, ".md5") - md5File, err := os.Create(md5FileName) - if err != nil { - return "", err - } - defer md5File.Close() - - _, err = md5File.Write([]byte(md5)) - if err != nil { - return "", err - } - return md5FileName, nil -} diff --git a/pkg/util/pkgerror/pluginerror.go b/pkg/util/pkgerror/pluginerror.go deleted file mode 100644 index b0272876d..000000000 --- a/pkg/util/pkgerror/pluginerror.go +++ /dev/null @@ -1,43 +0,0 @@ -package pkgerror - -import ( - "fmt" - "strings" -) - -type ErrorMessage string - -// PluginError is plugin's error wrapper -type PluginError struct { - // which plugin the error raises - PluginName string - // error message - Message string - // error extra module info - Module string -} - -func (e *PluginError) Error() string { - moduleMsg := "" - if e.Module != "" { - moduleMsg = fmt.Sprintf("(%s)", e.Module) - } - return fmt.Sprintf("[%s]%s: %s", e.PluginName, moduleMsg, e.Message) -} - -func CheckErrorMatchByMessage(targetError error, errCheckStrings ...ErrorMessage) bool { - for _, checkErr := range errCheckStrings { - if strings.Contains(targetError.Error(), string(checkErr)) { - return true - } - } - return false -} - -func NewErrorFromPlugin(pluginName, moduleName string, err error) *PluginError { - return &PluginError{ - PluginName: pluginName, - Message: err.Error(), - Module: moduleName, - } -} diff --git a/pkg/util/random/token.go b/pkg/util/random/token.go deleted file mode 100644 index 526802b89..000000000 --- a/pkg/util/random/token.go +++ /dev/null @@ -1,14 +0,0 @@ -package random - -import ( - "fmt" - "math/rand" - "time" -) - -func GenerateRandomSecretToken() string { - rand.Seed(time.Now().UnixNano()) - b := make([]byte, 32) - rand.Read(b) - return fmt.Sprintf("%x", b)[:32] -} diff --git a/pkg/util/scm/client.go b/pkg/util/scm/client.go deleted file mode 100644 index fcb0bf186..000000000 --- a/pkg/util/scm/client.go +++ /dev/null @@ -1,65 +0,0 @@ -package scm - -import ( - "github.com/devstream-io/devstream/pkg/util/log" - "github.com/devstream-io/devstream/pkg/util/scm/git" - "github.com/devstream-io/devstream/pkg/util/scm/github" - "github.com/devstream-io/devstream/pkg/util/scm/gitlab" -) - -func NewClientWithAuth(repoInfo *git.RepoInfo) (ClientOperation, error) { - repoInfo.NeedAuth = true - return NewClient(repoInfo) -} - -func NewClient(repoInfo *git.RepoInfo) (ClientOperation, error) { - if err := repoInfo.SetDefault(); err != nil { - return nil, err - } - switch repoInfo.RepoType { - case "github": - return github.NewClient(repoInfo) - default: - // default use gitlab repo - return gitlab.NewClient(repoInfo) - } -} - -type ClientOperation interface { - InitRepo() error - DeleteRepo() error - DownloadRepo() (string, error) - DescribeRepo() (*git.RepoInfo, error) - PushFiles(commitInfo *git.CommitInfo, checkUpdate bool) (bool, error) - DeleteFiles(commitInfo *git.CommitInfo) error - GetPathInfo(path string) ([]*git.RepoFileStatus, error) - AddWebhook(webhookConfig *git.WebhookConfig) error - DeleteWebhook(webhookConfig *git.WebhookConfig) error - AddRepoSecret(secretKey, secretValue string) error -} - -func PushInitRepo(client ClientOperation, commitInfo *git.CommitInfo) error { - // 1. init repo - if err := client.InitRepo(); err != nil { - return err - } - - var ( - // if encounter rollout error, delete repo - needRollBack bool - err error - ) - defer func() { - if !needRollBack { - return - } - // need to clean the repo created when reterr != nil - if err := client.DeleteRepo(); err != nil { - log.Errorf("failed to delete the repo: %s.", err) - } - }() - - // 2. push local path to repo - needRollBack, err = client.PushFiles(commitInfo, false) - return err -} diff --git a/pkg/util/scm/client_mock.go b/pkg/util/scm/client_mock.go deleted file mode 100644 index 89f9488fb..000000000 --- a/pkg/util/scm/client_mock.go +++ /dev/null @@ -1,66 +0,0 @@ -package scm - -import ( - "github.com/devstream-io/devstream/pkg/util/scm/git" -) - -type MockScmClient struct { - InitRaiseError error - PushRaiseError error - GetPathInfoError error - DownloadRepoError error - AddRepoSecretError error - DownloadRepoValue string - NeedRollBack bool - DeleteFuncIsRun bool - GetPathInfoReturnValue []*git.RepoFileStatus -} - -func (m *MockScmClient) InitRepo() error { - if m.InitRaiseError != nil { - return m.InitRaiseError - } - return nil -} -func (m *MockScmClient) PushFiles(commitInfo *git.CommitInfo, checkUpdate bool) (bool, error) { - if m.PushRaiseError != nil { - return m.NeedRollBack, m.PushRaiseError - } - return m.NeedRollBack, nil -} -func (m *MockScmClient) DeleteRepo() error { - m.DeleteFuncIsRun = true - return nil -} -func (m *MockScmClient) GetPathInfo(path string) ([]*git.RepoFileStatus, error) { - if m.GetPathInfoError != nil { - return nil, m.GetPathInfoError - } - if m.GetPathInfoReturnValue != nil { - return m.GetPathInfoReturnValue, nil - } - return nil, nil -} -func (m *MockScmClient) DeleteFiles(commitInfo *git.CommitInfo) error { - return nil -} -func (m *MockScmClient) AddWebhook(webhookConfig *git.WebhookConfig) error { - return nil -} -func (m *MockScmClient) DeleteWebhook(webhookConfig *git.WebhookConfig) error { - return nil -} -func (m *MockScmClient) DownloadRepo() (string, error) { - if m.DownloadRepoError != nil { - return "", m.DownloadRepoError - } - return m.DownloadRepoValue, nil -} - -func (m *MockScmClient) DescribeRepo() (*git.RepoInfo, error) { - return nil, nil -} - -func (m *MockScmClient) AddRepoSecret(secretKey, secretValue string) error { - return m.AddRepoSecretError -} diff --git a/pkg/util/scm/client_test.go b/pkg/util/scm/client_test.go deleted file mode 100644 index 94d2d4ab8..000000000 --- a/pkg/util/scm/client_test.go +++ /dev/null @@ -1,86 +0,0 @@ -package scm_test - -import ( - "errors" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/pkg/util/scm" - "github.com/devstream-io/devstream/pkg/util/scm/git" -) - -var _ = Describe("PushInitRepo func", func() { - var ( - mockRepo *scm.MockScmClient - commitInfo *git.CommitInfo - err error - ) - BeforeEach(func() { - commitInfo = &git.CommitInfo{ - CommitMsg: "test", - CommitBranch: "test-branch", - } - }) - When("init method return err", func() { - BeforeEach(func() { - mockRepo = &scm.MockScmClient{ - InitRaiseError: errors.New("init error"), - } - }) - It("should return err", func() { - err = scm.PushInitRepo(mockRepo, commitInfo) - Expect(err).Error().Should(HaveOccurred()) - Expect(err.Error()).Should(Equal("init error")) - }) - }) - - When("push method failed", func() { - BeforeEach(func() { - mockRepo = &scm.MockScmClient{ - PushRaiseError: errors.New("push error"), - NeedRollBack: false, - } - }) - It("should return err", func() { - err = scm.PushInitRepo(mockRepo, commitInfo) - Expect(err).Error().Should(HaveOccurred()) - Expect(err.Error()).Should(Equal("push error")) - Expect(mockRepo.DeleteFuncIsRun).Should(BeFalse()) - }) - - When("push method return needRollBack", func() { - BeforeEach(func() { - mockRepo = &scm.MockScmClient{ - PushRaiseError: errors.New("push error"), - NeedRollBack: true, - } - }) - It("should run DeleteRepo method", func() { - err = scm.PushInitRepo(mockRepo, commitInfo) - Expect(err).Error().Should(HaveOccurred()) - Expect(err.Error()).Should(Equal("push error")) - Expect(mockRepo.DeleteFuncIsRun).Should(BeTrue()) - }) - }) - }) -}) - -var _ = Describe("NewClientWithAuth func", func() { - var ( - r *git.RepoInfo - ) - BeforeEach(func() { - r = &git.RepoInfo{} - }) - When("scm type not valid", func() { - BeforeEach(func() { - r.RepoType = "not_exist" - }) - It("should return error", func() { - _, err := scm.NewClientWithAuth(r) - Expect(err).Error().Should(HaveOccurred()) - Expect(err.Error()).Should(ContainSubstring("git scmType only support gitlab and github")) - }) - }) -}) diff --git a/pkg/util/scm/git/commit.go b/pkg/util/scm/git/commit.go deleted file mode 100644 index 7301ebe84..000000000 --- a/pkg/util/scm/git/commit.go +++ /dev/null @@ -1,15 +0,0 @@ -package git - -type GitFileContentMap map[string][]byte - -type CommitInfo struct { - CommitMsg string - CommitBranch string - GitFileMap GitFileContentMap -} - -// GitFilePathInfo contains file local path and remote git path -type GitFilePathInfo struct { - SourcePath string - DestinationPath string -} diff --git a/pkg/util/scm/git/git_suite_test.go b/pkg/util/scm/git/git_suite_test.go deleted file mode 100644 index a1b80b93f..000000000 --- a/pkg/util/scm/git/git_suite_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package git_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestPlanmanager(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Util git Suite") -} diff --git a/pkg/util/scm/git/repoinfo.go b/pkg/util/scm/git/repoinfo.go deleted file mode 100644 index abbb768f6..000000000 --- a/pkg/util/scm/git/repoinfo.go +++ /dev/null @@ -1,247 +0,0 @@ -package git - -import ( - "fmt" - "net/url" - "strings" - - "github.com/devstream-io/devstream/pkg/util/log" - "github.com/devstream-io/devstream/pkg/util/mapz" -) - -type ScmURL string - -type RepoInfo struct { - // repo detail fields - Owner string `yaml:"owner" mapstructure:"owner,omitempty"` - Org string `yaml:"org" mapstructure:"org,omitempty"` - Repo string `yaml:"name" mapstructure:"name,omitempty"` - Branch string `yaml:"branch" mapstructure:"branch,omitempty"` - RepoType string `yaml:"scmType" mapstructure:"scmType,omitempty"` - Token string `yaml:"token" mapstructure:"token,omitempty"` - - // url fields - APIURL string `yaml:"apiURL" mapstructure:"apiURL,omitempty"` - CloneURL ScmURL `yaml:"url" mapstructure:"url,omitempty"` - - // used for gitlab - Namespace string `mapstructure:"nameSpace,omitempty"` - Visibility string `mapstructure:"visibility,omitempty"` - BaseURL string `yaml:"baseURL" mapstructure:"baseURL,omitempty"` - SSHPrivateKey string `yaml:"sshPrivateKey" mapstructure:"sshPrivateKey,omitempty"` - - // used for GitHub - WorkPath string `mapstructure:"workPath,omitempty"` - NeedAuth bool `mapstructure:"needAuth,omitempty"` -} - -func (r *RepoInfo) SetDefault() error { - // set repoInfo default field - if r.checkNeedUpdateFromURL() { - // opts has only CloneURL is configured, update other fields from url - if err := r.updateFieldsFromURLField(); err != nil { - return err - } - } else { - // else build ScmURL from RepoInfo other fields - r.CloneURL = r.buildScmURL() - r.Branch = r.getBranchWithDefault() - r.BaseURL = r.getBaseURL() - } - return r.checkValid() -} - -func (r *RepoInfo) GetRepoOwner() string { - // if org or owner is not empty, return org/owner - if r.Org != "" { - return r.Org - } - if r.Owner != "" { - return r.Owner - } - // else return owner extract from url - if r.CloneURL != "" { - owner, _, err := r.CloneURL.extractRepoOwnerAndName() - if err != nil { - log.Warnf("git GetRepoName failed %s", err) - } - return owner - } - return "" -} - -func (r *RepoInfo) GetRepoPath() string { - return fmt.Sprintf("%s/%s", r.GetRepoOwner(), r.GetRepoName()) -} - -func (r *RepoInfo) GetRepoName() string { - var repoName string - var err error - if r.Repo != "" { - repoName = r.Repo - } else if r.CloneURL != "" { - _, repoName, err = r.CloneURL.extractRepoOwnerAndName() - if err != nil { - log.Warnf("git GetRepoName failed %s", err) - } - } - return repoName -} - -func (r *RepoInfo) GetCloneURL() string { - var cloneURL string - if r.CloneURL != "" { - cloneURL = string(r.CloneURL.addGithubURLScheme()) - } else { - cloneURL = string(r.buildScmURL()) - } - return cloneURL -} - -func (r *RepoInfo) Encode() map[string]any { - m, err := mapz.DecodeStructToMap(r) - if err != nil { - log.Errorf("gitRepo [%+v] decode to map failed: %+v", r, err) - } - return m -} - -// IsGithubRepo return ture if repo is github -func (r *RepoInfo) IsGithubRepo() bool { - return r.RepoType == "github" || strings.Contains(string(r.CloneURL), "github") -} - -func (r *RepoInfo) getBranchWithDefault() string { - branch := r.Branch - if branch != "" { - return branch - } - if r.IsGithubRepo() { - branch = "main" - } else { - branch = "master" - } - return branch -} - -// BuildURL return url build from repo struct -func (r *RepoInfo) buildScmURL() ScmURL { - switch r.RepoType { - case "github": - return ScmURL(fmt.Sprintf("https://github.com/%s/%s", r.GetRepoOwner(), r.Repo)) - case "gitlab": - gitlabURL := r.getBaseURL() - return ScmURL(fmt.Sprintf("%s/%s/%s.git", gitlabURL, r.GetRepoOwner(), r.Repo)) - default: - log.Warnf("git repo buildScmURL get invalid repo type: %s", r.RepoType) - return "" - } -} - -func (r *RepoInfo) checkNeedUpdateFromURL() bool { - return r.CloneURL != "" && (r.RepoType == "" || r.Repo == "") -} - -func (r *RepoInfo) updateFieldsFromURLField() error { - // 1. config basic info for different repo type - if r.IsGithubRepo() { - r.RepoType = "github" - } else { - r.RepoType = "gitlab" - // extract gitlab baseURL from url string - apiURL := r.APIURL - if apiURL == "" { - apiURL = string(r.CloneURL) - } - gitlabBaseURL, err := extractBaseURLfromRaw(apiURL) - if err != nil { - return err - } - r.BaseURL = gitlabBaseURL - } - - // 2. get repoOwner and Name from CloneURL field - repoOwner, repoName, err := r.CloneURL.extractRepoOwnerAndName() - if err != nil { - return err - } - if r.Org == "" && r.Owner == "" { - r.Owner = repoOwner - } - r.Repo = repoName - // 3. if scm.branch is not configured, just use repo's default branch - r.Branch = r.getBranchWithDefault() - return nil -} - -func (r *RepoInfo) checkValid() error { - // basic check - if r.Org != "" && r.Owner != "" { - return fmt.Errorf("git org and owner can't be configured at the same time") - } - if r.RepoType != "github" && r.RepoType != "gitlab" { - return fmt.Errorf("git scmType only support gitlab and github, current scm type is [%s]", r.RepoType) - } - if r.Repo == "" { - return fmt.Errorf("git name field must be configured") - } - return nil -} - -func (r *RepoInfo) getBaseURL() string { - if r.RepoType == "gitlab" { - if r.BaseURL != "" { - return r.BaseURL - } else { - return "https://gitlab.com" - } - } - return "" -} - -// extractRepoOwnerAndName will get repoOwner and repoName from ScmURL -func (u ScmURL) extractRepoOwnerAndName() (string, string, error) { - var paths string - ScmURLStr := string(u.addGithubURLScheme()) - c, err := url.ParseRequestURI(ScmURLStr) - if err != nil { - if strings.Contains(ScmURLStr, "git@") { - gitSSHLastIndex := strings.LastIndex(ScmURLStr, ":") - if gitSSHLastIndex == -1 { - err = fmt.Errorf("git url ssh repo not valid") - return "", "", err - } - paths = strings.Trim(ScmURLStr[gitSSHLastIndex:], ":") - } else { - err = fmt.Errorf("git url repo transport not support for now") - return "", "", err - } - } else { - paths = c.Path - } - projectPaths := strings.Split(strings.Trim(paths, "/"), "/") - if len(projectPaths) != 2 { - err = fmt.Errorf("git url repo path is not valid") - return "", "", err - } - repoOwner := projectPaths[0] - repoName := strings.TrimSuffix(projectPaths[1], ".git") - return repoOwner, repoName, nil -} - -// addGithubURLScheme will add "https://" in github url config if it doesn't contain schme -func (u ScmURL) addGithubURLScheme() ScmURL { - cloneURL := string(u) - if !strings.Contains(cloneURL, "git@") && !strings.HasPrefix(cloneURL, "http") { - return ScmURL(fmt.Sprintf("https://%s", cloneURL)) - } - return u -} - -func extractBaseURLfromRaw(repoURL string) (string, error) { - u, err := url.ParseRequestURI(repoURL) - if err != nil { - return "", err - } - return fmt.Sprintf("%s://%s", u.Scheme, u.Host), nil -} diff --git a/pkg/util/scm/git/repoinfo_test.go b/pkg/util/scm/git/repoinfo_test.go deleted file mode 100644 index 21d074905..000000000 --- a/pkg/util/scm/git/repoinfo_test.go +++ /dev/null @@ -1,391 +0,0 @@ -package git - -import ( - "fmt" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -var _ = Describe("RepoInfo struct", func() { - var ( - repoName, branch, owner, org string - repoInfo *RepoInfo - ) - BeforeEach(func() { - repoName = "test_repo" - branch = "test_branch" - owner = "test_owner" - org = "test_org" - repoInfo = &RepoInfo{ - Repo: repoName, - Branch: branch, - Owner: owner, - Org: org, - } - }) - Context("GetRepoOwner method", func() { - It("should return owner", func() { - result := repoInfo.GetRepoOwner() - Expect(result).Should(Equal(repoInfo.Org)) - }) - }) - - Context("GetRepoName method", func() { - When("name field is configured", func() { - BeforeEach(func() { - repoInfo = &RepoInfo{ - Repo: repoName, - } - }) - It("should return owner", func() { - result := repoInfo.GetRepoName() - Expect(result).Should(Equal(repoInfo.Repo)) - }) - }) - When("name field is not configured, url exist", func() { - BeforeEach(func() { - repoInfo = &RepoInfo{ - CloneURL: ScmURL("https://test.github.com/user/urlRepo"), - } - }) - It("should return owner", func() { - result := repoInfo.GetRepoName() - Expect(result).Should(Equal("urlRepo")) - }) - }) - }) - - Context("GetCloneURL mehtod", func() { - When("name field is configured", func() { - BeforeEach(func() { - repoInfo = &RepoInfo{ - CloneURL: "exist.com", - } - }) - It("should return owner", func() { - result := repoInfo.GetCloneURL() - Expect(result).Should(Equal("https://exist.com")) - }) - }) - When("url field is not configured, other fields exist", func() { - BeforeEach(func() { - repoInfo = &RepoInfo{ - Repo: "test_name", - Owner: "test_user", - RepoType: "github", - } - }) - It("should return owner", func() { - result := repoInfo.GetCloneURL() - Expect(result).Should(Equal("https://github.com/test_user/test_name")) - }) - }) - - }) - - Context("GetRepoPath method", func() { - It("should return repo path", func() { - result := repoInfo.GetRepoPath() - Expect(result).Should(Equal(fmt.Sprintf("%s/%s", repoInfo.GetRepoOwner(), repoName))) - }) - }) - - Context("getBranchWithDefault method", func() { - When("branch is not empty", func() { - BeforeEach(func() { - repoInfo.Branch = "test" - }) - It("should get branch name", func() { - Expect(repoInfo.getBranchWithDefault()).Should(Equal("test")) - }) - }) - When("repo is gitlab and branch is empty", func() { - BeforeEach(func() { - repoInfo.Branch = "" - repoInfo.RepoType = "gitlab" - }) - It("should return master branch", func() { - branch := repoInfo.getBranchWithDefault() - Expect(branch).Should(Equal("master")) - }) - }) - When("repo is github and branch is empty", func() { - BeforeEach(func() { - repoInfo.Branch = "" - repoInfo.RepoType = "github" - }) - It("should return main branch", func() { - branch := repoInfo.getBranchWithDefault() - Expect(branch).Should(Equal("main")) - }) - }) - }) - - Context("buildScmURL method", func() { - When("repo is github", func() { - BeforeEach(func() { - repoInfo.RepoType = "github" - }) - It("should return github url", func() { - url := repoInfo.buildScmURL() - Expect(string(url)).Should(Equal(fmt.Sprintf("https://github.com/%s/%s", repoInfo.Org, repoInfo.Repo))) - }) - }) - When("repo is gitlab", func() { - BeforeEach(func() { - repoInfo.RepoType = "gitlab" - repoInfo.BaseURL = "http://test.com" - repoInfo.Org = "" - }) - It("should return gitlab url", func() { - url := repoInfo.buildScmURL() - Expect(string(url)).Should(Equal(fmt.Sprintf("%s/%s/%s.git", repoInfo.BaseURL, repoInfo.Owner, repoInfo.Repo))) - }) - }) - When("repo is gitlab and BaseURL is not configured", func() { - BeforeEach(func() { - repoInfo.RepoType = "gitlab" - repoInfo.Org = "" - }) - It("should return gitlab url", func() { - url := repoInfo.buildScmURL() - Expect(string(url)).Should(Equal(fmt.Sprintf("https://gitlab.com/%s/%s.git", repoInfo.Owner, repoInfo.Repo))) - }) - }) - }) - - Context("checkValid method", func() { - When("org and owner are all exist", func() { - BeforeEach(func() { - repoInfo = &RepoInfo{ - Org: "exist", - Owner: "exist", - } - }) - It("should return error", func() { - err := repoInfo.checkValid() - Expect(err).Should(HaveOccurred()) - Expect(err.Error()).Should(Equal("git org and owner can't be configured at the same time")) - }) - }) - - When("repo type is not valid", func() { - BeforeEach(func() { - repoInfo = &RepoInfo{ - RepoType: "not_exist", - } - }) - It("should return error", func() { - err := repoInfo.checkValid() - Expect(err).Should(HaveOccurred()) - Expect(err.Error()).Should(ContainSubstring("git scmType only support gitlab and github")) - }) - }) - - }) - - Context("Encode method", func() { - It("should return map", func() { - m := repoInfo.Encode() - Expect(m).Should(Equal(map[string]any{ - "name": "test_repo", - "branch": "test_branch", - "owner": "test_owner", - "org": "test_org", - })) - }) - }) - - Context("SetDefault method", func() { - When("is github repo", func() { - BeforeEach(func() { - repoInfo = &RepoInfo{ - CloneURL: "git@github.com:test/dtm-test.git", - Branch: "test", - } - }) - It("should return github repo info", func() { - err := repoInfo.SetDefault() - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(repoInfo).ShouldNot(BeNil()) - Expect(repoInfo.Repo).Should(Equal("dtm-test")) - Expect(repoInfo.Owner).Should(Equal("test")) - Expect(repoInfo.RepoType).Should(Equal("github")) - Expect(repoInfo.Branch).Should(Equal("test")) - }) - }) - When("clone url is not valid", func() { - BeforeEach(func() { - repoInfo = &RepoInfo{ - CloneURL: "git@github.comtest/dtm-test.git", - Branch: "test", - } - }) - It("should return error", func() { - err := repoInfo.SetDefault() - Expect(err).Error().Should(HaveOccurred()) - }) - }) - When("is gitlab repo", func() { - When("apiURL is not set, url is ssh format", func() { - BeforeEach(func() { - repoInfo = &RepoInfo{ - CloneURL: "git@gitlab.test.com:root/test-demo.git", - APIURL: "", - Branch: "test", - RepoType: "gitlab", - } - - }) - It("should return error", func() { - err := repoInfo.SetDefault() - Expect(err).Error().Should(HaveOccurred()) - }) - }) - When("apiURL is not set, url is http format", func() { - BeforeEach(func() { - repoInfo = &RepoInfo{ - CloneURL: "http://gitlab.test.com:3000/root/test-demo.git", - APIURL: "", - Branch: "test", - RepoType: "gitlab", - } - }) - It("should return error", func() { - err := repoInfo.SetDefault() - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(repoInfo.BaseURL).Should(Equal("http://gitlab.test.com:3000")) - Expect(repoInfo.Owner).Should(Equal("root")) - Expect(repoInfo.Repo).Should(Equal("test-demo")) - Expect(repoInfo.Branch).Should(Equal("test")) - }) - }) - When("apiURL is set", func() { - BeforeEach(func() { - repoInfo = &RepoInfo{ - CloneURL: "git@gitlab.test.com:root/test-demo.git", - APIURL: "http://gitlab.http.com", - Branch: "test", - RepoType: "gitlab", - Org: "cover_org", - } - }) - It("should set apiURL as BaseURL", func() { - err := repoInfo.SetDefault() - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(repoInfo.BaseURL).Should(Equal("http://gitlab.http.com")) - Expect(repoInfo.Owner).Should(Equal("")) - Expect(repoInfo.Repo).Should(Equal("test-demo")) - Expect(repoInfo.Branch).Should(Equal("test")) - Expect(repoInfo.Org).Should(Equal("cover_org")) - }) - - }) - }) - When("scm repo has fields", func() { - BeforeEach(func() { - repoInfo = &RepoInfo{ - CloneURL: "https://github.com/test_org/test", - Repo: "test", - RepoType: "github", - Org: "test_org", - } - }) - It("should return repoInfo", func() { - err := repoInfo.SetDefault() - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(repoInfo).Should(Equal(&RepoInfo{ - Branch: "main", - Repo: "test", - RepoType: "github", - NeedAuth: false, - CloneURL: "https://github.com/test_org/test", - Org: "test_org", - })) - }) - }) - }) -}) - -var _ = Describe("ScmURL type", func() { - var cloneURL ScmURL - Context("UpdateRepoPathByCloneURL method", func() { - When("cloneURL is http format", func() { - When("url is valid", func() { - BeforeEach(func() { - cloneURL = "http://test.com/test_user/test_repo.git" - }) - It("should update owner and repo", func() { - owner, name, err := cloneURL.extractRepoOwnerAndName() - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(owner).Should(Equal("test_user")) - Expect(name).Should(Equal("test_repo")) - }) - }) - When("url is path is not valid", func() { - BeforeEach(func() { - cloneURL = "http://test.com/test_user" - }) - It("should update owner and repo", func() { - _, _, err := cloneURL.extractRepoOwnerAndName() - Expect(err).Error().Should(HaveOccurred()) - Expect(err.Error()).Should(ContainSubstring("git url repo path is not valid")) - }) - }) - When("url is path doesn't have scheme", func() { - BeforeEach(func() { - cloneURL = "test.com/test_user/test_repo" - }) - It("should add scheme auto", func() { - owner, repo, err := cloneURL.extractRepoOwnerAndName() - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(owner).Should(Equal("test_user")) - Expect(repo).Should(Equal("test_repo")) - }) - }) - - }) - When("cloneURL is git ssh format", func() { - When("ssh format is valid", func() { - BeforeEach(func() { - cloneURL = "git@test.com:devstream-io/devstream.git" - }) - It("should update owner and repo", func() { - owner, name, err := cloneURL.extractRepoOwnerAndName() - Expect(err).ShouldNot(HaveOccurred()) - Expect(owner).Should(Equal("devstream-io")) - Expect(name).Should(Equal("devstream")) - }) - }) - }) - When("ssh format has not valid path", func() { - BeforeEach(func() { - cloneURL = "git@test.com" - }) - It("should return error", func() { - _, _, err := cloneURL.extractRepoOwnerAndName() - Expect(err).Error().Should(HaveOccurred()) - Expect(err.Error()).Should(ContainSubstring("git url ssh repo not valid")) - }) - }) - When("cloneURL is not valid", func() { - BeforeEach(func() { - cloneURL = "I'm just a string" - }) - It("should return error", func() { - _, _, err := cloneURL.extractRepoOwnerAndName() - Expect(err).Error().Should(HaveOccurred()) - Expect(err.Error()).Should(ContainSubstring("git url repo transport not support for now")) - }) - }) - }) - Context("addGithubURL method", func() { - BeforeEach(func() { - cloneURL = "github.com/test/repo" - }) - It("should add scheme", func() { - Expect(string(cloneURL.addGithubURLScheme())).Should(Equal("https://github.com/test/repo")) - }) - }) -}) diff --git a/pkg/util/scm/git/state.go b/pkg/util/scm/git/state.go deleted file mode 100644 index e8c9abdcb..000000000 --- a/pkg/util/scm/git/state.go +++ /dev/null @@ -1,31 +0,0 @@ -package git - -import ( - "crypto/sha1" - "encoding/hex" - "fmt" - - "github.com/google/go-github/v42/github" -) - -type RepoFileStatus struct { - Path string - SHA string - Branch string -} - -func (f *RepoFileStatus) EncodeToGitHubContentOption(commitMsg string) *github.RepositoryContentFileOptions { - return &github.RepositoryContentFileOptions{ - Message: github.String(commitMsg), - SHA: github.String(f.SHA), - Branch: github.String(f.Branch), - } -} - -func CalculateGitHubBlobSHA(fileContent []byte) string { - p := fmt.Sprintf("blob %d\x00", len(fileContent)) - h := sha1.New() - h.Write([]byte(p)) - h.Write([]byte(fileContent)) - return hex.EncodeToString(h.Sum(nil)) -} diff --git a/pkg/util/scm/git/state_test.go b/pkg/util/scm/git/state_test.go deleted file mode 100644 index b676223b0..000000000 --- a/pkg/util/scm/git/state_test.go +++ /dev/null @@ -1,42 +0,0 @@ -package git_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/pkg/util/scm/git" -) - -var _ = Describe("RepoFileStatus struct", func() { - var ( - repoFileStatus *git.RepoFileStatus - path, branch, sha string - ) - BeforeEach(func() { - path = "testPath" - branch = "test_branch" - sha = "s" - repoFileStatus = &git.RepoFileStatus{ - Path: path, - Branch: branch, - SHA: sha, - } - }) - Context("EncodeToGitHubContentOption method", func() { - It("should return github contentInfo", func() { - commitMsg := "commit" - result := repoFileStatus.EncodeToGitHubContentOption(commitMsg) - Expect(*result.SHA).Should(Equal(sha)) - Expect(*result.Message).Should(Equal(commitMsg)) - Expect(*result.Branch).Should(Equal(branch)) - }) - }) -}) - -var _ = Describe("CalculateGitHubBlobSHA func", func() { - var content string - It("should return as expect", func() { - content = "test Content" - Expect(git.CalculateGitHubBlobSHA([]byte(content))).Should(Equal("d9c012c6ecfcc8ce04a6538cc43490b1d5401241")) - }) -}) diff --git a/pkg/util/scm/git/webhook.go b/pkg/util/scm/git/webhook.go deleted file mode 100644 index dba89855a..000000000 --- a/pkg/util/scm/git/webhook.go +++ /dev/null @@ -1,6 +0,0 @@ -package git - -type WebhookConfig struct { - Address string - SecretToken string -} diff --git a/pkg/util/scm/github/branch.go b/pkg/util/scm/github/branch.go deleted file mode 100644 index b63a758f1..000000000 --- a/pkg/util/scm/github/branch.go +++ /dev/null @@ -1,57 +0,0 @@ -package github - -import ( - "fmt" - - "github.com/google/go-github/v42/github" - - "github.com/devstream-io/devstream/pkg/util/log" - "github.com/devstream-io/devstream/pkg/util/scm/git" -) - -func (c *Client) NewBranch(newBranch string) (*github.Reference, error) { - ref, err := c.getMainBranchRef(c.Branch) - if err != nil { - return nil, err - } - - newRef := fmt.Sprintf("heads/%s", newBranch) - newRefItem, _, err := c.Git.CreateRef(c.Context, c.GetRepoOwner(), c.Repo, &github.Reference{ - Ref: &newRef, - Object: &github.GitObject{ - Type: nil, - SHA: ref.GetObject().SHA, - URL: nil, - }, - }) - return newRefItem, err -} - -func (c *Client) DeleteBranch(branch string) error { - refStr := fmt.Sprintf("heads/%s", branch) - log.Debugf("Deleting ref: %s.", refStr) - - _, err := c.Git.DeleteRef(c.Context, c.GetRepoOwner(), c.Repo, refStr) - return err -} - -func (c *Client) MergeCommits(commitInfo *git.CommitInfo) error { - number, err := c.NewPullRequest(commitInfo) - if err != nil { - return err - } - - return c.MergePullRequest(number, MergeMethodMerge) -} - -func (c *Client) getMainBranchRef(branch string) (*github.Reference, error) { - refStr := fmt.Sprintf("heads/%s", branch) - ref, _, err := c.Git.GetRef(c.Context, c.GetRepoOwner(), c.Repo, refStr) - if err != nil { - log.Debugf("Failed to get the ref for %s: %s.", refStr, err) - return nil, err - } - log.Debugf("Got the ref: Ref %s, URL %s, nodeId %s, Obj: %s.", - ref.GetRef(), ref.GetURL(), ref.GetNodeID(), ref.GetObject().String()) - return ref, nil -} diff --git a/pkg/util/scm/github/branch_test.go b/pkg/util/scm/github/branch_test.go deleted file mode 100644 index 0cda76d95..000000000 --- a/pkg/util/scm/github/branch_test.go +++ /dev/null @@ -1,92 +0,0 @@ -package github - -import ( - "fmt" - "net/http" - "testing" - - "github.com/devstream-io/devstream/pkg/util/scm/git" -) - -type newBranchTest struct { - BaseTest - newBranch string - wantErr bool -} - -type delBranchTest struct { - BaseTest - branch string - wantErr bool -} - -func TestClient_NewBranch(t *testing.T) { - respBody := ` - { - "ref": "refs/heads/b", - "url": "https://api.github.com/repos/o/r/git/refs/heads/b", - "object": { - "type": "commit", - "sha": "aa218f56b14c9653891f9e74264a383fa43fefbd", - "url": "https://api.github.com/repos/o/r/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd" - } - }` - mux, serverUrl, teardown := Setup() - defer teardown() - - tests := []newBranchTest{ - // TODO: Add test cases. - { - BaseTest{"base", GetClientWithOption( - t, &git.RepoInfo{Owner: "o", Repo: "r", Org: "or", Branch: "b"}, serverUrl, - ), - "/repos/or/r/git/ref/heads/b", http.MethodGet, false, "", ""}, - "", true, - }, - { - BaseTest{"base set wrong register url for GetRef api in mock server", GetClientWithOption( - t, &git.RepoInfo{Owner: "o", Repo: "r", Branch: "b"}, serverUrl, - ), - "repos", http.MethodGet, false, "", ""}, "", true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - mux.HandleFunc(tt.registerUrl, func(w http.ResponseWriter, r *http.Request) { - t.Logf("test name: %s, hit path: %s", tt.name, r.URL.Path) - fmt.Fprint(w, respBody) - }) - if _, err := tt.client.NewBranch(tt.newBranch); (err != nil) != tt.wantErr { - t.Errorf("Client.NewBranch() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func TestClient_DeleteBranch(t *testing.T) { - mux, serverUrl, teardown := Setup() - defer teardown() - - tests := []delBranchTest{ - // TODO: Add test cases. - { - BaseTest{"base", GetClientWithOption( - t, &git.RepoInfo{Owner: "o", Repo: "r", Org: "or"}, serverUrl, - ), - "/repos/or/r/git/ref/heads/b", http.MethodGet, false, "", ""}, - "b", true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - mux.HandleFunc(tt.registerUrl, func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, ``) - }) - err := tt.client.DeleteBranch(tt.branch) - t.Log(err) - if (err != nil) != tt.wantErr { - t.Errorf("Client.DeleteBranch() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} diff --git a/pkg/util/scm/github/commit.go b/pkg/util/scm/github/commit.go deleted file mode 100644 index e076f4c14..000000000 --- a/pkg/util/scm/github/commit.go +++ /dev/null @@ -1,47 +0,0 @@ -package github - -import ( - "fmt" - - "github.com/google/go-github/v42/github" - - "github.com/devstream-io/devstream/pkg/util/log" - "github.com/devstream-io/devstream/pkg/util/scm/git" -) - -func (c *Client) GetLastCommit() (*github.RepositoryCommit, error) { - commits, _, err := c.Client.Repositories.ListCommits(c.Context, c.GetRepoOwner(), c.Repo, &github.CommitsListOptions{}) - if err != nil { - log.Debugf("Failed to get RepositoryCommits: %s.", err) - return nil, err - } - - if len(commits) == 0 { - msg := "no commits was found" - log.Debug(msg) - return nil, fmt.Errorf(msg) - } - - return commits[0], nil -} - -func (c *Client) BuildCommitTree(ref *github.Reference, commitInfo *git.CommitInfo, checkChange bool) (*github.Tree, error) { - var entries []*github.TreeEntry - for githubPath, content := range commitInfo.GitFileMap { - if checkChange && !c.checkFileChange(githubPath, content) { - log.Debugf("Github File [%s] content not changed, not commit", githubPath) - continue - } - entries = append(entries, &github.TreeEntry{ - Path: github.String(githubPath), - Type: github.String("blob"), - Content: github.String(string(content)), - Mode: github.String("100644"), - }) - } - if len(entries) == 0 { - return nil, nil - } - tree, _, err := client.Git.CreateTree(c.Context, c.GetRepoOwner(), c.Repo, *ref.Object.SHA, entries) - return tree, err -} diff --git a/pkg/util/scm/github/commit_test.go b/pkg/util/scm/github/commit_test.go deleted file mode 100644 index 9dc3bf759..000000000 --- a/pkg/util/scm/github/commit_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package github - -import ( - "fmt" - "net/http" - "reflect" - "testing" - - "github.com/google/go-github/v42/github" - - "github.com/devstream-io/devstream/pkg/util/scm/git" -) - -type commitTest struct { - BaseTest - want *github.RepositoryCommit - wantErr bool -} - -func TestClient_GetLastCommit(t *testing.T) { - mux, serverUrl, teardown := Setup() - defer teardown() - sha := "s" - tests := []commitTest{ - // TODO: Add test cases. - { - BaseTest{"base 200 ", GetClientWithOption( - t, &git.RepoInfo{Owner: "o", Repo: "r", Org: "or"}, serverUrl, - ), - "/repos/or/r/commits", http.MethodGet, false, "", `[{"sha": "s"}]`}, - &github.RepositoryCommit{SHA: &sha}, false, - }, - { - BaseTest{"base 200 with empty result", GetClientWithOption( - t, &git.RepoInfo{Owner: "o", Repo: "r"}, serverUrl, - ), - "/repos/o/r/commits", http.MethodGet, false, "", `[]`}, - nil, true, - }, - { - BaseTest{"base 404", GetClientWithOption( - t, &git.RepoInfo{Owner: "o"}, serverUrl, - ), - "/aaa", http.MethodGet, false, "", ""}, - nil, true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - mux.HandleFunc(tt.registerUrl, func(w http.ResponseWriter, r *http.Request) { - DoTestMethod(t, r, tt.wantMethod) - fmt.Fprint(w, tt.respBody) - }) - got, err := tt.client.GetLastCommit() - if (err != nil) != tt.wantErr { - t.Errorf("Client.GetLastCommit() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("Client.GetLastCommit() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/pkg/util/scm/github/download.go b/pkg/util/scm/github/download.go deleted file mode 100644 index 7cdc9987b..000000000 --- a/pkg/util/scm/github/download.go +++ /dev/null @@ -1,71 +0,0 @@ -package github - -import ( - "context" - "fmt" - - "github.com/google/go-github/v42/github" - - "github.com/devstream-io/devstream/pkg/util/downloader" - "github.com/devstream-io/devstream/pkg/util/log" -) - -func (c *Client) DownloadAsset(tagName, assetName, fileName string) error { - // 1. get releases - releases, _, err := c.Repositories.ListReleases(context.TODO(), c.GetRepoOwner(), c.Repo, &github.ListOptions{}) - if err != nil { - return err - } - - log.Debug("Got releases successful.") - for i, r := range releases { - log.Debugf("Release(%d): %s.", i+1, r.GetName()) - } - - // 2. get assets - var assets []*github.ReleaseAsset - for _, r := range releases { - if *r.TagName != tagName { - continue - } - log.Debugf("Got a matched tag %s with release <%s>.", *r.TagName, *r.Name) - - if len(r.Assets) == 0 { - log.Debug("Assets is empty.") - return fmt.Errorf("assets is empty") - } - log.Debugf("%d Assets was found.", len(r.Assets)) - - assets = r.Assets - break - } - if len(assets) == 0 { - log.Debugf("Release with tag <%s> was not found.", tagName) - return fmt.Errorf("release with tag <%s> was not found", tagName) - } - - // 3. get download url - // format: https://github.com/merico-dev/dtm-repo-scaffolding-golang-gin/releases/download/v0.0.1/dtm-repo-scaffolding-golang-gin-v0.0.1.tar.gz - var downloadUrl string - for _, a := range assets { - if a.GetName() == assetName { - downloadUrl = a.GetBrowserDownloadURL() - log.Debugf("Download url: %s.", downloadUrl) - break - } - } - if downloadUrl == "" { - log.Debugf("Failed to got the download url for %s, maybe it not exists.", assetName) - return fmt.Errorf("failed to got the download url for %s, maybe it not exists", assetName) - } - - // 4. download - n, err := downloader.New().WithProgressBar().Download(downloadUrl, fileName, c.WorkPath) - if err != nil { - log.Debugf("Failed to download asset from %s.", downloadUrl) - return err - } - log.Debugf("Downloaded <%d> bytes.", n) - - return nil -} diff --git a/pkg/util/scm/github/download_test.go b/pkg/util/scm/github/download_test.go deleted file mode 100644 index 1d5f4323c..000000000 --- a/pkg/util/scm/github/download_test.go +++ /dev/null @@ -1,199 +0,0 @@ -package github_test - -import ( - "fmt" - "net/http" - "os" - - "github.com/argoproj/gitops-engine/pkg/utils/io" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "github.com/onsi/gomega/ghttp" - - "github.com/devstream-io/devstream/pkg/util/scm/git" - "github.com/devstream-io/devstream/pkg/util/scm/github" -) - -var _ = Describe("DownloadAsset", func() { - const ( - owner, repoName = "owner", "repo" - rightOrg, wrongOrg = "org", "/" - tagName, assetName, fileName = "t", "a", "f3" - ) - - var ( - s *ghttp.Server - org string - opts *git.RepoInfo - workPath string - ) - - JustBeforeEach(func() { - opts = &git.RepoInfo{ - Owner: owner, - Repo: repoName, - Org: org, - } - if len(workPath) != 0 { - opts.WorkPath = workPath - } - }) - When("// 1. get releases: the ListReleases url is incorrect", func() { - BeforeEach(func() { - org = wrongOrg - s = ghttp.NewServer() - }) - - It("should return error", func() { - s.SetAllowUnhandledRequests(true) - ghClient, err := github.NewClientWithOption(opts, s.URL()) - Expect(err).NotTo(HaveOccurred()) - Expect(ghClient).NotTo(Equal(nil)) - err = ghClient.DownloadAsset(tagName, assetName, fileName) - Expect(err).To(HaveOccurred()) - }) - }) - - When("// 1. get releases: the ListReleases url is correct but assets is empty", func() { - BeforeEach(func() { - org = rightOrg - s = ghttp.NewServer() - }) - - It("should return error", func() { - u := fmt.Sprintf("/repos/%s/%s/releases", org, repoName) - s.RouteToHandler("GET", github.BaseURLPath+u, func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusInternalServerError) - fmt.Fprint(w, `[{"id":1, "tag_name": "t"}]`) - }) - ghClient, err := github.NewClientWithOption(opts, s.URL()) - Expect(err).NotTo(HaveOccurred()) - Expect(ghClient).NotTo(Equal(nil)) - err = ghClient.DownloadAsset(tagName, assetName, fileName) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("500")) - }) - }) - - When("// 2. get assets: tagName is equal and the assets is empty", func() { - BeforeEach(func() { - org = rightOrg - s = ghttp.NewServer() - }) - - It("should return error", func() { - u := fmt.Sprintf("/repos/%s/%s/releases", org, repoName) - s.RouteToHandler("GET", github.BaseURLPath+u, func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, `[{"id":1, "tag_name": "t", "name": "n", "assets": []}]`) - }) - ghClient, err := github.NewClientWithOption(opts, s.URL()) - Expect(err).NotTo(HaveOccurred()) - Expect(ghClient).NotTo(Equal(nil)) - err = ghClient.DownloadAsset(tagName, assetName, fileName) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("assets is empty")) - }) - }) - - When("// 2. get assets: tagName is not equal and the assets is empty", func() { - BeforeEach(func() { - org = rightOrg - s = ghttp.NewServer() - }) - - It("should return error", func() { - u := fmt.Sprintf("/repos/%s/%s/releases", org, repoName) - s.RouteToHandler("GET", github.BaseURLPath+u, func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, `[{"id":1, "tag_name": "tttt", "name": "n", "assets": []}]`) - }) - ghClient, err := github.NewClientWithOption(opts, s.URL()) - Expect(err).NotTo(HaveOccurred()) - Expect(ghClient).NotTo(Equal(nil)) - err = ghClient.DownloadAsset(tagName, assetName, fileName) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring(fmt.Sprintf("release with tag <%s> was not found", tagName))) - }) - }) - - When("// 3. get download url: browser_download_url is empty", func() { - BeforeEach(func() { - org = rightOrg - s = ghttp.NewServer() - }) - - It("should return error", func() { - u := fmt.Sprintf("/repos/%s/%s/releases", org, repoName) - s.RouteToHandler("GET", github.BaseURLPath+u, func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, `[{"id":1, "tag_name": "t", "name": "a", "assets": [{"browser_download_url": ""}]}]`) - }) - ghClient, err := github.NewClientWithOption(opts, s.URL()) - Expect(err).NotTo(HaveOccurred()) - Expect(ghClient).NotTo(Equal(nil)) - err = ghClient.DownloadAsset(tagName, assetName, fileName) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring(fmt.Sprintf("failed to got the download url for %s, maybe it not exists", assetName))) - }) - }) - - When("// 4. download: filename is '.' ", func() { - BeforeEach(func() { - org = rightOrg - s = ghttp.NewServer() - }) - - It("should return error", func() { - downloadUrl := s.URL() + github.BaseURLPath + "/download" - u := fmt.Sprintf("/repos/%s/%s/releases", org, repoName) - s.RouteToHandler("GET", github.BaseURLPath+u, func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, `[ - { - "id":1, - "tag_name":"t", - "name":"n", - "assets": [{"id":1, "name":"a", "browser_download_url":"%s"}] - }]`, downloadUrl) - }) - ghClient, err := github.NewClientWithOption(opts, s.URL()) - Expect(err).NotTo(HaveOccurred()) - Expect(ghClient).NotTo(Equal(nil)) - err = ghClient.DownloadAsset(tagName, assetName, ".") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("filename must not be dir")) - }) - }) - - When("// 4. download", func() { - BeforeEach(func() { - org = rightOrg - workPath = os.TempDir() - s = ghttp.NewServer() - }) - - It("should return no error", func() { - downloadUrl := s.URL() + github.BaseURLPath + "/download" - u := fmt.Sprintf("/repos/%s/%s/releases", org, repoName) - s.RouteToHandler("GET", github.BaseURLPath+"/download", func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, "file content") - }) - s.RouteToHandler("GET", github.BaseURLPath+u, func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, `[ - { - "id":1, - "tag_name":"t", - "name":"n", - "assets": [{"id":1, "name":"a", "browser_download_url":"%s"}] - }]`, downloadUrl) - }) - ghClient, err := github.NewClientWithOption(opts, s.URL()) - Expect(err).NotTo(HaveOccurred()) - Expect(ghClient).NotTo(Equal(nil)) - err = ghClient.DownloadAsset(tagName, assetName, fileName) - Expect(err).To(Succeed()) - }) - }) - - AfterEach(func() { - s.Close() - DeferCleanup(io.DeleteFile, workPath+"/"+fileName) - }) -}) diff --git a/pkg/util/scm/github/errors.go b/pkg/util/scm/github/errors.go deleted file mode 100644 index 00f330315..000000000 --- a/pkg/util/scm/github/errors.go +++ /dev/null @@ -1,9 +0,0 @@ -package github - -import ( - "github.com/devstream-io/devstream/pkg/util/pkgerror" -) - -var ( - errHookAlreadyExist pkgerror.ErrorMessage = "Hook already exists" -) diff --git a/pkg/util/scm/github/file.go b/pkg/util/scm/github/file.go deleted file mode 100644 index f6efdaba7..000000000 --- a/pkg/util/scm/github/file.go +++ /dev/null @@ -1,168 +0,0 @@ -package github - -import ( - "net/http" - "time" - - "github.com/google/go-github/v42/github" - - "github.com/devstream-io/devstream/pkg/util/log" - "github.com/devstream-io/devstream/pkg/util/scm/git" -) - -// PushFiles will push local change to remote repo -// return boolean value is for control whether to roll out if encounter error -func (c *Client) PushFiles(commitInfo *git.CommitInfo, checkChange bool) (bool, error) { - // 1. create new branch from main - ref, err := c.NewBranch(commitInfo.CommitBranch) - if err != nil { - log.Warnf("Failed to create transit branch: %s", err) - return false, err - } - // delete new branch after func exit - defer func() { - err = c.DeleteBranch(commitInfo.CommitBranch) - if err != nil { - log.Warnf("Failed to delete transit branch: %s", err) - } - }() - tree, err := c.BuildCommitTree(ref, commitInfo, checkChange) - if err != nil { - log.Debugf("Failed to build commit tree: %s.", err) - return true, err - } - // if no new files to commit, just return - if tree == nil { - log.Successf("Github file all not change, pass...") - return false, nil - } - - // 2. push local file change to new branch - if err := c.PushLocalPath(ref, tree, commitInfo); err != nil { - log.Debugf("Failed to walk local repo-path: %s.", err) - return true, err - } - - // 3. merge new branch to main - if err = c.MergeCommits(commitInfo); err != nil { - log.Debugf("Failed to merge commits: %s.", err) - return true, err - } - // 4. delete placeholder file - if err = c.DeleteFiles(&git.CommitInfo{ - CommitMsg: "delete placeholder file", - CommitBranch: c.Branch, - GitFileMap: git.GitFileContentMap{ - repoPlaceHolderFileName: []byte{}, - }, - }); err != nil { - log.Debugf("github delete init file failed: %s", err) - } - return false, nil -} - -func (c *Client) CreateFile(content []byte, filePath, targetBranch string) error { - defaultMsg := "Initialize the repository" - - opt := &github.RepositoryContentFileOptions{ - Message: &defaultMsg, - Content: content, - Branch: &targetBranch, - } - - _, _, err := c.Repositories.CreateFile(c.Context, c.GetRepoOwner(), c.Repo, filePath, opt) - return err -} - -func (c *Client) PushLocalPath(ref *github.Reference, tree *github.Tree, commitInfo *git.CommitInfo) error { - parent, _, err := client.Repositories.GetCommit(c.Context, c.GetRepoOwner(), c.Repo, *ref.Object.SHA, nil) - if err != nil { - return err - } - // This is not always populated, but is needed. - parent.Commit.SHA = parent.SHA - date := time.Now() - author := &github.CommitAuthor{Date: &date, Name: github.String(defaultCommitAuthor), Email: github.String(defaultCommitAuthorEmail)} - commit := &github.Commit{Author: author, Message: github.String(commitInfo.CommitMsg), Tree: tree, Parents: []*github.Commit{parent.Commit}} - newCommit, _, err := client.Git.CreateCommit(c.Context, c.GetRepoOwner(), c.Repo, commit) - if err != nil { - return err - } - ref.Object.SHA = newCommit.SHA - _, _, err = client.Git.UpdateRef(c.Context, c.GetRepoOwner(), c.Repo, ref, false) - return err -} - -func (c *Client) GetPathInfo(location string) ([]*git.RepoFileStatus, error) { - fileContent, directoryContent, resp, err := c.Client.Repositories.GetContents( - c.Context, - c.GetRepoOwner(), - c.Repo, - location, - &github.RepositoryContentGetOptions{Ref: c.Branch}, - ) - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, nil - } - - if err != nil { - return nil, err - } - - gitfilesStatus := make([]*git.RepoFileStatus, 0, len(directoryContent)+1) - if fileContent != nil { - gitfilesStatus = append(gitfilesStatus, buildGitFileInfoFromRepContent(c.Branch, fileContent)) - } - for _, repFileContent := range directoryContent { - gitfilesStatus = append(gitfilesStatus, buildGitFileInfoFromRepContent(c.Branch, repFileContent)) - } - return gitfilesStatus, nil -} - -func (c *Client) checkFileChange(location string, content []byte) bool { - fileInfos, err := c.GetPathInfo(location) - if err != nil { - log.Debugf("Github request check file SHA failed: %s", err) - return true - } - contentSHA := git.CalculateGitHubBlobSHA(content) - for _, f := range fileInfos { - if f.SHA == contentSHA { - return false - } - } - return true -} - -func (c *Client) DeleteFiles(commitInfo *git.CommitInfo) error { - for fileLoc := range commitInfo.GitFileMap { - fileInfos, err := c.GetPathInfo(fileLoc) - if err != nil || len(fileInfos) != 1 { - log.Debugf("Github file %s already removed.", fileLoc) - continue - } - opts := fileInfos[0].EncodeToGitHubContentOption( - commitInfo.CommitMsg, - ) - log.Debugf("Deleting GitHub file %s ...", fileLoc) - _, _, err = c.Client.Repositories.DeleteFile( - c.Context, - c.GetRepoOwner(), - c.Repo, - fileLoc, - opts) - if err != nil { - return err - } - log.Debugf("GitHub file %s removed.", fileLoc) - } - return nil -} - -func buildGitFileInfoFromRepContent(branch string, repContent *github.RepositoryContent) *git.RepoFileStatus { - return &git.RepoFileStatus{ - Path: *repContent.Path, - SHA: *repContent.SHA, - Branch: branch, - } -} diff --git a/pkg/util/scm/github/file_test.go b/pkg/util/scm/github/file_test.go deleted file mode 100644 index ce8d87680..000000000 --- a/pkg/util/scm/github/file_test.go +++ /dev/null @@ -1,126 +0,0 @@ -package github_test - -import ( - "fmt" - "net/http" - "os" - "strconv" - "strings" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "github.com/onsi/gomega/ghttp" - - "github.com/devstream-io/devstream/pkg/util/scm/git" - "github.com/devstream-io/devstream/pkg/util/scm/github" -) - -var _ = Describe("Github files methods", func() { - var ( - s *ghttp.Server - repoName, owner, branch, reqPath string - c *github.Client - commitInfo *git.CommitInfo - ) - BeforeEach(func() { - repoName = "test_repo" - owner = "test_owner" - branch = "test_branch" - s = ghttp.NewServer() - repoInfo := &git.RepoInfo{ - Repo: repoName, - Branch: branch, - Owner: owner, - } - commitInfo = &git.CommitInfo{ - CommitMsg: "test", - CommitBranch: branch, - GitFileMap: map[string][]byte{ - "srcPath": []byte("test data"), - }, - } - c = newTestClient(s.URL(), repoInfo) - }) - AfterEach(func() { - s.Close() - }) - Context("GetLocationInfo func", func() { - var ( - testFile, filePath, fileSHA string - ) - When("file exist", func() { - BeforeEach(func() { - testFile = "testFile" - filePath = "/test_path" - fileSHA = "s" - reqPath = fmt.Sprintf("%s/repos/test_owner/test_repo/contents/%s", basePath, testFile) - query := fmt.Sprintf("ref=%s", branch) - mockRsp := map[string]string{ - "type": "test", - "path": filePath, - "sha": fileSHA, - } - s.RouteToHandler("GET", reqPath, ghttp.CombineHandlers( - ghttp.VerifyRequest("GET", reqPath, query), - ghttp.RespondWithJSONEncoded(http.StatusOK, mockRsp), - )) - }) - It("should work", func() { - info, err := c.GetPathInfo(testFile) - Expect(err).ShouldNot(HaveOccurred()) - Expect(info).ShouldNot(BeNil()) - Expect(len(info)).Should(Equal(1)) - Expect(info[0].Branch).Should(Equal(branch)) - Expect(info[0].SHA).Should(Equal(fileSHA)) - Expect(info[0].Path).Should(Equal(filePath)) - }) - }) - When("file not exist", func() { - BeforeEach(func() { - testFile = "not_exist" - fileSHA = "s" - reqPath = fmt.Sprintf("%s/repos/test_owner/test_repo/contents/%s", basePath, testFile) - query := fmt.Sprintf("ref=%s", branch) - s.RouteToHandler("GET", reqPath, ghttp.CombineHandlers( - ghttp.VerifyRequest("GET", reqPath, query), - ghttp.RespondWithJSONEncoded(http.StatusNotFound, nil), - )) - }) - It("should return empty list", func() { - info, err := c.GetPathInfo(testFile) - Expect(err).ShouldNot(HaveOccurred()) - Expect(len(info)).Should(Equal(0)) - }) - }) - }) - - Context("PushLocalPathToRepo", func() { - BeforeEach(func() { - s.Reset() - s.SetAllowUnhandledRequests(true) - }) - - It("1. create new branch from main", func() { - s.SetUnhandledRequestStatusCode(http.StatusInternalServerError) - r, err := c.PushFiles(commitInfo, false) - Expect(err).NotTo(Succeed()) - Expect(err.Error()).To(ContainSubstring(strconv.Itoa(http.StatusInternalServerError))) - Expect(r).To(Equal(false)) - }) - It("2. create new branch from main", func() { - // u := fmt.Sprintf("/repos/%v/%v/git/ref/heads/%s", org, repo, filePath) - u := fmt.Sprintf("/repos/%s/%s/contents/%s", owner, repoName, strings.Trim(os.TempDir(), "/")) - s.Reset() - s.SetAllowUnhandledRequests(true) - s.SetUnhandledRequestStatusCode(http.StatusInternalServerError) - s.RouteToHandler("GET", u, func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, "") - }) - r, err := c.PushFiles(commitInfo, false) - Expect(err).NotTo(Succeed()) - Expect(err.Error()).To(ContainSubstring(strconv.Itoa(http.StatusInternalServerError))) - Expect(r).To(Equal(false)) - }) - }) - -}) diff --git a/pkg/util/scm/github/github.go b/pkg/util/scm/github/github.go deleted file mode 100644 index f648923a3..000000000 --- a/pkg/util/scm/github/github.go +++ /dev/null @@ -1,90 +0,0 @@ -package github - -import ( - "context" - "fmt" - - "github.com/google/go-github/v42/github" - "golang.org/x/oauth2" - - "github.com/devstream-io/devstream/pkg/util/log" - "github.com/devstream-io/devstream/pkg/util/scm/git" -) - -const ( - defaultWorkPath = ".github-workpath" - defaultCommitAuthor = "devstream" - defaultCommitAuthorEmail = "devstream@merico.dev" - repoPlaceHolderFileName = ".placeholder" -) - -var ( - client *Client -) - -type Client struct { - *git.RepoInfo - *github.Client - context.Context -} - -func NewClient(option *git.RepoInfo) (*Client, error) { - // same option will get same client - if client != nil && *client.RepoInfo == *option { - log.Debug("Use a cached client.") - return client, nil - } - - var retErr error - defer func() { - if retErr != nil { - return - } - if client.RepoInfo.WorkPath == "" { - client.RepoInfo.WorkPath = defaultWorkPath - } - }() - - // a. client without auth enabled - if !option.NeedAuth { - log.Debug("Auth is not enabled.") - client = &Client{ - RepoInfo: option, - Client: github.NewClient(nil), - Context: context.Background(), - } - - return client, nil - } - log.Debug("Auth is enabled.") - - // b. client with auth enabled - - // TL;DR: Don't use viper.GetString("xxx") in the `util/xxx` package. - // Don't use `token := viper.GetString("github_token")` here, - // it will fail without calling `viper.BindEnv("github_token")` first. - // os.Getenv() function is more clear and reasonable here. - if option.Token == "" { - retErr = fmt.Errorf("config field scm.token is not set. Failed to initialize GitHub token. More info - " + - "https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token") - return nil, retErr - } - log.Debugf("Token: %s.", option.Token) - - tc := oauth2.NewClient( - context.TODO(), - oauth2.StaticTokenSource( - &oauth2.Token{ - AccessToken: option.Token, - }, - ), - ) - - client = &Client{ - RepoInfo: option, - Client: github.NewClient(tc), - Context: context.Background(), - } - - return client, nil -} diff --git a/pkg/util/scm/github/github_suite_test.go b/pkg/util/scm/github/github_suite_test.go deleted file mode 100644 index ac3fb5832..000000000 --- a/pkg/util/scm/github/github_suite_test.go +++ /dev/null @@ -1,58 +0,0 @@ -package github_test - -import ( - "context" - "net/http" - "net/url" - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - githubCommon "github.com/google/go-github/v42/github" - - "github.com/devstream-io/devstream/pkg/util/scm/git" - "github.com/devstream-io/devstream/pkg/util/scm/github" -) - -var ( - mux *http.ServeMux - serverURL string - teardown func() -) - -const basePath = "/api-v3" - -var _ = BeforeSuite(func() { - mux, serverURL, teardown = github.Setup() -}) - -var _ = AfterSuite(func() { - teardown() -}) - -func CreateClientWithOr(opt *git.RepoInfo) *github.Client { - c, err := github.NewClientWithOption(opt, serverURL) - Expect(c).NotTo(Equal(nil)) - Expect(err).To(Succeed()) - return c -} - -func TestPlanmanager(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "GitHub Suite") -} - -func newTestClient(baseUrl string, repoInfo *git.RepoInfo) *github.Client { - githubClient := githubCommon.NewClient(nil) - url, _ := url.Parse(baseUrl + basePath + "/") - - githubClient.BaseURL = url - githubClient.UploadURL = url - - return &github.Client{ - RepoInfo: repoInfo, - Client: githubClient, - Context: context.Background(), - } -} diff --git a/pkg/util/scm/github/github_test.go b/pkg/util/scm/github/github_test.go deleted file mode 100644 index 0013409da..000000000 --- a/pkg/util/scm/github/github_test.go +++ /dev/null @@ -1,158 +0,0 @@ -package github - -import ( - "io" - "net/http" - "net/http/httptest" - "net/url" - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/pkg/util/scm/git" -) - -const ( - // BaseURLPath is a non-empty Client.BaseURL path to use during tests, - // to ensure relative URLs are used for all endpoints. - BaseURLPath = "/api-v3" -) - -type BaseTest struct { - name string //test name - client *Client - registerUrl string // url resigtered in mock server - wantMethod string // wanted method in mock server - wantReqBody bool // want request body nor not in mock server - reqBody string // content of request body - respBody string // content of response body -} - -func DoTestMethod(t *testing.T, r *http.Request, want string) { - if got := r.Method; got != want { - t.Errorf("Request method: %v, want %v", got, want) - } -} - -func DoTestBody(t *testing.T, r *http.Request, want string) { - b, err := io.ReadAll(r.Body) - if err != nil { - t.Errorf("Error reading request body: %v", err) - } - if got := string(b); got != want { - t.Errorf("request Body is %s, want %s", got, want) - } -} - -// Setup sets up a test HTTP server along with a github.Client that is -// configured to talk to that test server. Tests should register handlers on -// mux which provide mock responses for the API method being tested. -func Setup() (mux *http.ServeMux, serverURL string, teardown func()) { - // mux is the HTTP request multiplexer used with the test server. - mux = http.NewServeMux() - - // We want to ensure that tests catch mistakes where the endpoint URL is - // specified as absolute rather than relative. It only makes a difference - // when there's a non-empty base URL path. - apiHandler := http.NewServeMux() - apiHandler.Handle(BaseURLPath+"/", http.StripPrefix(BaseURLPath, mux)) - - // server is a test HTTP server used to provide mock API responses. - server := httptest.NewServer(apiHandler) - - return mux, server.URL, server.Close -} - -func GetClientWithOption(t *testing.T, opt *git.RepoInfo, severUrl string) *Client { - client, err := NewClient(opt) - if err != nil { - t.Error(err) - } - - url, _ := url.Parse(severUrl + BaseURLPath + "/") - - client.Client.BaseURL = url - client.Client.UploadURL = url - return client -} - -func NewClientWithOption(opt *git.RepoInfo, severUrl string) (*Client, error) { - client, err := NewClient(opt) - if err != nil { - return nil, err - } - - url, _ := url.Parse(severUrl + BaseURLPath + "/") - - client.Client.BaseURL = url - client.Client.UploadURL = url - return client, nil -} - -var _ = Describe("GitHub", func() { - var ( - optNotNeedAuth, optNeedAuth, optNeedAuthWithToken *git.RepoInfo - ) - BeforeEach(func() { - optNotNeedAuth = &git.RepoInfo{ - Owner: "", - Org: "devstream-io", - Repo: "dtm-repo-scaffolding-golang-gin", - } - optNeedAuth = &git.RepoInfo{ - Owner: "", - Org: "devstream-io", - Repo: "dtm-repo-scaffolding-golang-gin", - NeedAuth: true, - } - optNeedAuthWithToken = &git.RepoInfo{ - Owner: "", - Org: "devstream-io", - Repo: "dtm-repo-scaffolding-golang-gin", - NeedAuth: true, - Token: "token", - } - }) - Context("Client with cacahe", func() { - var ghClient *Client - var err error - - BeforeEach(func() { - ghClient, err = NewClient(optNotNeedAuth) - Expect(err).NotTo(HaveOccurred()) - Expect(ghClient).NotTo(Equal(nil)) - }) - - It("with cacahe client", func() { - ghClient, err = NewClient(optNotNeedAuth) - Expect(err).NotTo(HaveOccurred()) - Expect(ghClient).NotTo(Equal(nil)) - }) - }) - - Context("Client without auth enabled", func() { - var ghClient *Client - var err error - It("", func() { - ghClient, err = NewClient(optNotNeedAuth) - Expect(err).NotTo(HaveOccurred()) - Expect(ghClient).NotTo(Equal(nil)) - }) - }) - - Context("Client with auth enabled but not github token", func() { - It("should return err", func() { - _, err := NewClient(optNeedAuth) - Expect(err).To(HaveOccurred()) - }) - }) - - Context("Client with auth enabled and github token", func() { - It("should return client", func() { - ghClient, err := NewClient(optNeedAuthWithToken) - Expect(err).NotTo(HaveOccurred()) - Expect(ghClient).NotTo(Equal(nil)) - }) - }) -}) diff --git a/pkg/util/scm/github/pullrequest.go b/pkg/util/scm/github/pullrequest.go deleted file mode 100644 index 7dca2b0bd..000000000 --- a/pkg/util/scm/github/pullrequest.go +++ /dev/null @@ -1,66 +0,0 @@ -package github - -import ( - "fmt" - - "github.com/google/go-github/v42/github" - - "github.com/devstream-io/devstream/pkg/util/log" - "github.com/devstream-io/devstream/pkg/util/scm/git" -) - -type MergeMethod string - -const ( - MergeMethodSquash MergeMethod = "squash" - MergeMethodMerge MergeMethod = "merge" - MergeMethodRebase MergeMethod = "rebase" -) - -func (c *Client) NewPullRequest(commitInfo *git.CommitInfo) (int, error) { - title := commitInfo.CommitMsg - head := commitInfo.CommitBranch - base := c.Branch - body := title - mcm := false - draft := false - - pr, _, err := c.PullRequests.Create(c.Context, c.GetRepoOwner(), c.Repo, &github.NewPullRequest{ - Title: &title, - Head: &head, - Base: &base, - Body: &body, - Issue: nil, - MaintainerCanModify: &mcm, - Draft: &draft, - }) - - if err != nil { - log.Debugf("Failed to create the pr: %s.", err) - return 0, err - } - - log.Debugf("The pr has been created: #%d.", pr.GetNumber()) - - return pr.GetNumber(), nil -} - -func (c *Client) MergePullRequest(number int, mergeMethod MergeMethod) error { - commitMsg := "Initialized by DevStream" - ret, _, err := c.PullRequests.Merge(c.Context, c.GetRepoOwner(), c.Repo, number, commitMsg, &github.PullRequestOptions{ - CommitTitle: commitMsg, - SHA: "", - MergeMethod: string(mergeMethod), - DontDefaultIfBlank: false, - }) - if err != nil { - log.Debugf("Got an error when merge the pr: %s.", err) - return err - } - - if !ret.GetMerged() { - return fmt.Errorf("merge failed") - } - - return nil -} diff --git a/pkg/util/scm/github/pullrequest_test.go b/pkg/util/scm/github/pullrequest_test.go deleted file mode 100644 index 126cee6fb..000000000 --- a/pkg/util/scm/github/pullrequest_test.go +++ /dev/null @@ -1,157 +0,0 @@ -package github_test - -import ( - "fmt" - "net/http" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "github.com/onsi/gomega/ghttp" - - "github.com/devstream-io/devstream/pkg/util/scm/git" - "github.com/devstream-io/devstream/pkg/util/scm/github" -) - -var _ = Describe("NewPullRequest", func() { - const ( - fromBranch, toBranch = "fb", "tb" - owner, repoName = "owner", "repo" - rightOrg, wrongOrg = "org", "/" - ) - - var ( - s *ghttp.Server - org string - opts *git.RepoInfo - commitInfo *git.CommitInfo - ) - - JustBeforeEach(func() { - opts = &git.RepoInfo{ - Owner: owner, - Repo: repoName, - Org: org, - Branch: toBranch, - } - commitInfo = &git.CommitInfo{ - CommitBranch: fromBranch, - } - }) - - AfterEach(func() { - s.Close() - }) - - When("Create", func() { - BeforeEach(func() { - s = ghttp.NewServer() - org = wrongOrg - }) - It("url is incorrect", func() { - s.SetAllowUnhandledRequests(true) - c, err := github.NewClientWithOption(opts, s.URL()) - Expect(err).NotTo(HaveOccurred()) - Expect(c).NotTo(Equal(nil)) - n, err := c.NewPullRequest(commitInfo) - Expect(err).To(HaveOccurred()) - fmt.Println(err) - Expect(n).To(Equal(0)) - }) - }) - When("Create", func() { - BeforeEach(func() { - s = ghttp.NewServer() - org = rightOrg - }) - It("url is correct", func() { - u := github.BaseURLPath + fmt.Sprintf("/repos/%v/%v/pulls", org, repoName) - s.RouteToHandler("POST", u, func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, `{"number": 1}`) - }) - c, err := github.NewClientWithOption(opts, s.URL()) - Expect(err).NotTo(HaveOccurred()) - Expect(c).NotTo(Equal(nil)) - n, err := c.NewPullRequest(commitInfo) - Expect(err).NotTo(HaveOccurred()) - Expect(n).To(Equal(1)) - }) - }) -}) - -var _ = Describe("MergePullRequest", func() { - const ( - fromBranch, toBranch = "fb", "tb" - owner, repoName = "owner", "repo" - rightOrg, wrongOrg = "org", "/" - number = 1 - ) - - var ( - s *ghttp.Server - org string - opts *git.RepoInfo - ) - - JustBeforeEach(func() { - opts = &git.RepoInfo{ - Owner: owner, - Repo: repoName, - Org: org, - Branch: toBranch, - } - }) - - AfterEach(func() { - s.Close() - }) - - When("Merge", func() { - BeforeEach(func() { - s = ghttp.NewServer() - org = wrongOrg - }) - It("url is incorrect", func() { - s.SetAllowUnhandledRequests(true) - c, err := github.NewClientWithOption(opts, s.URL()) - Expect(err).NotTo(HaveOccurred()) - Expect(c).NotTo(Equal(nil)) - err = c.MergePullRequest(number, github.MergeMethodMerge) - Expect(err).To(HaveOccurred()) - }) - }) - When("Merge", func() { - BeforeEach(func() { - s = ghttp.NewServer() - org = rightOrg - }) - It("url is correct but merged is false", func() { - u := github.BaseURLPath + fmt.Sprintf("/repos/%v/%v/pulls/%d/merge", org, repoName, number) - s.RouteToHandler("PUT", u, func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, `{}`) - }) - c, err := github.NewClientWithOption(opts, s.URL()) - Expect(err).NotTo(HaveOccurred()) - Expect(c).NotTo(Equal(nil)) - err = c.MergePullRequest(number, github.MergeMethodMerge) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("merge failed")) - }) - }) - When("Merge", func() { - BeforeEach(func() { - s = ghttp.NewServer() - org = rightOrg - }) - It("url is correct", func() { - u := github.BaseURLPath + fmt.Sprintf("/repos/%v/%v/pulls/%d/merge", org, repoName, number) - s.RouteToHandler("PUT", u, func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, `{"merged": true}`) - }) - c, err := github.NewClientWithOption(opts, s.URL()) - Expect(err).NotTo(HaveOccurred()) - Expect(c).NotTo(Equal(nil)) - err = c.MergePullRequest(number, github.MergeMethodMerge) - Expect(err).To(Succeed()) - }) - }) -}) diff --git a/pkg/util/scm/github/release.go b/pkg/util/scm/github/release.go deleted file mode 100644 index 059578a1b..000000000 --- a/pkg/util/scm/github/release.go +++ /dev/null @@ -1,14 +0,0 @@ -package github - -import ( - "context" -) - -func (c *Client) GetLatestReleaseTagName() (string, error) { - release, _, err := c.Repositories.GetLatestRelease(context.Background(), c.Org, c.Repo) - if err != nil { - return "", err - } - - return *release.TagName, nil -} diff --git a/pkg/util/scm/github/release_test.go b/pkg/util/scm/github/release_test.go deleted file mode 100644 index 20d1fa6ec..000000000 --- a/pkg/util/scm/github/release_test.go +++ /dev/null @@ -1,53 +0,0 @@ -package github - -import ( - "fmt" - "net/http" - "testing" - - "gotest.tools/assert/cmp" - - "github.com/devstream-io/devstream/pkg/util/scm/git" -) - -type releaseTest struct { - BaseTest - wantTag string - wantErr bool -} - -func TestClient_GetLatestReleaseTagName(t *testing.T) { - mux, serverUrl, teardown := Setup() - defer teardown() - - tests := []releaseTest{ - { - BaseTest{"base err != nil", GetClientWithOption( - t, &git.RepoInfo{Owner: ""}, serverUrl, - ), - "/repos2/o/r/releases/latest", http.MethodGet, false, "", ""}, - "", true}, - { - BaseTest{"base 200", GetClientWithOption( - t, &git.RepoInfo{Owner: "", Org: "o", Repo: "r"}, serverUrl, - ), - "/repos/o/r/releases/latest", http.MethodGet, false, "", `{"id":3,"tag_name":"v1.0.0"}`}, - "v1.0.0", false}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - mux.HandleFunc(tt.registerUrl, func(w http.ResponseWriter, r *http.Request) { - DoTestMethod(t, r, tt.wantMethod) - DoTestBody(t, r, "") - fmt.Fprint(w, `{"id":3,"tag_name":"v1.0.0"}`) - }) - tag, err := tt.client.GetLatestReleaseTagName() - if (err != nil) != tt.wantErr { - t.Errorf("client.GetLatestReleaseTagName returned error: %v", err) - } - if !cmp.Equal(tag, tt.wantTag)().Success() { - t.Errorf("Repositories.GenerateReleaseNotes returned %+v, want %+v", tag, tt.wantTag) - } - }) - } -} diff --git a/pkg/util/scm/github/repo.go b/pkg/util/scm/github/repo.go deleted file mode 100644 index 5d1471561..000000000 --- a/pkg/util/scm/github/repo.go +++ /dev/null @@ -1,165 +0,0 @@ -package github - -import ( - "fmt" - "net/http" - "strings" - - "github.com/google/go-github/v42/github" - - "github.com/devstream-io/devstream/pkg/util/downloader" - "github.com/devstream-io/devstream/pkg/util/log" - "github.com/devstream-io/devstream/pkg/util/pkgerror" - "github.com/devstream-io/devstream/pkg/util/scm/git" -) - -const ( - webhookName = "devstream_webhook" - defaultLatestCodeZipfileDownloadUrlFormat = "https://codeload.github.com/%s/%s/zip/refs/heads/%s?archive=zip" -) - -// DownloadRepo will download repo, return repo local path and error -func (c *Client) DownloadRepo() (string, error) { - latestCodeZipfileDownloadLocation := downloader.ResourceLocation(fmt.Sprintf( - defaultLatestCodeZipfileDownloadUrlFormat, c.GetRepoOwner(), c.Repo, c.Branch, - )) - log.Debugf("github get repo download url: %s.", latestCodeZipfileDownloadLocation) - log.Info("github start to download repoTemplate...") - return latestCodeZipfileDownloadLocation.Download() -} - -func (c *Client) CreateRepo(org, defaultBranch string) error { - repo := &github.Repository{ - Name: &c.Repo, - DefaultBranch: github.String(defaultBranch), - } - - if org != "" { - log.Debugf("github prepare to create an organization repository: %s/%s", org, repo.GetName()) - } - _, _, err := c.Repositories.Create(c.Context, org, repo) - if err != nil { - return err - } - return nil -} - -func (c *Client) DeleteRepo() error { - response, err := c.Client.Repositories.Delete(c.Context, c.GetRepoOwner(), c.Repo) - - // error reason is not 404 - if err != nil && !strings.Contains(err.Error(), "404") { - log.Errorf("Delete repo failed: %s.", err) - return err - } - - if response.StatusCode == http.StatusNotFound { - log.Warnf("GitHub repo %s was not found. Nothing to does here.", c.Repo) - return nil - } - - log.Successf("GitHub repo %s removed.", c.Repo) - return nil -} - -func (c *Client) DescribeRepo() (*git.RepoInfo, error) { - repo, resp, err := c.Client.Repositories.Get( - c.Context, - c.GetRepoOwner(), - c.Repo) - - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, nil - } - - if err != nil { - return nil, err - } - repoInfo := &git.RepoInfo{ - Repo: repo.GetName(), - Owner: repo.GetOwner().GetLogin(), - Org: repo.GetOrganization().GetLogin(), - RepoType: "github", - CloneURL: git.ScmURL(repo.GetCloneURL()), - } - return repoInfo, nil -} - -func (c *Client) InitRepo() error { - // It's ok to give the opts.Org to CreateRepo() when create a repository for an authenticated user. - if err := c.CreateRepo(c.Org, c.Branch); err != nil { - // recreate if set tryTime - log.Errorf("Failed to create repo: %s.", err) - return err - } - log.Successf("The repo %s has been created.", c.Repo) - - // upload a placeholder file to make repo not empty - if err := c.CreateFile([]byte(" "), repoPlaceHolderFileName, c.Branch); err != nil { - log.Debugf("Failed to add the first file: %s. ", err) - return err - } - log.Debugf("Added the .placeholder file.") - return nil -} - -// ProtectBranch will protect the special branch -func (c *Client) ProtectBranch(branch string) error { - req := &github.ProtectionRequest{ - EnforceAdmins: false, - RequiredPullRequestReviews: &github.PullRequestReviewsEnforcementRequest{ - RequireCodeOwnerReviews: true, - DismissStaleReviews: true, - RequiredApprovingReviewCount: 1, - }, - RequiredConversationResolution: github.Bool(true), - } - - repo, err := c.DescribeRepo() - if err != nil { - return err - } - - _, _, err = c.Repositories.UpdateBranchProtection(c.Context, repo.Owner, repo.Repo, branch, req) - if err != nil { - return err - } - - log.Infof("The branch \"%s\" has been protected", branch) - return nil -} - -func (c *Client) AddWebhook(webhookConfig *git.WebhookConfig) error { - hook := new(github.Hook) - hook.Name = github.String(webhookName) - hook.Events = []string{"pull_request", "push"} - hook.Config = map[string]interface{}{} - hook.Config["url"] = webhookConfig.Address - hook.Config["content_type"] = "json" - _, _, err := client.Repositories.CreateHook(c.Context, c.Owner, c.Repo, hook) - if err != nil && !pkgerror.CheckErrorMatchByMessage(err, errHookAlreadyExist) { - return err - } - return nil -} - -func (c *Client) DeleteWebhook(webhookConfig *git.WebhookConfig) error { - // list 100 webhooks of this repo - allHooks, _, err := client.Repositories.ListHooks( - c.Context, c.Owner, c.Repo, &github.ListOptions{ - PerPage: 100, - }, - ) - if err != nil { - log.Debugf("github list webhook failed: %v", err) - return err - } - for _, hook := range allHooks { - if *hook.Name == webhookName { - _, err := client.Repositories.DeleteHook(c.Context, c.Owner, c.Repo, *hook.ID) - return err - } - } - log.Debugf("github webhook is already deleted") - return nil -} diff --git a/pkg/util/scm/github/repo_test.go b/pkg/util/scm/github/repo_test.go deleted file mode 100644 index 88cdefbe3..000000000 --- a/pkg/util/scm/github/repo_test.go +++ /dev/null @@ -1,180 +0,0 @@ -package github_test - -import ( - "fmt" - "net/http" - "strconv" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "github.com/onsi/gomega/ghttp" - - "github.com/devstream-io/devstream/pkg/util/scm/git" - "github.com/devstream-io/devstream/pkg/util/scm/github" -) - -var _ = Describe("Repo", func() { - var ( - s *ghttp.Server - rightClient, wrongClient *github.Client - owner, repoName, org = "o", "r", "or" - ) - // var rep *go_github.Repository - defaultBranch := "db" - mainBranch := "mab" - rightOpt := &git.RepoInfo{ - Owner: owner, - Repo: repoName, - Org: org, - Branch: mainBranch, - } - wrongOpt := &git.RepoInfo{ - Owner: owner, - Repo: "", - Org: org, - } - BeforeEach(func() { - s = ghttp.NewServer() - rightClient, _ = github.NewClientWithOption(rightOpt, s.URL()) - Expect(rightClient).NotTo(Equal(nil)) - wrongClient, _ = github.NewClientWithOption(wrongOpt, s.URL()) - Expect(wrongClient).NotTo(Equal(nil)) - }) - - AfterEach(func() { - s.Close() - }) - - Context("CreateRepo", func() { - BeforeEach(func() { - s.Reset() - s.SetAllowUnhandledRequests(true) - }) - It("create with status 500", func() { - err := wrongClient.CreateRepo(org, defaultBranch) - Expect(err).NotTo(Succeed()) - Expect(err.Error()).To(ContainSubstring(strconv.Itoa(http.StatusInternalServerError))) - }) - It("create with status 200", func() { - s.Reset() - s.SetAllowUnhandledRequests(true) - s.SetUnhandledRequestStatusCode(http.StatusOK) - err := wrongClient.CreateRepo(org, defaultBranch) - Expect(err).To(Succeed()) - }) - }) - - Context("DeleteRepo", func() { - BeforeEach(func() { - s.Reset() - s.SetAllowUnhandledRequests(true) - }) - It("DeleteRepo with status 500", func() { - err := rightClient.DeleteRepo() - Expect(err).NotTo(Succeed()) - Expect(err.Error()).To(ContainSubstring(strconv.Itoa(http.StatusInternalServerError))) - }) - It("DeleteRepo with status 404", func() { - s.SetUnhandledRequestStatusCode(http.StatusNotFound) - err := wrongClient.DeleteRepo() - Expect(err).To(Succeed()) - }) - It("DeleteRepo with status 200", func() { - s.SetUnhandledRequestStatusCode(http.StatusOK) - err := wrongClient.DeleteRepo() - Expect(err).To(Succeed()) - }) - }) - - Context("GetRepoDescription", func() { - BeforeEach(func() { - s.Reset() - s.SetAllowUnhandledRequests(true) - }) - - It("GetRepoDescription with status 500", func() { - s.SetUnhandledRequestStatusCode(http.StatusInternalServerError) - r, err := rightClient.DescribeRepo() - Expect(err).NotTo(Succeed()) - Expect(err.Error()).To(ContainSubstring(strconv.Itoa(http.StatusInternalServerError))) - var wantR *git.RepoInfo - Expect(r).To(Equal(wantR)) - }) - It("GetRepoDescription with no error and status 200", func() { - u := fmt.Sprintf("/repos/%v/%v", org, repoName) - s.Reset() - s.RouteToHandler("GET", github.BaseURLPath+u, func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, ``) - }) - r, err := rightClient.DescribeRepo() - Expect(err).To(Succeed()) - wantR := &git.RepoInfo{ - RepoType: "github", - } - Expect(r).To(Equal(wantR)) - }) - }) - - Context("InitRepo", func() { - It("CreateRepo with status 500", func() { - s.Reset() - s.SetAllowUnhandledRequests(true) - s.SetUnhandledRequestStatusCode(http.StatusInternalServerError) - err := rightClient.InitRepo() - Expect(err).NotTo(Succeed()) - Expect(err.Error()).To(ContainSubstring(strconv.Itoa(http.StatusInternalServerError))) - }) - It("CreateFile with status 500", func() { - u := github.BaseURLPath + fmt.Sprintf("/orgs/%v/repos", org) - s.Reset() - s.SetAllowUnhandledRequests(true) - s.SetUnhandledRequestStatusCode(http.StatusInternalServerError) - s.RouteToHandler("POST", u, func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, `{}`) - }) - err := rightClient.InitRepo() - fmt.Println(err) - Expect(err).NotTo(Succeed()) - Expect(err.Error()).To(ContainSubstring(strconv.Itoa(http.StatusInternalServerError))) - }) - It("CreateFile with status 200", func() { - u := github.BaseURLPath + fmt.Sprintf("/orgs/%v/repos", org) - u2 := github.BaseURLPath + fmt.Sprintf("/repos/%s/%s/contents/%s", org, repoName, ".placeholder") - s.Reset() - s.RouteToHandler("POST", u, func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, `{}`) - }) - s.RouteToHandler("PUT", u2, func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, `{}`) - }) - err := rightClient.InitRepo() - Expect(err).To(Succeed()) - }) - }) - - Context("ProtectBranch", func() { - BeforeEach(func() { - s.Reset() - s.SetAllowUnhandledRequests(true) - }) - It("ProtectBranch with status 500", func() { - u := fmt.Sprintf("/repos/%v/%v", org, repoName) - s.Reset() - s.SetUnhandledRequestStatusCode(http.StatusInternalServerError) - s.SetAllowUnhandledRequests(true) - s.RouteToHandler("GET", github.BaseURLPath+u, func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, ``) - }) - err := rightClient.ProtectBranch(mainBranch) - Expect(err).NotTo(Succeed()) - Expect(err.Error()).To(ContainSubstring(strconv.Itoa(http.StatusInternalServerError))) - }) - It("ProtectBranch with status 200", func() { - s.Reset() - s.SetAllowUnhandledRequests(true) - s.SetUnhandledRequestStatusCode(http.StatusOK) - err := rightClient.ProtectBranch(mainBranch) - Expect(err).To(Succeed()) - }) - }) -}) diff --git a/pkg/util/scm/github/secrets.go b/pkg/util/scm/github/secrets.go deleted file mode 100644 index 2cd41a007..000000000 --- a/pkg/util/scm/github/secrets.go +++ /dev/null @@ -1,100 +0,0 @@ -package github - -import ( - "context" - crypto_rand "crypto/rand" - "encoding/base64" - "fmt" - "net/http" - - "github.com/google/go-github/v42/github" - "golang.org/x/crypto/nacl/box" -) - -// AddRepoSecret adds a secret to a GitHub repo. -func (c *Client) AddRepoSecret(secretKey, secretValue string) error { - // The transmission of the secret value to GitHub using the api requires the secret value to be encrypted - // with the public key of the repo. - // First, the public key of the repo is retrieved. - ctx := context.Background() - publicKey, _, err := client.Actions.GetRepoPublicKey(ctx, c.GetRepoOwner(), c.Repo) - if err != nil { - return err - } - - // Second, we encrypt the secret to get a github.EncodedSecret - encryptedSecret, err := encryptSecretWithPublicKey(publicKey, secretKey, secretValue) - if err != nil { - return err - } - - // Finally, the github.EncodedSecret is passed into the GitHub client.Actions.CreateOrUpdateRepoSecret method to - // create the secret in the GitHub repo - if _, err := client.Actions.CreateOrUpdateRepoSecret(ctx, c.GetRepoOwner(), c.Repo, encryptedSecret); err != nil { - return fmt.Errorf("github Actions.CreateOrUpdateRepoSecret returned error: %v", err) - } - - return nil -} - -func encryptSecretWithPublicKey(publicKey *github.PublicKey, secretName string, secretValue string) (*github.EncryptedSecret, error) { - // The public key comes base64 encoded, so it must be decoded prior to use. - decodedPublicKey, err := base64.StdEncoding.DecodeString(publicKey.GetKey()) - if err != nil { - return nil, fmt.Errorf("base64.StdEncoding.DecodeString was unable to decode public key: %v", err) - } - - // The decode key is converted into a fixed size byte array. - var boxKey [32]byte - - // The secret value is converted into a slice of bytes. - copy(boxKey[:], decodedPublicKey) - secretBytes := []byte(secretValue) - - // The secret is encrypted with box.SealAnonymous using the repo's decoded public key. - encryptedBytes, err := box.SealAnonymous([]byte{}, secretBytes, &boxKey, crypto_rand.Reader) - if err != nil { - return nil, fmt.Errorf("box.SealAnonymous failed with error %w", err) - } - - // The encrypted secret is encoded as a base64 string to be used in a github.EncodedSecret type. - encryptedString := base64.StdEncoding.EncodeToString(encryptedBytes) - keyID := publicKey.GetKeyID() - encryptedSecret := &github.EncryptedSecret{ - Name: secretName, - KeyID: keyID, - EncryptedValue: encryptedString, - } - return encryptedSecret, nil -} - -// DeleteRepoSecret deletes a secret in a GitHub repo. -func (c *Client) DeleteRepoSecret(secretKey string) error { - ctx := context.Background() - response, err := client.Actions.DeleteRepoSecret(ctx, c.GetRepoOwner(), c.Repo, secretKey) - - if response != nil && response.StatusCode == http.StatusNotFound { - return nil - } - - if err != nil { - return fmt.Errorf("github Actions.DeleteRepoSecret returned error: %v", err) - } - - return nil -} - -// RepoSecretExists detects if a secret exists in a GitHub repo. -func (c *Client) RepoSecretExists(secretKey string) (bool, error) { - ctx := context.Background() - _, response, err := client.Actions.GetRepoSecret(ctx, c.GetRepoOwner(), c.Repo, secretKey) - - if response != nil && response.StatusCode == http.StatusNotFound { - return false, nil - } - - if err != nil { - return false, fmt.Errorf("github Actions.GetRepoSecret returned error: %v", err) - } - return true, nil -} diff --git a/pkg/util/scm/github/secrets_test.go b/pkg/util/scm/github/secrets_test.go deleted file mode 100644 index 53de7da0e..000000000 --- a/pkg/util/scm/github/secrets_test.go +++ /dev/null @@ -1,133 +0,0 @@ -package github_test - -import ( - "fmt" - "net/http" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/pkg/util/scm/git" - "github.com/devstream-io/devstream/pkg/util/scm/github" -) - -var _ = Describe("Secrets", func() { - var owner, repoName, org = "o", "r", "or" - var registerUrl string = fmt.Sprintf("/repos/%v/%v/actions/secrets/public-key", org, repoName) - sk, sv := "sk", "sv" - - Context("does AddRepoSecret", func() { - It("step1: does GetRepoPublicKey with wrong url", func() { - rightClient, err := github.NewClientWithOption(&git.RepoInfo{ - Owner: owner, - Repo: repoName, - Org: org, - }, serverURL) - Expect(err).NotTo(HaveOccurred()) - Expect(rightClient).NotTo(Equal(nil)) - err = rightClient.AddRepoSecret(sk, sv) - Expect(err).NotTo(Succeed()) - }) - - It("step2: does AddRepoSecret with correct url", func() { - registerUrl = fmt.Sprintf("/repos/%v/%v/actions/secrets/public-key", org, repoName) - mux.HandleFunc(registerUrl, func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, `{"key_id":"1234","key":"2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvv1234"}`) - }) - rightClient, err := github.NewClientWithOption(&git.RepoInfo{ - Owner: owner, - Repo: repoName, - Org: org, - }, serverURL) - Expect(err).NotTo(HaveOccurred()) - Expect(rightClient).NotTo(Equal(nil)) - err = rightClient.AddRepoSecret(sk, sv) - Expect(err).NotTo(Succeed()) - }) - - It("step3: does CreateOrUpdateRepoSecret with wrong url", func() { - rightClient, err := github.NewClientWithOption(&git.RepoInfo{ - Owner: owner, - Repo: repoName, - Org: org, - }, serverURL) - Expect(err).NotTo(HaveOccurred()) - Expect(rightClient).NotTo(Equal(nil)) - - err = rightClient.AddRepoSecret(sk, sv) - Expect(err).NotTo(Succeed()) - }) - - It("step3: does CreateOrUpdateRepoSecret with correct url", func() { - registerUrl = fmt.Sprintf("/repos/%v/%v/actions/secrets/%v", org, repoName, sk) - mux.HandleFunc(registerUrl, func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, `{"key_id":"1234","key":"2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvv1234"}`) - }) - rightClient, err := github.NewClientWithOption(&git.RepoInfo{ - Owner: owner, - Repo: repoName, - Org: org, - }, serverURL) - Expect(err).NotTo(HaveOccurred()) - Expect(rightClient).NotTo(Equal(nil)) - err = rightClient.AddRepoSecret(sk, sv) - - Expect(err).To(Succeed()) - }) - - }) - - Context("RepoSecretExists", func() { - It("does RepoSecretExists with wrong url", func() { - wrongClient, err := github.NewClientWithOption(&git.RepoInfo{ - Owner: owner, - Repo: "rrrr", - Org: "ororor", - }, serverURL) - Expect(err).NotTo(HaveOccurred()) - Expect(wrongClient).NotTo(Equal(nil)) - b, err := wrongClient.RepoSecretExists(sk) - Expect(err).To(Succeed()) - Expect(b).To(Equal(false)) - }) - - It("does RepoSecretExists with correct url", func() { - rightClient, err := github.NewClientWithOption(&git.RepoInfo{ - Owner: owner, - Repo: repoName, - Org: org, - }, serverURL) - Expect(err).NotTo(HaveOccurred()) - Expect(rightClient).NotTo(Equal(nil)) - b, err := rightClient.RepoSecretExists(sk) - Expect(err).To(Succeed()) - Expect(b).To(Equal(true)) - }) - }) - - Context("DeleteRepoSecret", func() { - It("does DeleteRepoSecret with wrong url", func() { - wrongClient, err := github.NewClientWithOption(&git.RepoInfo{ - Owner: owner, - Repo: "rrrr", - Org: "ororor", - }, serverURL) - Expect(err).NotTo(HaveOccurred()) - Expect(wrongClient).NotTo(Equal(nil)) - err = wrongClient.DeleteRepoSecret(sk) - Expect(err).To(Succeed()) - }) - - It("does DeleteRepoSecret with correct url", func() { - rightClient, err := github.NewClientWithOption(&git.RepoInfo{ - Owner: owner, - Repo: repoName, - Org: org, - }, serverURL) - Expect(err).NotTo(HaveOccurred()) - Expect(rightClient).NotTo(Equal(nil)) - err = rightClient.DeleteRepoSecret(sk) - Expect(err).To(Succeed()) - }) - }) -}) diff --git a/pkg/util/scm/github/workflow.go b/pkg/util/scm/github/workflow.go deleted file mode 100644 index bc538836f..000000000 --- a/pkg/util/scm/github/workflow.go +++ /dev/null @@ -1,192 +0,0 @@ -package github - -import ( - "fmt" - "net/http" - - mapset "github.com/deckarep/golang-set/v2" - - "github.com/google/go-github/v42/github" - - "github.com/devstream-io/devstream/pkg/util/log" - "github.com/devstream-io/devstream/pkg/util/mapz" -) - -// Workflow is the struct for a GitHub Actions Workflow. -type Workflow struct { - CommitMessage string - WorkflowFileName string - WorkflowContent string -} - -func (c *Client) AddWorkflow(workflow *Workflow, branch string) error { - sha, err := c.getFileSHA(workflow.WorkflowFileName) - if err != nil { - return err - } - if sha != "" { - log.Infof("GitHub Actions workflow %s already exists.", workflow.WorkflowFileName) - return nil - } - - // Note: the file needs to be absent from the repository as you are not - // specifying a SHA reference here. - opts := &github.RepositoryContentFileOptions{ - Message: github.String(workflow.CommitMessage), - Content: []byte(workflow.WorkflowContent), - Branch: github.String(branch), - } - - log.Infof("Creating GitHub Actions workflow %s ...", workflow.WorkflowFileName) - _, _, err = c.Client.Repositories.CreateFile( - c.Context, - c.GetRepoOwner(), - c.Repo, - generateGitHubWorkflowFileByName(workflow.WorkflowFileName), - opts) - - if err != nil { - return err - } - log.Successf("Github Actions workflow %s created.", workflow.WorkflowFileName) - return nil -} -func (c *Client) DeleteWorkflow(workflow *Workflow, branch string) error { - sha, err := c.getFileSHA(workflow.WorkflowFileName) - if err != nil { - return err - } - if sha == "" { - log.Successf("Github Actions workflow %s already removed.", workflow.WorkflowFileName) - return nil - } - - // Note: the file needs to be absent from the repository as you are not - // specifying a SHA reference here. - opts := &github.RepositoryContentFileOptions{ - Message: github.String(workflow.CommitMessage), - SHA: github.String(sha), - Branch: github.String(branch), - } - - log.Infof("Deleting GitHub Actions workflow %s ...", workflow.WorkflowFileName) - _, _, err = c.Client.Repositories.DeleteFile( - c.Context, - c.GetRepoOwner(), - c.Repo, - generateGitHubWorkflowFileByName(workflow.WorkflowFileName), - opts) - - if err != nil { - return err - } - log.Successf("GitHub Actions workflow %s removed.", workflow.WorkflowFileName) - return nil -} - -// VerifyWorkflows get the workflows with names "wf1.yml", "wf2.yml", then: -// If all workflows is ok => return ({"wf1.yml":nil, "wf2.yml:nil}, nil) -// If some error occurred => return (nil, error) -// If wf1.yml is not found => return ({"wf1.yml":error("not found"), "wf2.yml:nil},nil) -func (c *Client) VerifyWorkflows(workflows []*Workflow) (map[string]error, error) { - wsFiles := make([]string, 0, len(workflows)) - for _, w := range workflows { - wsFiles = append(wsFiles, w.WorkflowFileName) - } - fmt.Printf("Workflow files: %v", wsFiles) - - _, dirContent, resp, err := c.Client.Repositories.GetContents( - c.Context, - c.GetRepoOwner(), - c.Repo, - ".github/workflows", - &github.RepositoryContentGetOptions{}, - ) - - // StatusCode == 404 - if resp != nil && resp.StatusCode == http.StatusNotFound { - log.Errorf("GetContents return with status code 404.") - retMap := mapz.FillMapWithStrAndError(wsFiles, fmt.Errorf("not found")) - return retMap, nil - } - - // error reason is not 404 - if err != nil { - log.Errorf("GetContents failed with error: %s.", err) - return nil, err - } - - // StatusCode == 200 - log.Success("GetContents return with status code 200.") - var filesInRemoteDir = make([]string, 0) - for _, f := range dirContent { - log.Infof("Found remote file: %s.", f.GetName()) - filesInRemoteDir = append(filesInRemoteDir, f.GetName()) - } - - lostFiles := mapset.NewSet(wsFiles...).Difference(mapset.NewSet(filesInRemoteDir...)).ToSlice() - // all files exist - if len(lostFiles) == 0 { - log.Info("All files exist.") - retMap := mapz.FillMapWithStrAndError(wsFiles, nil) - return retMap, nil - } - // some files lost - log.Warn("Some files lost.") - retMap := mapz.FillMapWithStrAndError(wsFiles, nil) - for _, f := range lostFiles { - log.Infof("Lost file: %s.", f) - retMap[f] = fmt.Errorf("not found") - } - return retMap, nil -} - -func (c *Client) GetWorkflowPath() (string, error) { - _, _, resp, err := c.Client.Repositories.GetContents( - c.Context, - c.GetRepoOwner(), - c.Repo, - ".github/workflows", - &github.RepositoryContentGetOptions{}, - ) - - if resp != nil && resp.StatusCode == http.StatusNotFound { - return "", nil - } - - if err != nil { - return "", err - } - - return resp.Request.URL.Path, nil -} - -func (c *Client) FetchRemoteContent(wsFiles []string) ([]string, map[string]error, error) { - var filesInRemoteDir = make([]string, 0) - _, dirContent, resp, err := c.Repositories.GetContents( - c.Context, - c.GetRepoOwner(), - c.Repo, - ".github/workflows", - &github.RepositoryContentGetOptions{}, - ) - - if resp != nil && resp.StatusCode == http.StatusNotFound { - log.Error("GetContents returned with status code 404.") - retMap := mapz.FillMapWithStrAndError(wsFiles, fmt.Errorf("not found")) - return nil, retMap, nil - } - - // error reason is not 404 - if err != nil { - log.Errorf("GetContents failed with error: %s.", err) - return nil, nil, err - } - - log.Info("GetContents successfully.") - for _, f := range dirContent { - log.Infof("Found remote file: %s.", f.GetName()) - filesInRemoteDir = append(filesInRemoteDir, f.GetName()) - } - return filesInRemoteDir, nil, nil -} diff --git a/pkg/util/scm/github/workflow_helper.go b/pkg/util/scm/github/workflow_helper.go deleted file mode 100644 index 0ae1b4d4c..000000000 --- a/pkg/util/scm/github/workflow_helper.go +++ /dev/null @@ -1,37 +0,0 @@ -package github - -import ( - "fmt" - "net/http" - - "github.com/google/go-github/v42/github" -) - -func generateGitHubWorkflowFileByName(f string) string { - return fmt.Sprintf(".github/workflows/%s", f) -} - -// getFileSHA will try to collect the SHA hash value of the file, then return it. the return values will be: -// 1. If file exists without error -> string(SHA), nil -// 2. If some errors occurred -> return "", err -// 3. If file not found without error -> return "", nil -func (c *Client) getFileSHA(filename string) (string, error) { - content, _, resp, err := c.Client.Repositories.GetContents( - c.Context, - c.GetRepoOwner(), - c.Repo, - generateGitHubWorkflowFileByName(filename), - &github.RepositoryContentGetOptions{}, - ) - - if resp != nil && resp.StatusCode == http.StatusNotFound { - return "", nil - } - - // error reason is not 404 - if err != nil { - return "", err - } - - return *content.SHA, nil -} diff --git a/pkg/util/scm/github/workflow_helper_test.go b/pkg/util/scm/github/workflow_helper_test.go deleted file mode 100644 index e3d2e679f..000000000 --- a/pkg/util/scm/github/workflow_helper_test.go +++ /dev/null @@ -1,91 +0,0 @@ -package github - -import ( - "fmt" - "net/http" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "github.com/onsi/gomega/ghttp" - - "github.com/devstream-io/devstream/pkg/util/scm/git" -) - -var _ = Describe("WorkflowHelper", func() { - var s *ghttp.Server - var rightClient, wrongClient *Client - owner, repoName, f, org := "o", "r", ".github/workflows/test", "or" - u := fmt.Sprintf("/repos/%s/%s/contents/%s", org, repoName, generateGitHubWorkflowFileByName(f)) - rightOpt := &git.RepoInfo{ - Owner: owner, - Repo: repoName, - Org: org, - } - wrongOpt := &git.RepoInfo{ - Owner: owner, - Repo: "", - Org: org, - } - BeforeEach(func() { - s = ghttp.NewServer() - rightClient, _ = NewClientWithOption(rightOpt, s.URL()) - Expect(rightClient).NotTo(Equal(nil)) - wrongClient, _ = NewClientWithOption(wrongOpt, s.URL()) - Expect(wrongClient).NotTo(Equal(nil)) - }) - - AfterEach(func() { - // shut down the server between tests - s.Close() - }) - - Describe("fetching getFileSHA", func() { - - It("error reason is not 404", func() { - s.Reset() - s.RouteToHandler("GET", BaseURLPath+u, func(w http.ResponseWriter, req *http.Request) { - w.WriteHeader(http.StatusInternalServerError) - }) - - res, err := rightClient.getFileSHA(f) - Expect(s.ReceivedRequests()).Should(HaveLen(1)) - Expect(err.Error()).NotTo(ContainSubstring("404")) - Expect(res).To(Equal("")) - }) - - It("error reason is 404", func() { - s.Reset() - s.SetAllowUnhandledRequests(true) - s.SetUnhandledRequestStatusCode(http.StatusNotFound) - res, err := rightClient.getFileSHA(f) - Expect(s.ReceivedRequests()).Should(HaveLen(1)) - Expect(err).To(Succeed()) - Expect(res).To(Equal("")) - }) - - It("no error occurred", func() { - s.Reset() - s.RouteToHandler("GET", BaseURLPath+u, func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, `{ - "sha": "21212" - }`) - }) - res, err := rightClient.getFileSHA(f) - Expect(s.ReceivedRequests()).Should(HaveLen(1)) - Expect(err).To(Succeed()) - Expect(res).To(Equal("21212")) - }) - - It("finally got unexpected error", func() { - s.Reset() - s.RouteToHandler("GET", BaseURLPath+u, func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusInternalServerError) - fmt.Fprint(w, `{}`) - }) - res, err := rightClient.getFileSHA(f) - Expect(s.ReceivedRequests()).Should(HaveLen(1)) - Expect(err.Error()).To(ContainSubstring("500")) - Expect(res).To(Equal("")) - }) - }) -}) diff --git a/pkg/util/scm/github/workflow_test.go b/pkg/util/scm/github/workflow_test.go deleted file mode 100644 index f1a2fb5b3..000000000 --- a/pkg/util/scm/github/workflow_test.go +++ /dev/null @@ -1,306 +0,0 @@ -package github_test - -import ( - "fmt" - "net/http" - "strconv" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "github.com/onsi/gomega/ghttp" - - "github.com/devstream-io/devstream/pkg/util/scm/git" - "github.com/devstream-io/devstream/pkg/util/scm/github" -) - -var _ = Describe("Workflow", func() { - var s *ghttp.Server - var rightClient, wrongClient *github.Client - var wantS []string - var wantM map[string]error - owner, repoName, f, org := "o", "r", ".github/workflows", "or" - branch := "b" - wsFiles := []string{"file1", "file2"} - rightOpt := &git.RepoInfo{ - Owner: owner, - Repo: repoName, - Org: org, - } - wrongOpt := &git.RepoInfo{ - Owner: owner, - Repo: "", - Org: org, - } - partialFilesInRemoteDir := `[{ - "type": "file", - "encoding": "base64", - "size": 20678, - "name": "file1", - "path": "LICENSE" - }]` - fullFilesInRemoteDir := `[ - { - "type": "file", - "encoding": "base64", - "size": 20678, - "name": "file1", - "path": "LICENSE" - }, - { - "type": "file", - "encoding": "base64", - "size": 20678, - "name": "file2", - "path": "LICENSE" - } - ]` - workflows := []*github.Workflow{ - {WorkflowFileName: "file1"}, - {WorkflowFileName: "file2"}, - } - u := fmt.Sprintf("/repos/%s/%s/contents/%s", org, repoName, f) - u2 := fmt.Sprintf("/repos/%s/%s/contents/%s", org, repoName, ".github/workflows/"+workflows[0].WorkflowFileName) - allFileFoundMap := map[string]error{ - "file1": nil, - "file2": nil, - } - allFileNotFoundMap := map[string]error{ - "file1": fmt.Errorf("not found"), - "file2": fmt.Errorf("not found"), - } - partialFileNotFoundMap := map[string]error{ - "file1": nil, - "file2": fmt.Errorf("not found"), - } - BeforeEach(func() { - s = ghttp.NewServer() - rightClient, _ = github.NewClientWithOption(rightOpt, s.URL()) - Expect(rightClient).NotTo(Equal(nil)) - wrongClient, _ = github.NewClientWithOption(wrongOpt, s.URL()) - Expect(wrongClient).NotTo(Equal(nil)) - }) - - AfterEach(func() { - // shut down the server between tests - s.Close() - }) - - Describe("AddWorkflow", func() { - It("got sha and WorkflowFileName already exists", func() { - s.Reset() - s.RouteToHandler("GET", github.BaseURLPath+u2, func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, `{"sha": "1212"}`) - }) - err := rightClient.AddWorkflow(workflows[0], branch) - Expect(s.ReceivedRequests()).Should(HaveLen(1)) - Expect(err).To(Succeed()) - }) - It("got getFileSHA error", func() { - s.Reset() - s.SetAllowUnhandledRequests(true) - s.RouteToHandler("GET", github.BaseURLPath+u2, func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, `{"sha": "1212"}`) - }) - err := wrongClient.AddWorkflow(workflows[0], branch) - Expect(s.ReceivedRequests()).Should(HaveLen(1)) - Expect(err).NotTo(Succeed()) - Expect(err.Error()).To(ContainSubstring("500")) - }) - It("WorkflowFileName not exist and add one failed", func() { - s.Reset() - s.SetAllowUnhandledRequests(true) - s.SetUnhandledRequestStatusCode(http.StatusNotFound) - err := wrongClient.AddWorkflow(workflows[0], branch) - Expect(err).NotTo(Succeed()) - }) - It("WorkflowFileName not exist and add one successfully", func() { - s.Reset() - s.RouteToHandler("PUT", github.BaseURLPath+u2, func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, ``) - }) - s.RouteToHandler("GET", github.BaseURLPath+u2, func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, `{"sha": ""}`) - }) - err := rightClient.AddWorkflow(workflows[0], branch) - Expect(s.ReceivedRequests()).Should(HaveLen(2)) - Expect(err).To(Succeed()) - }) - }) - - Describe("DeleteWorkflow", func() { - It("got sha and WorkflowFileName ok", func() { - s.Reset() - s.RouteToHandler("GET", github.BaseURLPath+u2, func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, `{"sha": ""}`) - }) - err := rightClient.DeleteWorkflow(workflows[0], branch) - Expect(s.ReceivedRequests()).Should(HaveLen(1)) - Expect(err).To(Succeed()) - }) - It("got getFileSHA error", func() { - s.Reset() - s.SetAllowUnhandledRequests(true) - err := wrongClient.DeleteWorkflow(workflows[0], branch) - Expect(s.ReceivedRequests()).Should(HaveLen(1)) - Expect(err).NotTo(Succeed()) - Expect(err.Error()).To(ContainSubstring("500")) - }) - It("WorkflowFileName exist but delete it failed", func() { - s.Reset() - s.SetAllowUnhandledRequests(true) - s.RouteToHandler("GET", github.BaseURLPath+u2, func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, `{"sha": "213"}`) - }) - s.SetUnhandledRequestStatusCode(http.StatusNotFound) - err := rightClient.DeleteWorkflow(workflows[0], branch) - Expect(s.ReceivedRequests()).Should(HaveLen(2)) - Expect(err).NotTo(Succeed()) - Expect(err.Error()).To(ContainSubstring("404")) - }) - It("WorkflowFileName exists and delete it successfully", func() { - s.Reset() - s.RouteToHandler("DELETE", github.BaseURLPath+u2, func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, ``) - }) - s.RouteToHandler("GET", github.BaseURLPath+u2, func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, `{"sha": "213"}`) - }) - err := rightClient.DeleteWorkflow(workflows[0], branch) - Expect(s.ReceivedRequests()).Should(HaveLen(2)) - Expect(err).To(Succeed()) - }) - }) - - Describe("FetchRemoteContent", func() { - It("got GetContents error is both not equal nil and 404", func() { - wsFiles := []string{"file1", "file2"} - s.Reset() - s.SetAllowUnhandledRequests(true) - s.SetUnhandledRequestStatusCode(http.StatusNotImplemented) - s, m, err := wrongClient.FetchRemoteContent(wsFiles) - Expect(s).To(Equal(wantS)) - Expect(m).To(Equal(wantM)) - Expect(err.Error()).To(ContainSubstring(strconv.Itoa(http.StatusNotImplemented))) - }) - It("got GetContents error is not equal nil and equal 404", func() { - s.Reset() - s.SetAllowUnhandledRequests(true) - s.SetUnhandledRequestStatusCode(http.StatusNotFound) - s, m, err := wrongClient.FetchRemoteContent(wsFiles) - var wantS []string - Expect(s).To(Equal(wantS)) - Expect(m).To(Equal(allFileNotFoundMap)) - Expect(err).To(Succeed()) - }) - It("got GetContents error is nil and status is not 200", func() { - s.Reset() - s.RouteToHandler("GET", github.BaseURLPath+u, func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusInternalServerError) - fmt.Fprint(w, `{}`) - }) - s, m, err := rightClient.FetchRemoteContent(wsFiles) - Expect(s).To(Equal(wantS)) - Expect(m).To(Equal(wantM)) - Expect(err.Error()).To(ContainSubstring("500")) - }) - It("got GetContents error is nil and status is 200", func() { - wantS := []string{"file1", "file2"} - s.Reset() - s.RouteToHandler("GET", github.BaseURLPath+u, func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, `[ - { - "type": "dir", - "name": "file1", - "path": "lib" - }, - { - "type": "file", - "size": 20678, - "name": "file2", - "path": "LICENSE" - } - ]`) - }) - s, m, err := rightClient.FetchRemoteContent(wsFiles) - Expect(s).To(Equal(wantS)) - Expect(m).To(Equal(wantM)) - Expect(err).To(Succeed()) - }) - - }) - - Describe("fetching VerifyWorkflows", func() { - It("not found and return errMap", func() { - s.Reset() - s.SetAllowUnhandledRequests(true) - s.SetUnhandledRequestStatusCode(http.StatusNotFound) - res, err := wrongClient.VerifyWorkflows(workflows) - Expect(s.ReceivedRequests()).Should(HaveLen(1)) - Expect(err).To(Succeed()) - Expect(res).To(Equal(allFileNotFoundMap)) - }) - It("error is not equal nil and status is not 404", func() { - s.Reset() - s.SetAllowUnhandledRequests(true) - s.SetUnhandledRequestStatusCode(http.StatusNotImplemented) - res, err := wrongClient.VerifyWorkflows(workflows) - Expect(s.ReceivedRequests()).Should(HaveLen(1)) - Expect(err.Error()).To(ContainSubstring(strconv.Itoa(http.StatusNotImplemented))) - var m map[string]error - Expect(res).To(Equal(m)) - }) - It("status is 200 and some files lost", func() { - s.Reset() - s.RouteToHandler("GET", github.BaseURLPath+u, func(w http.ResponseWriter, req *http.Request) { - fmt.Fprint(w, partialFilesInRemoteDir) - }) - res, err := rightClient.VerifyWorkflows(workflows) - Expect(s.ReceivedRequests()).Should(HaveLen(1)) - Expect(err).To(Succeed()) - Expect(res).To(Equal(partialFileNotFoundMap)) - }) - It("status is 200 and no files lost", func() { - s.Reset() - s.RouteToHandler("GET", github.BaseURLPath+u, func(w http.ResponseWriter, req *http.Request) { - fmt.Fprint(w, fullFilesInRemoteDir) - }) - res, err := rightClient.VerifyWorkflows(workflows) - Expect(s.ReceivedRequests()).Should(HaveLen(1)) - Expect(err).To(Succeed()) - Expect(res).To(Equal(allFileFoundMap)) - }) - }) - - Describe("fetching GetWorkflowPath", func() { - It("not found", func() { - s.Reset() - s.SetAllowUnhandledRequests(true) - s.SetUnhandledRequestStatusCode(http.StatusNotFound) - res, err := wrongClient.GetWorkflowPath() - Expect(s.ReceivedRequests()).Should(HaveLen(1)) - Expect(err).To(Succeed()) - Expect(res).To(Equal("")) - }) - It("status is both not equal 200 and 404", func() { - s.Reset() - s.SetAllowUnhandledRequests(true) - s.SetUnhandledRequestStatusCode(http.StatusNotImplemented) - res, err := wrongClient.GetWorkflowPath() - Expect(s.ReceivedRequests()).Should(HaveLen(1)) - Expect(err).NotTo(Succeed()) - Expect(res).To(Equal("")) - }) - It("200", func() { - s.Reset() - s.RouteToHandler("GET", github.BaseURLPath+u, func(w http.ResponseWriter, req *http.Request) { - fmt.Fprint(w, "{}") - }) - - res, err := rightClient.GetWorkflowPath() - Expect(s.ReceivedRequests()).Should(HaveLen(1)) - Expect(err).To(Succeed()) - Expect(res).To(Equal(github.BaseURLPath + u)) - }) - }) - -}) diff --git a/pkg/util/scm/gitlab/commit.go b/pkg/util/scm/gitlab/commit.go deleted file mode 100644 index 64f1c2651..000000000 --- a/pkg/util/scm/gitlab/commit.go +++ /dev/null @@ -1,72 +0,0 @@ -package gitlab - -import ( - "github.com/imdario/mergo" - "github.com/xanzy/go-gitlab" - - "github.com/devstream-io/devstream/pkg/util/log" - "github.com/devstream-io/devstream/pkg/util/scm/git" -) - -type commitTree struct { - commitMessage string - commitBranch string - gitlabFileMap map[gitlab.FileActionValue]git.GitFileContentMap -} - -func newCommitTree(commitMessage string, branch string) *commitTree { - return &commitTree{ - commitMessage: commitMessage, - commitBranch: branch, - gitlabFileMap: make(map[gitlab.FileActionValue]git.GitFileContentMap), - } -} - -func (t *commitTree) addCommitFile(action gitlab.FileActionValue, scmPath string, content []byte) { - actionMap, ok := t.gitlabFileMap[action] - if !ok { - t.gitlabFileMap[action] = git.GitFileContentMap{ - scmPath: content, - } - } else { - actionMap[scmPath] = content - } -} - -func (t *commitTree) addCommitFilesFromMap(action gitlab.FileActionValue, gitMap git.GitFileContentMap) { - actionMap, ok := t.gitlabFileMap[action] - if !ok { - t.gitlabFileMap[action] = gitMap - } else { - err := mergo.Merge(&actionMap, gitMap, mergo.WithOverride) - if err != nil { - log.Debugf("gitlab add commit files failed: %+v", err) - } - } -} - -func (t *commitTree) getFilesCount() int { - var fileCount int - for _, files := range t.gitlabFileMap { - fileCount += len(files) - } - return fileCount -} - -func (t *commitTree) createCommitInfo() *gitlab.CreateCommitOptions { - var commitActionsOptions = make([]*gitlab.CommitActionOptions, 0, t.getFilesCount()) - for action, fileMap := range t.gitlabFileMap { - for fileName, content := range fileMap { - commitActionsOptions = append(commitActionsOptions, &gitlab.CommitActionOptions{ - Action: gitlab.FileAction(action), - FilePath: gitlab.String(fileName), - Content: gitlab.String(string(content)), - }) - } - } - return &gitlab.CreateCommitOptions{ - Branch: gitlab.String(t.commitBranch), - CommitMessage: gitlab.String(t.commitMessage), - Actions: commitActionsOptions, - } -} diff --git a/pkg/util/scm/gitlab/commit_test.go b/pkg/util/scm/gitlab/commit_test.go deleted file mode 100644 index 105c95e38..000000000 --- a/pkg/util/scm/gitlab/commit_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package gitlab_test - -import ( - "fmt" - "net/http" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "github.com/onsi/gomega/ghttp" - - gitlabCommon "github.com/xanzy/go-gitlab" - - "github.com/devstream-io/devstream/pkg/util/scm/git" - "github.com/devstream-io/devstream/pkg/util/scm/gitlab" -) - -var _ = Describe("commit method", func() { - var ( - repoInfo *git.RepoInfo - commitInfo *git.CommitInfo - gitlabClient *gitlab.Client - server *ghttp.Server - repoName, reqPath, branch, owner, commitMsg, gitFile, gitFileContent string - ) - BeforeEach(func() { - server = ghttp.NewServer() - server.SetAllowUnhandledRequests(true) - owner = "test_user" - repoName = "test_repo" - branch = "test_branch" - gitFile = "test_git.file" - gitFileContent = "test_git_content" - commitMsg = "test msg" - reqPath = fmt.Sprintf("%sprojects/%s/%s/repository/commits", apiRootPath, owner, repoName) - repoInfo = &git.RepoInfo{ - BaseURL: server.URL(), - Branch: branch, - Repo: repoName, - Owner: owner, - } - commitInfo = &git.CommitInfo{ - CommitMsg: commitMsg, - CommitBranch: branch, - GitFileMap: git.GitFileContentMap{ - gitFile: []byte(gitFileContent), - }, - } - client, err := gitlabCommon.NewClient( - "test", gitlabCommon.WithBaseURL(server.URL())) - Expect(err).Error().ShouldNot(HaveOccurred()) - gitlabClient = &gitlab.Client{ - Client: client, - RepoInfo: repoInfo, - } - }) - // Context("CreateCommitInfo method", func() { - // It("should return gitlab commit options", func() { - // commitInfoData := gitlabClient.CreateCommitInfo(gitlabCommon.FileCreate, commitInfo) - // Expect(*commitInfoData.Branch).Should(Equal(branch)) - // Expect(*commitInfoData.CommitMessage).Should(Equal(commitMsg)) - // actions := commitInfoData.Actions - // Expect(len(actions)).Should(Equal(1)) - // action := actions[0] - // Expect(*action.Action).Should(Equal(gitlabCommon.FileCreate)) - // Expect(*action.FilePath).Should(Equal(gitFile)) - // Expect(*action.Content).Should(Equal(gitFileContent)) - // }) - // }) - - Context("CommitActions method", func() { - BeforeEach(func() { - server.RouteToHandler("POST", reqPath, ghttp.CombineHandlers( - ghttp.VerifyRequest("POST", reqPath), - ghttp.RespondWithJSONEncoded(http.StatusOK, nil), - )) - }) - It("should work normal", func() { - needRollBack, err := gitlabClient.PushFiles(commitInfo, false) - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(needRollBack).Should(BeFalse()) - err = gitlabClient.DeleteFiles(commitInfo) - Expect(err).Error().ShouldNot(HaveOccurred()) - }) - }) - AfterEach(func() { - server.Close() - }) -}) diff --git a/pkg/util/scm/gitlab/errors.go b/pkg/util/scm/gitlab/errors.go deleted file mode 100644 index f3345a93d..000000000 --- a/pkg/util/scm/gitlab/errors.go +++ /dev/null @@ -1,32 +0,0 @@ -package gitlab - -import ( - "errors" - "strings" - - "github.com/devstream-io/devstream/pkg/util/pkgerror" -) - -var ( - errRepoNotFound pkgerror.ErrorMessage = "Project Not Found" - errRepoExist pkgerror.ErrorMessage = "{name: [has already been taken]}" - errWebHookInvalid pkgerror.ErrorMessage = "invlid url given" - errFileExist pkgerror.ErrorMessage = "A file with this name already exists" - errVariableExist pkgerror.ErrorMessage = "has already been taken" - errFileNotExist pkgerror.ErrorMessage = "file with this name doesn't exist" -) - -var errorMsgMap = map[pkgerror.ErrorMessage]string{ - errWebHookInvalid: "webhook config doesn't support local networks, and you should config gitlab or change jenkinsURL config. For more info, you can refer to https://docs.gitlab.com/ee/security/webhooks.html#allow-webhook-and-service-requests-to-local-network", - errFileExist: "file already exist", -} - -func (c *Client) newModuleError(err error) error { - var newError = err - for k, v := range errorMsgMap { - if strings.Contains(err.Error(), string(k)) { - newError = errors.New(v) - } - } - return pkgerror.NewErrorFromPlugin("gitlab", c.GetRepoPath(), newError) -} diff --git a/pkg/util/scm/gitlab/file.go b/pkg/util/scm/gitlab/file.go deleted file mode 100644 index 27236ddeb..000000000 --- a/pkg/util/scm/gitlab/file.go +++ /dev/null @@ -1,89 +0,0 @@ -package gitlab - -import ( - "net/http" - - "github.com/xanzy/go-gitlab" - - mapset "github.com/deckarep/golang-set/v2" - - "github.com/devstream-io/devstream/pkg/util/pkgerror" - "github.com/devstream-io/devstream/pkg/util/scm/git" -) - -func (c *Client) PushFiles(commitInfo *git.CommitInfo, checkUpdate bool) (bool, error) { - // if checkUpdate is true, check files to update first - tree := newCommitTree(commitInfo.CommitMsg, c.Branch) - if checkUpdate { - for scmPath, content := range commitInfo.GitFileMap { - fileExist, err := c.checkFileExist(scmPath) - if err != nil { - return false, err - } - if fileExist { - tree.addCommitFile(gitlab.FileUpdate, scmPath, content) - } else { - tree.addCommitFile(gitlab.FileCreate, scmPath, content) - } - } - } else { - tree.addCommitFilesFromMap(gitlab.FileCreate, commitInfo.GitFileMap) - } - _, _, err := c.Commits.CreateCommit(c.GetRepoPath(), tree.createCommitInfo()) - if err != nil && !pkgerror.CheckErrorMatchByMessage(err, errFileExist) { - return true, c.newModuleError(err) - } - return false, nil -} - -func (c *Client) DeleteFiles(commitInfo *git.CommitInfo) error { - tree := newCommitTree(commitInfo.CommitMsg, c.Branch) - tree.addCommitFilesFromMap(gitlab.FileDelete, commitInfo.GitFileMap) - _, _, err := c.Commits.CreateCommit(c.GetRepoPath(), tree.createCommitInfo()) - if err != nil && !pkgerror.CheckErrorMatchByMessage(err, errRepoNotFound, errFileNotExist) { - return c.newModuleError(err) - } - return nil -} - -func (c *Client) checkFileExist(filename string) (bool, error) { - getFileOptions := &gitlab.GetFileOptions{ - Ref: gitlab.String(c.Branch), - } - - _, response, err := c.RepositoryFiles.GetFile(c.GetRepoPath(), filename, getFileOptions) - for _, v := range []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusNotFound} { - if response.StatusCode == v { - return false, nil - } - } - - if err != nil { - return false, err - } - - return true, nil -} - -func (c *Client) GetPathInfo(path string) ([]*git.RepoFileStatus, error) { - var errCodeSet = mapset.NewSet(http.StatusBadRequest, http.StatusUnauthorized, http.StatusNotFound) - gitRepoFileStatus := make([]*git.RepoFileStatus, 0) - getFileOptions := &gitlab.GetFileOptions{ - Ref: gitlab.String(c.Branch), - } - - file, response, err := c.RepositoryFiles.GetFile(c.GetRepoPath(), path, getFileOptions) - if response != nil && errCodeSet.Contains(response.StatusCode) { - return gitRepoFileStatus, nil - } - - if err != nil { - return gitRepoFileStatus, err - } - gitRepoFileStatus = append(gitRepoFileStatus, &git.RepoFileStatus{ - Path: file.FilePath, - Branch: file.Ref, - SHA: file.SHA256, - }) - return gitRepoFileStatus, nil -} diff --git a/pkg/util/scm/gitlab/file_test.go b/pkg/util/scm/gitlab/file_test.go deleted file mode 100644 index e1677f5a0..000000000 --- a/pkg/util/scm/gitlab/file_test.go +++ /dev/null @@ -1,93 +0,0 @@ -package gitlab_test - -import ( - "fmt" - "net/http" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "github.com/onsi/gomega/ghttp" - gitlabCommon "github.com/xanzy/go-gitlab" - - "github.com/devstream-io/devstream/pkg/util/scm/git" - "github.com/devstream-io/devstream/pkg/util/scm/gitlab" -) - -var _ = Describe("CreateCommitInfo method", func() { - var ( - repoInfo *git.RepoInfo - gitlabClient *gitlab.Client - testFile, repoName, reqPath, owner string - server *ghttp.Server - ) - BeforeEach(func() { - server = ghttp.NewServer() - testFile = "test_file" - owner = "test_user" - repoName = "test_repo" - repoInfo = &git.RepoInfo{ - BaseURL: server.URL(), - Branch: "test", - Repo: repoName, - Owner: owner, - } - client, err := gitlabCommon.NewClient( - "test", gitlabCommon.WithBaseURL(server.URL()), - // don't retry http request when test - gitlabCommon.WithCustomRetryMax(0), - ) - Expect(err).Error().ShouldNot(HaveOccurred()) - gitlabClient = &gitlab.Client{ - Client: client, - RepoInfo: repoInfo, - } - server.SetAllowUnhandledRequests(true) - }) - Context("GetPathInfo method", func() { - When("gitlab return normal", func() { - BeforeEach(func() { - reqPath = fmt.Sprintf("%sprojects/%s/%s/repository/files/%s", apiRootPath, owner, repoName, testFile) - server.RouteToHandler("GET", reqPath, ghttp.CombineHandlers( - ghttp.VerifyRequest("GET", reqPath), - ghttp.RespondWithJSONEncoded(http.StatusOK, nil), - )) - }) - It("should work", func() { - fileInfo, err := gitlabClient.GetPathInfo(testFile) - Expect(err).Error().ShouldNot(HaveOccurred()) - Expect(fileInfo).ShouldNot(BeNil()) - Expect(fileInfo[0].Branch).Should(BeEmpty()) - }) - }) - When("gitlab return error", func() { - BeforeEach(func() { - reqPath = fmt.Sprintf("%sprojects/%s/%s/repository/files/%s", apiRootPath, owner, repoName, testFile) - server.RouteToHandler("GET", reqPath, ghttp.CombineHandlers( - ghttp.VerifyRequest("GET", reqPath), - ghttp.RespondWithJSONEncoded(http.StatusBadGateway, nil), - )) - }) - It("should return err", func() { - _, err := gitlabClient.GetPathInfo(testFile) - Expect(err).Error().Should(HaveOccurred()) - }) - }) - }) - Context("DeleteFiles method", func() { - BeforeEach(func() { - reqPath = fmt.Sprintf("%sprojects/%s/%s/repository/files/%s", apiRootPath, owner, repoName, testFile) - server.RouteToHandler("POST", reqPath, ghttp.CombineHandlers( - ghttp.VerifyRequest("POST", reqPath), - ghttp.RespondWithJSONEncoded(http.StatusOK, nil), - )) - }) - It("should work normal", func() { - _, err := gitlabClient.GetPathInfo(testFile) - Expect(err).Error().Should(HaveOccurred()) - }) - - }) - AfterEach(func() { - server.Close() - }) -}) diff --git a/pkg/util/scm/gitlab/gitlab.go b/pkg/util/scm/gitlab/gitlab.go deleted file mode 100644 index 6b1a18b00..000000000 --- a/pkg/util/scm/gitlab/gitlab.go +++ /dev/null @@ -1,42 +0,0 @@ -package gitlab - -import ( - "errors" - - "github.com/xanzy/go-gitlab" - - "github.com/devstream-io/devstream/pkg/util/scm/git" -) - -const ( - DefaultGitlabHost = "https://gitlab.com" -) - -type Client struct { - *gitlab.Client - *git.RepoInfo -} - -func NewClient(options *git.RepoInfo) (*Client, error) { - if options.Token == "" { - return nil, errors.New("config field scm.token is not setted") - } - - c := &Client{} - - var err error - - if options.BaseURL == "" { - c.Client, err = gitlab.NewClient(options.Token) - } else { - c.Client, err = gitlab.NewClient(options.Token, gitlab.WithBaseURL(options.BaseURL)) - } - c.RepoInfo = options - - if err != nil { - return nil, err - } - - return c, nil - -} diff --git a/pkg/util/scm/gitlab/gitlab_suite_test.go b/pkg/util/scm/gitlab/gitlab_suite_test.go deleted file mode 100644 index b3354a745..000000000 --- a/pkg/util/scm/gitlab/gitlab_suite_test.go +++ /dev/null @@ -1,21 +0,0 @@ -package gitlab_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -var ( - apiRootPath string -) - -func TestPlanmanager(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "GitLab Suite") -} - -var _ = BeforeSuite(func() { - apiRootPath = "/api/v4/" -}) diff --git a/pkg/util/scm/gitlab/gitlab_test.go b/pkg/util/scm/gitlab/gitlab_test.go deleted file mode 100644 index a6609f9be..000000000 --- a/pkg/util/scm/gitlab/gitlab_test.go +++ /dev/null @@ -1,60 +0,0 @@ -package gitlab_test - -import ( - "fmt" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/pkg/util/scm/git" - "github.com/devstream-io/devstream/pkg/util/scm/gitlab" -) - -var _ = Describe("NewClient func", func() { - var ( - repoInfo *git.RepoInfo - ) - When("gitlab Token is not set", func() { - BeforeEach(func() { - repoInfo = &git.RepoInfo{ - BaseURL: "test", - Repo: "no_token", - } - }) - It("should return error", func() { - _, err := gitlab.NewClient(repoInfo) - Expect(err).Error().Should(HaveOccurred()) - Expect(err.Error()).Should(Equal("config field scm.token is not setted")) - }) - }) - When("gitlab token is set", func() { - When("repoInfo field baseURL is empty", func() { - BeforeEach(func() { - repoInfo = &git.RepoInfo{ - BaseURL: "", - Token: "exist_token", - } - }) - It("should return client with gitlab url", func() { - client, err := gitlab.NewClient(repoInfo) - Expect(err).ShouldNot(HaveOccurred()) - Expect(client.Client.BaseURL().Host).Should(Equal("gitlab.com")) - }) - }) - When("repoInfo field baseURL is set", func() { - var baseURL string - BeforeEach(func() { - baseURL = "test.com" - repoInfo = &git.RepoInfo{ - BaseURL: fmt.Sprintf("http://%s", baseURL), - Token: "exist", - } - }) - It("should return self host url", func() { - client, err := gitlab.NewClient(repoInfo) - Expect(err).ShouldNot(HaveOccurred()) - Expect(client.Client.BaseURL().Host).Should(Equal(baseURL)) - }) - }) - }) -}) diff --git a/pkg/util/scm/gitlab/repo.go b/pkg/util/scm/gitlab/repo.go deleted file mode 100644 index bfc7fcdee..000000000 --- a/pkg/util/scm/gitlab/repo.go +++ /dev/null @@ -1,206 +0,0 @@ -package gitlab - -import ( - "fmt" - - "github.com/xanzy/go-gitlab" - - "github.com/devstream-io/devstream/pkg/util/log" - "github.com/devstream-io/devstream/pkg/util/pkgerror" - "github.com/devstream-io/devstream/pkg/util/scm/git" -) - -func (c *Client) InitRepo() error { - log.Debugf("Repo to be created: %s", c.Repo) - - var err error - var res *gitlab.Group - var groupId int - - gitlabGetGroupOptions := &gitlab.GetGroupOptions{} - - if c.Namespace != "" { - res, _, err = c.Groups.GetGroup(c.Namespace, gitlabGetGroupOptions) - if err != nil { - return err - } - groupId = res.ID - } - - log.Debugf("Group: %#v\n", res) - - p := &gitlab.CreateProjectOptions{ - Name: gitlab.String(c.Repo), - Description: gitlab.String("Bootstrapped by DevStream."), - MergeRequestsEnabled: gitlab.Bool(true), - SnippetsEnabled: gitlab.Bool(true), - DefaultBranch: gitlab.String(c.Branch), - AutoDevopsEnabled: gitlab.Bool(false), - } - - switch c.Visibility { - case "public": - p.Visibility = gitlab.Visibility(gitlab.PublicVisibility) - case "internal": - p.Visibility = gitlab.Visibility(gitlab.InternalVisibility) - case "private": - p.Visibility = gitlab.Visibility(gitlab.PrivateVisibility) - default: - p.Visibility = gitlab.Visibility(gitlab.PublicVisibility) - } - - if groupId != 0 { - p.NamespaceID = gitlab.Int(groupId) - } - _, _, err = c.Projects.CreateProject(p) - if err != nil && !pkgerror.CheckErrorMatchByMessage(err, errRepoNotFound, errRepoExist) { - return err - } - return nil -} - -func (c *Client) DeleteRepo() error { - _, err := c.Projects.DeleteProject(c.GetRepoPath()) - if err != nil && !pkgerror.CheckErrorMatchByMessage(err, errRepoNotFound) { - return err - } - return nil -} - -func (c *Client) DescribeRepo() (*git.RepoInfo, error) { - p := &gitlab.GetProjectOptions{} - project, _, err := c.Projects.GetProject(c.GetRepoPath(), p) - - if err != nil { - log.Debugf("gitlab project: [%s] error %+v", c.GetRepoPath(), err) - return nil, c.newModuleError(err) - } - - log.Debugf("GitLab Project is: %#v\n", project) - repoInfo := &git.RepoInfo{ - Repo: project.Name, - CloneURL: git.ScmURL(project.HTTPURLToRepo), - } - if project.Owner != nil { - log.Debugf("GitLab project owner is: %#v.\n", project.Owner) - repoInfo.Owner = project.Owner.Username - repoInfo.Org = project.Owner.Organization - } - return repoInfo, nil -} - -// AddWebhook will update webhook when it exists -// else create a webbhook -func (c *Client) AddWebhook(webhookConfig *git.WebhookConfig) error { - projectHook, err := c.getWebhook(webhookConfig) - if err != nil { - return err - } - if projectHook != nil { - log.Debugf("gitlab AddWebhook already exist, update this webhook") - p := &gitlab.EditProjectHookOptions{ - PushEvents: gitlab.Bool(true), - Token: gitlab.String(webhookConfig.SecretToken), - URL: gitlab.String(webhookConfig.Address), - MergeRequestsEvents: gitlab.Bool(true), - } - _, _, err = c.Projects.EditProjectHook(c.GetRepoPath(), projectHook.ID, p) - } else { - p := &gitlab.AddProjectHookOptions{ - PushEvents: gitlab.Bool(true), - Token: gitlab.String(webhookConfig.SecretToken), - URL: gitlab.String(webhookConfig.Address), - MergeRequestsEvents: gitlab.Bool(true), - } - _, _, err = c.Projects.AddProjectHook(c.GetRepoPath(), p) - } - if err != nil { - return c.newModuleError(err) - } - return nil -} - -func (c *Client) DeleteWebhook(webhookConfig *git.WebhookConfig) error { - projectHook, err := c.getWebhook(webhookConfig) - if err != nil && !pkgerror.CheckErrorMatchByMessage(err, errRepoNotFound) { - return err - } - if projectHook == nil { - log.Debugf("gitlab DeleteWebhook not found") - return nil - } - _, err = c.Projects.DeleteProjectHook(c.GetRepoPath(), projectHook.ID) - if err != nil { - return c.newModuleError(err) - } - return nil -} - -func (c *Client) getWebhook(webhookConfig *git.WebhookConfig) (*gitlab.ProjectHook, error) { - p := &gitlab.ListProjectHooksOptions{} - hooks, _, err := c.Projects.ListProjectHooks(c.GetRepoPath(), p) - if err != nil { - log.Debugf("gitlab get webhook list hooks failed: %s", err) - return nil, c.newModuleError(err) - } - for _, hook := range hooks { - if hook.URL == webhookConfig.Address { - return hook, nil - } - } - return nil, nil -} - -// TODO(steinliber): support gtlab later -func (c *Client) DownloadRepo() (string, error) { - return "", fmt.Errorf("gitlab doesn't support download repo for now") -} - -func (c *Client) AddRepoSecret(secretKey, secretValue string) error { - var err error - createOpts := gitlab.CreateProjectVariableOptions{ - Key: gitlab.String(secretKey), - Value: gitlab.String(secretValue), - Masked: gitlab.Bool(true), - } - _, _, err = c.ProjectVariables.CreateVariable(c.GetRepoPath(), &createOpts) - // if secret already exist, just update this secret - if err != nil && pkgerror.CheckErrorMatchByMessage(err, errVariableExist) { - updateOpts := gitlab.UpdateProjectVariableOptions{ - Value: gitlab.String(secretValue), - Masked: gitlab.Bool(true), - } - _, _, err = c.ProjectVariables.UpdateVariable(c.GetRepoPath(), secretKey, &updateOpts) - } - return err -} - -func (c *Client) ListRepoRunner() ([]*gitlab.Runner, error) { - listOpts := &gitlab.ListProjectRunnersOptions{ - Status: gitlab.String("online"), - TagList: &[]string{"ci"}, - } - runners, _, err := c.Runners.ListProjectRunners(c.GetRepoPath(), listOpts) - return runners, err - -} - -func (c *Client) ResetRepoRunnerToken() (string, error) { - token, _, err := c.Runners.ResetProjectRunnerRegistrationToken(c.GetRepoPath()) - if err != nil { - return "", err - } - return *token.Token, nil -} - -func (c *Client) DisableRepoSharedRunner() error { - editOption := gitlab.EditProjectOptions{ - Name: gitlab.String(c.Repo), - SharedRunnersEnabled: gitlab.Bool(false), - } - _, _, err := c.Projects.EditProject(c.GetRepoPath(), &editOption) - if err != nil && !pkgerror.CheckErrorMatchByMessage(err, errRepoNotFound, errRepoExist) { - return err - } - return nil -} diff --git a/pkg/util/scm/gitlab/repo_test.go b/pkg/util/scm/gitlab/repo_test.go deleted file mode 100644 index ca19bab3c..000000000 --- a/pkg/util/scm/gitlab/repo_test.go +++ /dev/null @@ -1,135 +0,0 @@ -package gitlab_test - -import ( - "fmt" - "net/http" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "github.com/onsi/gomega/ghttp" - gitlabCommon "github.com/xanzy/go-gitlab" - - "github.com/devstream-io/devstream/pkg/util/log" - "github.com/devstream-io/devstream/pkg/util/scm/git" - "github.com/devstream-io/devstream/pkg/util/scm/gitlab" -) - -var _ = Describe("repo methods", func() { - var ( - repoInfo *git.RepoInfo - gitlabClient *gitlab.Client - server *ghttp.Server - repoName, branch, reqPath, owner, visibility string - expectReqBody []byte - ) - BeforeEach(func() { - server = ghttp.NewServer() - server.SetAllowUnhandledRequests(true) - owner = "test_user" - repoName = "test_repo" - branch = "test_branch" - visibility = "internal" - repoInfo = &git.RepoInfo{ - BaseURL: server.URL(), - Branch: branch, - Repo: repoName, - Owner: owner, - Visibility: visibility, - } - client, err := gitlabCommon.NewClient( - "test", gitlabCommon.WithBaseURL(server.URL()), - gitlabCommon.WithCustomRetryMax(0), - ) - Expect(err).Error().ShouldNot(HaveOccurred()) - gitlabClient = &gitlab.Client{ - Client: client, - RepoInfo: repoInfo, - } - }) - Context("InitRepo method", func() { - BeforeEach(func() { - reqPath = fmt.Sprintf("%sprojects", apiRootPath) - expectReqBody = []byte(fmt.Sprintf(`{"auto_devops_enabled":false,"default_branch":"%s","description":"Bootstrapped by DevStream.","name":"%s","visibility":"%s","merge_requests_enabled":true,"snippets_enabled":true}`, branch, repoName, visibility)) - server.RouteToHandler("POST", reqPath, ghttp.CombineHandlers( - ghttp.VerifyRequest("POST", reqPath), - ghttp.VerifyBody(expectReqBody), - ghttp.RespondWithJSONEncoded(http.StatusOK, nil), - )) - }) - It("should create repo", func() { - err := gitlabClient.InitRepo() - Expect(err).ShouldNot(HaveOccurred()) - }) - }) - Context("DeleteRepo method", func() { - BeforeEach(func() { - reqPath = fmt.Sprintf("%sprojects/%s/%s", apiRootPath, owner, repoName) - server.RouteToHandler("DELETE", reqPath, ghttp.CombineHandlers( - ghttp.VerifyRequest("DELETE", reqPath), - ghttp.RespondWithJSONEncoded(http.StatusOK, nil), - )) - }) - It("should delete repo", func() { - err := gitlabClient.DeleteRepo() - Expect(err).ShouldNot(HaveOccurred()) - }) - }) - Context("DescribeRepo method", func() { - BeforeEach(func() { - reqPath = fmt.Sprintf("%sprojects/%s/%s", apiRootPath, owner, repoName) - server.RouteToHandler("GET", reqPath, ghttp.CombineHandlers( - ghttp.VerifyRequest("GET", reqPath), - ghttp.RespondWithJSONEncoded(http.StatusOK, nil), - )) - }) - It("should return repo info", func() { - _, err := gitlabClient.DescribeRepo() - Expect(err).ShouldNot(HaveOccurred()) - }) - }) - - Context("AddWebhook method", func() { - var ( - webHook *git.WebhookConfig - webhookURL string - ) - BeforeEach(func() { - webhookURL = "test.com" - webHook = &git.WebhookConfig{ - Address: webhookURL, - } - }) - When("get webhook return error", func() { - BeforeEach(func() { - reqPath = fmt.Sprintf("%sprojects/%s/%s/hooks", apiRootPath, owner, repoName) - server.RouteToHandler("GET", reqPath, ghttp.CombineHandlers( - ghttp.VerifyRequest("GET", reqPath), - ghttp.RespondWithJSONEncoded(http.StatusBadGateway, nil), - )) - }) - It("should return error", func() { - err := gitlabClient.AddWebhook(webHook) - Expect(err).Should(HaveOccurred()) - }) - }) - When("add webhook return invalid url", func() { - BeforeEach(func() { - reqPath = fmt.Sprintf("%sprojects/%s/%s/hooks", apiRootPath, owner, repoName) - webhookRawBody := fmt.Sprintf(`[{"id": "test", "url": %s}]`, webhookURL) - server.RouteToHandler("GET", reqPath, ghttp.CombineHandlers( - ghttp.VerifyRequest("GET", reqPath), - ghttp.RespondWithJSONEncoded(http.StatusOK, webhookRawBody), - )) - }) - It("should return error", func() { - log.Warnf("Start=> %+v", webHook) - err := gitlabClient.AddWebhook(webHook) - Expect(err).Should(HaveOccurred()) - }) - }) - }) - - AfterEach(func() { - server.Close() - }) -}) diff --git a/pkg/util/scm/scm_suite_test.go b/pkg/util/scm/scm_suite_test.go deleted file mode 100644 index 690788779..000000000 --- a/pkg/util/scm/scm_suite_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package scm_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestPlanmanager(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Util scm Suite") -} diff --git a/pkg/util/scm/state.go b/pkg/util/scm/state.go deleted file mode 100644 index 677d3b13c..000000000 --- a/pkg/util/scm/state.go +++ /dev/null @@ -1,32 +0,0 @@ -package scm - -import ( - "github.com/devstream-io/devstream/pkg/util/log" - "github.com/devstream-io/devstream/pkg/util/scm/git" -) - -func GetGitFileStats(client ClientOperation, gitFiles git.GitFileContentMap) (map[string]any, error) { - status := make(map[string]any) - for scmPath, content := range gitFiles { - // get localSHA - localFileSHA := calculateSHA(content) - // get scmInfo - gitFileInfos, err := client.GetPathInfo(scmPath) - if err != nil { - log.Debugf("ci status get location info failed: %+v", err) - return nil, err - } - scmFileStatus := []map[string]string{} - for _, fileStatus := range gitFileInfos { - scmFileStatus = append(scmFileStatus, map[string]string{ - "scmSHA": fileStatus.SHA, - "scmBranch": fileStatus.Branch, - }) - } - status[scmPath] = map[string]any{ - "localSHA": localFileSHA, - "scm": scmFileStatus, - } - } - return status, nil -} diff --git a/pkg/util/scm/state_test.go b/pkg/util/scm/state_test.go deleted file mode 100644 index 5ec887c00..000000000 --- a/pkg/util/scm/state_test.go +++ /dev/null @@ -1,68 +0,0 @@ -package scm_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "errors" - - "github.com/devstream-io/devstream/pkg/util/scm" - "github.com/devstream-io/devstream/pkg/util/scm/git" -) - -var _ = Describe("GetGitFileStats func", func() { - var ( - mockScmClient *scm.MockScmClient - gitFileMap git.GitFileContentMap - ) - - BeforeEach(func() { - mockScmClient = &scm.MockScmClient{} - gitFileMap = git.GitFileContentMap{ - "testFile": []byte("test_Content"), - } - }) - When("get scm pathinfo error", func() { - BeforeEach(func() { - mockScmClient.GetPathInfoError = errors.New("test") - }) - It("should return empty map", func() { - _, err := scm.GetGitFileStats(mockScmClient, gitFileMap) - Expect(err).Should(HaveOccurred()) - }) - }) - When("get scm pathinfo return", func() { - var ( - path, sha, branch string - ) - BeforeEach(func() { - path = "test_path" - sha = "test_sha" - branch = "test_branch" - gitFileStatus := []*git.RepoFileStatus{ - { - Path: path, - SHA: sha, - Branch: branch, - }, - } - mockScmClient.GetPathInfoReturnValue = gitFileStatus - }) - It("should return fileMap", func() { - status, err := scm.GetGitFileStats(mockScmClient, gitFileMap) - Expect(err).ShouldNot(HaveOccurred()) - Expect(len(status)).Should(Equal(1)) - Expect(status).Should(Equal(map[string]any{ - "testFile": map[string]any{ - "localSHA": "bbed2c0c2935a0e860cd4f6212aba4d6", - "scm": []map[string]string{ - { - "scmSHA": "test_sha", - "scmBranch": "test_branch", - }, - }, - }, - })) - }) - }) -}) diff --git a/pkg/util/scm/util.go b/pkg/util/scm/util.go deleted file mode 100644 index 30b895eaf..000000000 --- a/pkg/util/scm/util.go +++ /dev/null @@ -1,13 +0,0 @@ -package scm - -import ( - "crypto/md5" - "encoding/hex" -) - -// calculateSHA is used to calculate file content's md5 -func calculateSHA(fileContent []byte) string { - h := md5.New() - h.Write(fileContent) - return hex.EncodeToString(h.Sum(nil)) -} diff --git a/pkg/util/scm/util_test.go b/pkg/util/scm/util_test.go deleted file mode 100644 index a23c316e2..000000000 --- a/pkg/util/scm/util_test.go +++ /dev/null @@ -1,14 +0,0 @@ -package scm - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -var _ = Describe("calculateSHA func", func() { - var content string - It("should return as expect", func() { - content = "test Content" - Expect(calculateSHA([]byte(content))).Should(Equal("f73d59b513c429a33da4f7efe70c7af3")) - }) -}) diff --git a/pkg/util/template/client.go b/pkg/util/template/client.go deleted file mode 100644 index 9e8e24122..000000000 --- a/pkg/util/template/client.go +++ /dev/null @@ -1,102 +0,0 @@ -package template - -import ( - "bytes" - "fmt" - "os" - "text/template" - - "github.com/devstream-io/devstream/pkg/util/log" -) - -// RenderAPI is a interface with render methods -type RenderAPI interface { - Render(inputStr string, variables any) (string, error) -} - -type ( - renderClient struct { - Getter getFunc - Processors []processFunc - // render config - Option *TemplateOption - } - // TemplateOption is used to config template option - TemplateOption struct { - Name string - IgnoreMissKeyError bool - FuncMap template.FuncMap - } -) - -// NewRenderClient create template render client with template option, getter and processors -func NewRenderClient(clientOption *TemplateOption, getter getFunc, processors ...processFunc) RenderAPI { - return &renderClient{ - Option: clientOption, - Getter: getter, - Processors: processors, - } -} - -// Render gets the content, process the content, render and returns the result string -func (c *renderClient) Render(inputStr string, variables any) (string, error) { - // 1. get content - content, err := c.Getter(inputStr) - if err != nil { - return "", err - } - // 2. process content to update content data - for _, processFunc := range c.Processors { - content = processFunc(content) - } - // 3. render content - return c.renderContent(string(content), variables) -} - -// renderContent render data with variable -func (c *renderClient) renderContent(templateStr string, variable any) (string, error) { - t, err := c.newTemplateClient().Parse(templateStr) - if err != nil { - log.Warnf("Template parse file failed, template: %s, err: %s.", templateStr, err) - return "", fmt.Errorf("parse %w", err) - } - - var buff bytes.Buffer - if err = t.Execute(&buff, variable); err != nil { - log.Warnf("Template execution failed: %s.", err) - return "", fmt.Errorf("render %w", err) - } - return buff.String(), nil -} - -// newTemplateClient template client from renderClient.Option -func (c *renderClient) newTemplateClient() *template.Template { - // config options default value - if c.Option == nil { - c.Option = &TemplateOption{ - Name: "default_template", - } - } - t := template.New(c.Option.Name).Delims("[[", "]]") - if !c.Option.IgnoreMissKeyError { - t = t.Option("missingkey=error") - } - t.Funcs(defaultFuncMap) - if c.Option.FuncMap != nil { - t.Funcs(c.Option.FuncMap) - } - return t -} - -// defaultFuncMap is the default logic for templatge render func -var defaultFuncMap = map[string]any{ - "env": getEnvInTemplate, -} - -func getEnvInTemplate(envKey string) (string, error) { - envVal := os.Getenv(envKey) - if envVal == "" { - return "", fmt.Errorf("template can't get environment variable %s, maybe you should set this environment first", envKey) - } - return envVal, nil -} diff --git a/pkg/util/template/client_test.go b/pkg/util/template/client_test.go deleted file mode 100644 index 71b3fb80c..000000000 --- a/pkg/util/template/client_test.go +++ /dev/null @@ -1,154 +0,0 @@ -package template - -import ( - "fmt" - "os" - "strings" - "text/template" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -var _ = Describe("renderClient", func() { - var ( - tplName, tplWithFunc string - vars map[string]interface{} - client *renderClient - ) - When("all func is used", func() { - BeforeEach(func() { - tplName = "template" - tplWithFunc = `metadata: - name: "[[ .App.Name ]]" - namespace: "[[ .App.NameSpace ]]" - funcMap: - len: [[ "abc" | len ]] - equal: [[ eq "test" .App.Name ]] - upper: "[[ .App.Name | upper ]]"` - vars = map[string]interface{}{ - "App": map[string]interface{}{ - "Name": "test", - "NameSpace": "test_namespace", - }, - } - - }) - - It("should render template successfully", func() { - upper := func(s string) string { - return strings.ToUpper(s) - } - funcMap := template.FuncMap{"upper": upper} - - addNewLineProcessor := func(b []byte) []byte { - return append(b, '\n') - } - - content, err := NewRenderClient( - &TemplateOption{ - Name: tplName, - FuncMap: funcMap, - }, ContentGetter, addNewLineProcessor, addNewLineProcessor, - ).Render(tplWithFunc, vars) - - expected := `metadata: - name: "test" - namespace: "test_namespace" - funcMap: - len: 3 - equal: true - upper: "TEST" - -` - Expect(err).NotTo(HaveOccurred()) - Expect(content).To(Equal(expected)) - }) - }) - When("getter error", func() { - BeforeEach(func() { - getterErrorFunc := func(inputStr string) ([]byte, error) { - return nil, fmt.Errorf("test error") - } - client = &renderClient{ - Getter: getterErrorFunc, - } - }) - It("should return error", func() { - _, err := client.Render("test", nil) - Expect(err).Should(HaveOccurred()) - Expect(err.Error()).Should(Equal("test error")) - }) - }) - When("parse template failed", func() { - BeforeEach(func() { - client = &renderClient{ - Getter: ContentGetter, - } - }) - It("should return error", func() { - invalidTemplateData := `[[ .... not invalid ]] [[ notvalid data ]]` - _, err := client.Render(invalidTemplateData, nil) - Expect(err).Should(HaveOccurred()) - Expect(err.Error()).Should(ContainSubstring("parse template: default_template")) - }) - }) - - When("render template failed", func() { - BeforeEach(func() { - client = &renderClient{ - Getter: ContentGetter, - } - }) - It("should return error", func() { - invalidTemplateData := `[[ .variable ]] ` - _, err := client.Render(invalidTemplateData, map[string]interface{}{}) - Expect(err).Should(HaveOccurred()) - Expect(err.Error()).Should(ContainSubstring("render template: default_template")) - }) - }) -}) - -var _ = Describe("template default funcs", func() { - Context("getEnvInTemplate func", func() { - var ( - tokenKey string - existVal string - ) - BeforeEach(func() { - tokenKey = "TEMPLATE_ENV_TEST" - existVal = os.Getenv(tokenKey) - if existVal != "" { - err := os.Unsetenv(tokenKey) - Expect(err).ShouldNot(HaveOccurred()) - } - }) - When("env not exist", func() { - BeforeEach(func() { - err := os.Unsetenv(tokenKey) - Expect(err).ShouldNot(HaveOccurred()) - }) - It("should return err", func() { - _, err := getEnvInTemplate(tokenKey) - Expect(err).Should(HaveOccurred()) - Expect(err.Error()).Should(Equal("template can't get environment variable TEMPLATE_ENV_TEST, maybe you should set this environment first")) - }) - }) - When("env exist", func() { - BeforeEach(func() { - err := os.Setenv(tokenKey, "test") - Expect(err).ShouldNot(HaveOccurred()) - }) - It("should return err", func() { - data, err := getEnvInTemplate(tokenKey) - Expect(err).ShouldNot(HaveOccurred()) - Expect(data).Should(Equal("test")) - }) - }) - AfterEach(func() { - if existVal != "" { - os.Setenv(tokenKey, existVal) - } - }) - }) -}) diff --git a/pkg/util/template/getter.go b/pkg/util/template/getter.go deleted file mode 100644 index 23d756051..000000000 --- a/pkg/util/template/getter.go +++ /dev/null @@ -1,25 +0,0 @@ -package template - -import ( - "os" - - "github.com/devstream-io/devstream/pkg/util/downloader" -) - -// ContentGetter gets content from any source -type getFunc func(string) ([]byte, error) - -// LocalFileGetter get content bytes from file -func LocalFileGetter(filepath string) ([]byte, error) { - return os.ReadFile(filepath) -} - -// ContentGetter get content bytes from input content -func ContentGetter(content string) ([]byte, error) { - return []byte(content), nil -} - -// URLGetter get content bytes from remote url -func URLGetter(url string) ([]byte, error) { - return downloader.FetchContentFromURL(url) -} diff --git a/pkg/util/template/getter_test.go b/pkg/util/template/getter_test.go deleted file mode 100644 index ee11a8e4d..000000000 --- a/pkg/util/template/getter_test.go +++ /dev/null @@ -1,67 +0,0 @@ -package template_test - -import ( - "net/http" - "os" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "github.com/onsi/gomega/ghttp" - - "github.com/devstream-io/devstream/pkg/util/template" -) - -var _ = Describe("Getters(localFile, content, url)", func() { - - const src = "test_data" - - When("template content is local file", func() { - It("should return rendered template", func() { - file, err := os.CreateTemp("", "test") - Expect(err).To(Succeed()) - _, err = file.WriteString(src) - Expect(err).To(Succeed()) - - data, err := template.LocalFileGetter(file.Name()) - Expect(err).ShouldNot(HaveOccurred()) - Expect(string(data)).Should(Equal(src)) - }) - }) - - When("template content is content", func() { - It("should return rendered template", func() { - data, err := template.ContentGetter(src) - Expect(err).ShouldNot(HaveOccurred()) - Expect(string(data)).Should(Equal(src)) - }) - }) - - When("template content is url", func() { - var ( - server *ghttp.Server - testPath string - ) - - BeforeEach(func() { - testPath = "/testPath" - - server = ghttp.NewServer() - server.AppendHandlers( - ghttp.CombineHandlers( - ghttp.VerifyRequest("GET", testPath), - ghttp.RespondWith(http.StatusOK, src), - ), - ) - }) - - It("should return rendered template", func() { - data, err := template.URLGetter(server.URL() + testPath) - Expect(err).ShouldNot(HaveOccurred()) - Expect(string(data)).Should(Equal(src)) - }) - - AfterEach(func() { - server.Close() - }) - }) -}) diff --git a/pkg/util/template/processor.go b/pkg/util/template/processor.go deleted file mode 100644 index 1437a7c1e..000000000 --- a/pkg/util/template/processor.go +++ /dev/null @@ -1,30 +0,0 @@ -package template - -import ( - "regexp" -) - -// Processor process content before render -type processFunc func([]byte) []byte - -// AddDotForVariablesInConfigProcessor will add dot before varName -// this is because our variables' syntax is [[ varName ]] -// while Go's template is [[ .varName ]] -func AddDotForVariablesInConfigProcessor(bytes []byte) []byte { - regex := `\[\[\s*(\w+)\s*\]\]` - r := regexp.MustCompile(regex) - return r.ReplaceAll(bytes, []byte("[[ .$1 ]]")) -} - -// AddQuoteForVariablesInConfigProcessor will add quote for special varName -// When [[ ]] has two words and the first word don't contain quota, add quotes to the second word -// e.g. [[ env GITHUB_TOKEN]] -> [[ env "GITHUB_TOKEN" ]] -// [[ env 'GITHUB_TOKEN' ]] -> do nothing -// [[ env "GITHUB_TOKEN" ]] -> do nothing -// [[ "env" "GITHUB_TOKEN" ]] -> do nothing -// [[ GITHUB_TOKEN ]] -> do nothing -func AddQuoteForVariablesInConfigProcessor(bytes []byte) []byte { - regex := `\[\[\s*([^'"]\w+)\s+([^'"]\w+)\s*\]\]` - r := regexp.MustCompile(regex) - return r.ReplaceAll(bytes, []byte("[[ $1 \"$2\" ]]")) -} diff --git a/pkg/util/template/processor_test.go b/pkg/util/template/processor_test.go deleted file mode 100644 index 7a8161c66..000000000 --- a/pkg/util/template/processor_test.go +++ /dev/null @@ -1,150 +0,0 @@ -package template_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/pkg/util/template" -) - -var _ = Describe("AddDotForVariablesInConfigProcessor", func() { - var ( - gotten []byte - origin, expected string - ) - - JustBeforeEach(func() { - gotten = template.AddDotForVariablesInConfigProcessor([]byte(origin)) - }) - - When("config is normal", func() { - BeforeEach(func() { - origin = "[[varNameA]]" - expected = "[[ .varNameA ]]" - }) - It("should succeed", func() { - Expect(string(gotten)).To(Equal(expected)) - }) - }) - - When("config has spaces", func() { - BeforeEach(func() { - origin = "[[ varNameA ]]" - expected = "[[ .varNameA ]]" - }) - - It("should succeed", func() { - Expect(string(gotten)).To(Equal(expected)) - }) - }) - - When("config has trailing spaces", func() { - BeforeEach(func() { - origin = "[[ varNameA ]]" - expected = "[[ .varNameA ]]" - }) - - It("should succeed", func() { - Expect(string(gotten)).To(Equal(expected)) - }) - }) - - When("config has multiple variables", func() { - BeforeEach(func() { - origin = "[[ varNameA ]]/[[ varNameB ]]/[[ varNameC ]]" - expected = "[[ .varNameA ]]/[[ .varNameB ]]/[[ .varNameC ]]" - }) - - It("should succeed", func() { - Expect(string(gotten)).To(Equal(expected)) - }) - }) - - When("there are more than one words", func() { - BeforeEach(func() { - origin = "[[ func varNameA ]]" - expected = origin - }) - - It("should do nothing", func() { - Expect(string(gotten)).To(Equal(expected)) - }) - }) -}) - -var _ = Describe("AddQuoteForVariablesInConfigProcessor", func() { - var ( - gotten []byte - origin, expected string - ) - - JustBeforeEach(func() { - gotten = template.AddQuoteForVariablesInConfigProcessor([]byte(origin)) - }) - - When("config is normal", func() { - BeforeEach(func() { - origin = `[[env GITHUB_TOKEN]]` - expected = `[[ env "GITHUB_TOKEN" ]]` - }) - - It("should succeed", func() { - Expect(string(gotten)).To(Equal(expected)) - }) - }) - - When("config has single quote", func() { - BeforeEach(func() { - origin = `[[ env 'GITHUB_TOKEN']]` - expected = origin - }) - - It("should do nothing", func() { - Expect(string(gotten)).To(Equal(expected)) - }) - }) - - When("config has quote", func() { - BeforeEach(func() { - origin = `[[ env "GITHUB_TOKEN"]]` - expected = origin - }) - - It("should do nothing", func() { - Expect(string(gotten)).To(Equal(expected)) - }) - }) - - When("config has multiple variables", func() { - BeforeEach(func() { - origin = `[[ env GITHUB_TOKEN]]/[[ env "GITLAB_TOKEN"]]` - expected = `[[ env "GITHUB_TOKEN" ]]/[[ env "GITLAB_TOKEN"]]` - }) - - It("should succeed", func() { - Expect(string(gotten)).To(Equal(expected)) - }) - }) - - When("the first word has quote", func() { - BeforeEach(func() { - origin = `[[ "env" GITHUB_TOKEN]]` - expected = origin - }) - - It("should do nothing", func() { - Expect(string(gotten)).To(Equal(expected)) - }) - }) - - When("there is only one word", func() { - BeforeEach(func() { - origin = `[[GITHUB_TOKEN]]` - expected = origin - }) - - It("should do nothing", func() { - Expect(string(gotten)).To(Equal(expected)) - }) - }) -}) diff --git a/pkg/util/template/template_suite_test.go b/pkg/util/template/template_suite_test.go deleted file mode 100644 index 8420f9bb0..000000000 --- a/pkg/util/template/template_suite_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package template_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestTemplate(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Template Suite") -} diff --git a/pkg/util/trello/trello.go b/pkg/util/trello/trello.go deleted file mode 100644 index a37436d74..000000000 --- a/pkg/util/trello/trello.go +++ /dev/null @@ -1,52 +0,0 @@ -package trello - -import ( - "fmt" - - "github.com/adlio/trello" -) - -type TrelloAPI interface { - Create(name, description string) (*trello.Board, error) - Get(name, description string) (*trello.Board, error) -} - -type client struct { - *trello.Client -} - -func NewClient(apiKey, token string) (TrelloAPI, error) { - if apiKey == "" || token == "" { - const helpUrl = "https://docs.servicenow.com/bundle/quebec-it-asset-management/page/product/software-asset-management2/task/generate-trello-apikey-token.html" - return nil, fmt.Errorf("TRELLO_API_KEY and/or TRELLO_TOKEN are/is empty. see %s for more info", helpUrl) - } - - return &client{ - Client: trello.NewClient(apiKey, token), - }, nil -} - -func (c *client) Create(name, description string) (*trello.Board, error) { - board := trello.NewBoard(name) - board.Desc = description - - err := c.Client.CreateBoard(&board, trello.Defaults()) - if err != nil { - return nil, err - } - return &board, nil -} - -func (c *client) Get(name, description string) (*trello.Board, error) { - bs, err := c.Client.GetMyBoards() - if err != nil { - return nil, err - } - - for _, b := range bs { - if !b.Closed && b.Name == name && b.Desc == description { - return b, nil - } - } - return nil, nil -} diff --git a/pkg/util/trello/trello_mock.go b/pkg/util/trello/trello_mock.go deleted file mode 100644 index 6db490fe9..000000000 --- a/pkg/util/trello/trello_mock.go +++ /dev/null @@ -1,17 +0,0 @@ -package trello - -import "github.com/adlio/trello" - -type MockTrelloClient struct { - GetError error - GetValue *trello.Board - CreateError error -} - -func (c *MockTrelloClient) Create(name, description string) (*trello.Board, error) { - return nil, c.CreateError -} - -func (c *MockTrelloClient) Get(name, description string) (*trello.Board, error) { - return c.GetValue, c.GetError -} diff --git a/pkg/util/trello/trello_suit_test.go b/pkg/util/trello/trello_suit_test.go deleted file mode 100644 index f1a24bf1f..000000000 --- a/pkg/util/trello/trello_suit_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package trello_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestPlanmanager(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Util Trello Suite") -} diff --git a/pkg/util/trello/trello_test.go b/pkg/util/trello/trello_test.go deleted file mode 100644 index 4a9bf492c..000000000 --- a/pkg/util/trello/trello_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package trello_test - -import ( - . "github.com/onsi/ginkgo/v2" - //. "github.com/onsi/gomega" - "github.com/spf13/viper" -) - -var _ = Describe("Trello", func() { - Context("Board", func() { - It("Should does well with board CURD", func() { - // Adding the your env here - viper.Set("TRELLO_API_KEY", "") - viper.Set("TRELLO_TOKEN", "") - - // TODO(daniel-hutao): the code below is only used local now for my TRELLO_API_KEY & TRELLO_TOKEN can't be set in GitHub - - //c, err := trello.NewClient() - //Expect(err).NotTo(HaveOccurred()) - //err = c.CreateBoard("DS") - //Expect(err).NotTo(HaveOccurred()) - }) - }) -}) diff --git a/pkg/util/types/struct.go b/pkg/util/types/struct.go deleted file mode 100644 index ac220cee4..000000000 --- a/pkg/util/types/struct.go +++ /dev/null @@ -1,46 +0,0 @@ -package types - -import ( - "reflect" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - - "github.com/mitchellh/mapstructure" -) - -// FillStructDefaultValue will fill structData filed value by defaultStructData field value if they are empty -func FillStructDefaultValue(structData, defaultStructData any) { - if defaultStructData == nil { - return - } - defaultFalse := reflect.ValueOf(Bool(false)) - dataStruct := reflect.ValueOf(structData) - structIterator := reflect.Indirect(dataStruct) - defaultStruct := reflect.Indirect(reflect.ValueOf(defaultStructData)) - for i := 0; i < structIterator.NumField(); i++ { - field := structIterator.Type().Field(i) - fieldName := field.Name - fieldVal := structIterator.Field(i).Interface() - fieldType := structIterator.Field(i).Type() - zeroValue := reflect.Zero(fieldType).Interface() - - if reflect.DeepEqual(fieldVal, zeroValue) { - defaultValue := defaultStruct.FieldByName(fieldName) - if !reflect.DeepEqual(defaultValue.Interface(), zeroValue) { - dataStruct.Elem().Field(i).Set(defaultValue) - } else if defaultValue.Kind() == reflect.Ptr { - //TODO(steinliber): add more pointer judgement - dataStruct.Elem().Field(i).Set(defaultFalse) - } - } - } -} - -// EncodeStruct will get structData and encode this data to map -func EncodeStruct(structData any) (configmanager.RawOptions, error) { - var options configmanager.RawOptions - if err := mapstructure.Decode(structData, &options); err != nil { - return nil, err - } - return options, nil -} diff --git a/pkg/util/types/types.go b/pkg/util/types/types.go deleted file mode 100644 index 0de79fc7f..000000000 --- a/pkg/util/types/types.go +++ /dev/null @@ -1,5 +0,0 @@ -package types - -func Bool(v bool) *bool { return &v } - -func String(v string) *string { return &v } diff --git a/pkg/util/validator/validator.go b/pkg/util/validator/validator.go deleted file mode 100644 index 357d42f25..000000000 --- a/pkg/util/validator/validator.go +++ /dev/null @@ -1,91 +0,0 @@ -package validator - -import ( - "fmt" - "log" - "reflect" - "strings" - - "gopkg.in/yaml.v3" - - "github.com/go-playground/validator/v10" - "k8s.io/apimachinery/pkg/util/validation" -) - -var v *validator.Validate - -type StructFieldErrors []error - -func (errs StructFieldErrors) Combine() error { - if len(errs) == 0 { - return nil - } - var totalErr = "config options are not valid:\n" - for _, fieldErr := range errs { - totalErr = fmt.Sprintf("%s %s\n", totalErr, fieldErr) - } - return fmt.Errorf(strings.TrimSpace(totalErr)) -} - -func init() { - v = validator.New() - - validations := []struct { - tag string - fn validator.Func - }{ - {"dns1123subdomain", dns1123SubDomain}, - {"yaml", isYaml}, - } - - for _, vt := range validations { - if err := v.RegisterValidation(vt.tag, vt.fn); err != nil { - log.Fatal(err) - } - } - v.RegisterTagNameFunc(getMapstructureOrYamlTagName) -} - -// CheckStructError will check s, and return StructValidationError if this struct is not valid -func CheckStructError(s interface{}) StructFieldErrors { - fieldErrs := make(StructFieldErrors, 0) - if err := v.Struct(s); err != nil { - for _, fieldErr := range err.(validator.ValidationErrors) { - var fieldCustomErr error - switch fieldErr.Tag() { - case "required": - fieldCustomErr = fmt.Errorf("field %s is required", fieldErr.Namespace()) - case "url": - fieldCustomErr = fmt.Errorf("field %s is a not valid url", fieldErr.Namespace()) - case "oneof": - fieldCustomErr = fmt.Errorf("field %s must be one of [%s]", fieldErr.Namespace(), fieldErr.Param()) - default: - fieldCustomErr = fmt.Errorf("field %s validation failed on the '%s' tag", fieldErr.Namespace(), fieldErr.Tag()) - } - fieldErrs = append(fieldErrs, fieldCustomErr) - } - } - return fieldErrs -} - -func dns1123SubDomain(fl validator.FieldLevel) bool { - return len(validation.IsDNS1123Subdomain(fl.Field().String())) == 0 -} - -func isYaml(fl validator.FieldLevel) bool { - return yaml.Unmarshal([]byte(fl.Field().String()), &struct{}{}) == nil -} - -func getMapstructureOrYamlTagName(fld reflect.StructField) string { - // 1. get tag name from mapstructure or yaml - tagName := fld.Tag.Get("mapstructure") - if tagName == "" { - tagName = fld.Tag.Get("yaml") - } - // 2. else get yaml tag name - name := strings.SplitN(tagName, ",", 2)[0] - if name == "-" { - return "" - } - return name -} diff --git a/pkg/util/validator/validator_suite_test.go b/pkg/util/validator/validator_suite_test.go deleted file mode 100644 index 4598b7fbf..000000000 --- a/pkg/util/validator/validator_suite_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package validator_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestPlanmanager(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Pkg Util Validator Suite") -} diff --git a/pkg/util/validator/validator_test.go b/pkg/util/validator/validator_test.go deleted file mode 100644 index 4b1ed3f08..000000000 --- a/pkg/util/validator/validator_test.go +++ /dev/null @@ -1,114 +0,0 @@ -package validator - -import ( - "reflect" - - "github.com/go-playground/validator/v10" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -type mockTool struct { - Name string `validate:"required"` - InstanceID string `validate:"required,dns1123subdomain"` - URL string `validate:"url"` - TestOne string `validate:"required_without=TestTwo"` - TestTwo string `validate:"required_without=TestOne"` -} - -type mockFieldLeveler struct { - validator.FieldLevel - field string -} - -func (fl *mockFieldLeveler) Field() reflect.Value { - return reflect.ValueOf(fl.field) -} - -var _ = Describe("Check func", func() { - var ( - mockTest *mockTool - ) - When("all field is empty", func() { - BeforeEach(func() { - mockTest = &mockTool{} - }) - It("should return all field error", func() { - errs := CheckStructError(mockTest) - Expect(len(errs)).Should(Equal(5)) - Expect(errs.Combine().Error()).Should(Equal("config options are not valid:\n field mockTool.Name is required\n field mockTool.InstanceID is required\n field mockTool.URL is a not valid url\n field mockTool.TestOne validation failed on the 'required_without' tag\n field mockTool.TestTwo validation failed on the 'required_without' tag")) - }) - }) - When("all field is valid", func() { - BeforeEach(func() { - mockTest = &mockTool{ - Name: "test", - InstanceID: "test", - URL: "http://www.com", - TestOne: "without TestTwo", - } - }) - It("should return empty", func() { - errs := CheckStructError(mockTest) - Expect(errs).Should(BeEmpty()) - }) - }) -}) - -var _ = Describe("dns1123SubDomain func", func() { - var ( - testVal *mockFieldLeveler - ) - When("value is valid", func() { - BeforeEach(func() { - testVal = &mockFieldLeveler{field: "a"} - }) - It("should return true", func() { - Expect(dns1123SubDomain(testVal)).Should(BeTrue()) - }) - }) - When("valid is invalid", func() { - BeforeEach(func() { - testVal = &mockFieldLeveler{field: ""} - }) - It("should return false", func() { - Expect(dns1123SubDomain(testVal)).Should(BeFalse()) - }) - }) -}) - -var _ = Describe("isYaml func", func() { - var ( - testVal *mockFieldLeveler - ) - When("value is valid", func() { - BeforeEach(func() { - testVal = &mockFieldLeveler{field: ` -name: Martin D'vloper`} - }) - It("should return true", func() { - Expect(isYaml(testVal)).Should(BeTrue()) - }) - }) - When("valid is invalid", func() { - BeforeEach(func() { - testVal = &mockFieldLeveler{field: ` ---- -# An employee record -job: Developer -skill:Elite -foods: -- Apple -- Orange -languages: -perl: Elite -python: Elite -education: || -4 GCSEs -`} - }) - It("should return false", func() { - Expect(isYaml(testVal)).Should(BeFalse()) - }) - }) -}) diff --git a/staging/README.md b/staging/README.md deleted file mode 100644 index 5fbecd1d7..000000000 --- a/staging/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# Staging Repos - -This directory contains the staging repositories for the Devstream project. - -Each sub folder under `staging/` be synchronized to the repository of the same name under devstream-io. - -## How to Create a New Staging Repo - -Person who wants to create a new staging repo: - -1. Create a new directory under `staging/` with the name of the repo you want to create. -2. Update [`.github/sync-staging-repo.yml`](../.github/sync-staging-repo.yml). -3. Pull Request to the `main` branch of repo `devstream-io/devstream`. - -Reviewers: - -1. Review the PR, make sure everything is correct, and do not merge it immediately. -2. Create the repo under the `devstream-io` organization and **create a branch for the repo**. If you don't have the permission to create a repo, please ask for the members of the `devstream-io`. -3. Merge the PR. diff --git a/staging/dtm-jenkins-share-library/README.md b/staging/dtm-jenkins-share-library/README.md deleted file mode 100644 index 9822072ca..000000000 --- a/staging/dtm-jenkins-share-library/README.md +++ /dev/null @@ -1,56 +0,0 @@ -# dtm-jenkins-share-library - -This repo contains share library used by DevStream plugin "jenkins-pipeline" (thereafter: the plugin). It currently has the following functions: -- send notification to dingtalk when pipeline result is success or failed. -- run test for java project. -- run sonar scanner for java project. -- build docker image by current project, and push this image to image repo. - -## Example -```groovy dingtalk -/* -config dingtalk related info, if not set, pipeline result will not be sent -required plugins: dingding-notifications -*/ -setting.configNotifyDingtalk([ - 'robot_id': "dingdingtest", // robotID in jenkins config - 'at_user': "" -]) - -/* -config docker image repo info, if not set, image will not be sent to repo -*/ -setting.configImageRepo([ - 'image_repo': "test.com/library", - 'image_auth_secret_name': "docker-config", -]) - -/* -pipeline generic config -*/ -runPipeline([ - 'enable_test': true, // whether run test for code - 'name': "spring-test-github", // this name will be used for image repo name and sonar project name - 'enable_sonarqube': true, // whether use sonar to scan code -]) - -``` - -## General config variables - -| field | description | default_value | -| ---- | ---- | ---- | -| repo_type | whether code repo is github or gitlab, if repo_type is gitlab, we can use gitlab_connection to show jenkins pipeline status in gitlab | | -| name | this name will be used for image repo name and sonar project name | | -| language | the project language, current only support Java | java | -| container_requests_cpu | jenkins worker container requests cpu | 0.3 | -| container_requests_memory | jenkins worker container requests memory | 512Mi | -| container_limit_cpu | jenkins worker container limit cpu | 1 | -| container_limit_memory | jenkins worker container limit memory | 2Gi | -| enable_test | whether run test for code | true | -| enable_sonarqube | whether use sonar to scan code | false | - -## Where does this repo come from? - -`dtm-jenkins-share-library` is synced from https://github.com/devstream-io/devstream/blob/main/staging/dtm-jenkins-share-library. -Code changes are made in that location, merged into `devstream-io/devstream` and later synced here. diff --git a/staging/dtm-jenkins-share-library/src/com/devstream/ci/Git.groovy b/staging/dtm-jenkins-share-library/src/com/devstream/ci/Git.groovy deleted file mode 100644 index 9d5d226f0..000000000 --- a/staging/dtm-jenkins-share-library/src/com/devstream/ci/Git.groovy +++ /dev/null @@ -1,32 +0,0 @@ -package com.devstream.ci - -def getChangeString() { - def changeString = "" - def MAX_MSG_LEN = 10 - def changeLogSets = currentBuild.changeSets - for (int i = 0; i < changeLogSets.size(); i++) { - def entries = changeLogSets[i].items - for (int j = 0; j < entries.length; j++) { - def entry = entries[j] - truncatedMsg = entry.msg.take(MAX_MSG_LEN) - commitTime = new Date(entry.timestamp).format("yyyy-MM-dd HH:mm:ss") - changeString += " - ${truncatedMsg} [${entry.author} ${commitTime}]\n" - } - } - if (!changeString) { - changeString = " - No new changes" - } - return (changeString) -} - -def getCommitIDHead() { - String gitCommit - if (env.GIT_COMMIT) { - gitCommit = env.GIT_COMMIT.substring(0, 8) - } else { - sh "git config --global --add safe.directory '*'" - String gitCommitLang = sh (script: "git log -n 1 --pretty=format:'%H'", returnStdout: true) - gitCommit = gitCommitLang.substring(0, 8) - } - return gitCommit -} diff --git a/staging/dtm-jenkins-share-library/src/com/devstream/ci/Language.groovy b/staging/dtm-jenkins-share-library/src/com/devstream/ci/Language.groovy deleted file mode 100644 index 34b08168a..000000000 --- a/staging/dtm-jenkins-share-library/src/com/devstream/ci/Language.groovy +++ /dev/null @@ -1,26 +0,0 @@ -package com.devstream.ci - -def selector(String language){ - // config default options for different language - switch(language.toLowerCase()){ - case "java": - return javaDefault() - default: - if (!Config.generalSettings.ci_test_command) { - throw new Exception("Language %s language should set ci_test_command and ci_test_options in generalSettings") - } - } -} - - -def javaDefault() { - return [ - ci_test_command: 'mvn', - ci_test_options: '-B test', - ci_test_container_repo: 'maven:3.8.1-jdk-8', - container_requests_cpu: "512m", - container_requests_memory: "2Gi", - container_limit_cpu: "512m", - container_limit_memory: "2Gi", - ] -} diff --git a/staging/dtm-jenkins-share-library/src/com/devstream/notification/Dingtalk.groovy b/staging/dtm-jenkins-share-library/src/com/devstream/notification/Dingtalk.groovy deleted file mode 100644 index 7347a63e7..000000000 --- a/staging/dtm-jenkins-share-library/src/com/devstream/notification/Dingtalk.groovy +++ /dev/null @@ -1,31 +0,0 @@ -package com.devstream.notification - - -def send(changeString, headMessage, statusMessage, Integer _timeout=60) { - // String buildUser = variable.buildUserName() - String notifyUser = Config.notifySettings.get("at_user") - String robotID = Config.notifySettings.get("robot_id") - List atUsers = [] as String[] - if (notifyUser != null && notifyUser != "") { - atUsers = notifyUser.split(",") as String[] - } - timeout(time: _timeout, unit: 'SECONDS') { - dingtalk ( - robot: "${robotID}", - type: 'MARKDOWN', - title: "${env.JOB_NAME}[${env.BRANCH_NAME}]构建通知", - text: [ - "# $headMessage", - "# 构建详情", - "- 构建变更: ${changeString}", - "- 构建结果: ${statusMessage}", - // "- 构建人: **${buildUser}**", - "- 持续时间: ${currentBuild.durationString}", - "# 构建日志", - "[日志](${env.BUILD_URL}console)" - ], - at: atUsers - ) - } - -} diff --git a/staging/dtm-jenkins-share-library/src/com/devstream/scanner/SonarQube.groovy b/staging/dtm-jenkins-share-library/src/com/devstream/scanner/SonarQube.groovy deleted file mode 100644 index 8eee2ae18..000000000 --- a/staging/dtm-jenkins-share-library/src/com/devstream/scanner/SonarQube.groovy +++ /dev/null @@ -1,47 +0,0 @@ -package com.devstream.scanner - -import com.devstream.ci.Git - -def scanner( - String name, - String lang, - String options='') { - try { - println('Info: Preparing SonarQube Scanner') - gitUtil = new Git() - version = gitUtil.getCommitIDHead() - withSonarQubeEnv(){ - def private opts - - opts = ' -Dsonar.projectKey=' + name - opts += ' -Dsonar.projectName=' + name - opts += ' -Dsonar.projectVersion=' + version - opts += ' -Dsonar.language=' + lang - opts += ' -Dsonar.projectBaseDir=.' - opts += ' -Dsonar.sources=.' - opts += ' -Dsonar.java.binaries=.' - sonar_exec = 'sonar-scanner' + opts + ' ' + options - - sh(sonar_exec) - } - } - catch (e) { - println('Error: Failed with SonarQube Scanner') - throw e - } -} - -def qualityGateStatus(){ - try { - timeout(time: Config.generalSettings.sonarqube_timeout_minutes, unit: 'MINUTES') { - def qg_stats = waitForQualityGate() - if (qg_stats.status != 'SUCCESS') { - println('Error: Pipeline aborted due to quality gate failure: ' + qg.stats) - error "Pipeline aborted due to quality gate failure: ${qg.status}" - } - } - } - catch (e) { - throw e - } -} diff --git a/staging/dtm-jenkins-share-library/vars/Config.groovy b/staging/dtm-jenkins-share-library/vars/Config.groovy deleted file mode 100644 index dbc9156d0..000000000 --- a/staging/dtm-jenkins-share-library/vars/Config.groovy +++ /dev/null @@ -1,8 +0,0 @@ -// Config is a global config for jenkins pipeline -class Config implements Serializable { - static Map generalSettings = [:] - - static Map notifySettings = [:] - - static Map imageRepoSettings = [:] -} diff --git a/staging/dtm-jenkins-share-library/vars/controller.groovy b/staging/dtm-jenkins-share-library/vars/controller.groovy deleted file mode 100644 index 103121759..000000000 --- a/staging/dtm-jenkins-share-library/vars/controller.groovy +++ /dev/null @@ -1,80 +0,0 @@ -import com.devstream.scanner.SonarQube -import com.devstream.ci.Git - - -def testCode() { - def s = Config.generalSettings - private testCommand = s.ci_test_command + ' ' + s.ci_test_options - if (s.enable_test) { - container(s.ci_test_container_name) { - stage('Run Test') { - timeout(time: s.ci_test_timeout_minutes, unit: 'MINUTES') { - sh testCommand - } - } - } - } -} - - -def pushCodeImage() { - def s = Config.generalSettings - String imageRepo = "${Config.imageRepoSettings.user}/${s.name}" - if (Config.imageRepoSettings.image_repo) { - imageRepo = "${Config.imageRepoSettings.image_repo}/${s.name}" - } - String opts = "" - // if imageRepo contains http://, add registry.insecure=true option - if (imageRepo.contains("http://")) { - opts = ",registry.insecure=true" - imageRepo = imageRepo.replace("http://", "") - } - String defaultTag = "0.0.${currentBuild.number}" - String versionMethod = Config.imageRepoSettings.get("versionMethod") - String version = "default_version" - String buildContainerName = s.ci_build_container_name - switch (versionMethod) { - case "commitID": - gitUtil = new Git() - version = gitUtil.getCommitIDHead() - } - container(buildContainerName) { - stage('Build Docker Image') { - timeout(time: s.ci_build_timeout_minutes, unit: 'MINUTES') { - sh """ - buildctl build --frontend dockerfile.v0 --local context=. --local dockerfile=. --output type=image,name=${imageRepo}:${defaultTag},push=true${opts} - buildctl build --frontend dockerfile.v0 --local context=. --local dockerfile=. --output type=image,name=${imageRepo}:${version},push=true${opts} - """ - } - } - } -} - - -def cloneCode() { - stage("Get Project") { - checkout scm - } -} - -def sonarScan() { - def s = Config.generalSettings - if (s.enable_sonarqube) { - container(s.sonarqube_cli_container_name) { - stage('Sonar Scan Code') { - def sonar = new SonarQube() - sonar.scanner( - s.name, - s.language, - s.sonarqube_options, - ) - /* - This config has to config sonarqube, refer to https://docs.sonarqube.org/latest/analysis/scan/sonarscanner-for-jenkins/ - */ - // if (s.sonarqube_qualitygate_enable) { - // sonar.qualityGateStatus() - // } - } - } - } -} diff --git a/staging/dtm-jenkins-share-library/vars/pod.groovy b/staging/dtm-jenkins-share-library/vars/pod.groovy deleted file mode 100644 index 6a4ca5333..000000000 --- a/staging/dtm-jenkins-share-library/vars/pod.groovy +++ /dev/null @@ -1,83 +0,0 @@ -// testTemplate is used for config test container -def testTemplate(Closure body) { - String imageAddress = Config.generalSettings.ci_test_container_repo - String containerName = Config.generalSettings.ci_test_container_name - podTemplate( - containers: [ - containerTemplate( - name: containerName, - image: imageAddress, - command: 'sleep', - args: '99d', - resourceRequestCpu: Config.generalSettings.container_requests_cpu, - resourceLimitCpu: Config.generalSettings.container_limit_cpu, - resourceRequestMemory: Config.generalSettings.container_requests_memory, - resourceLimitMemory: Config.generalSettings.container_limit_memory, - ), - ], - ) { - body.call() - } -} - -// buildTemplate is used for config build container -def buildTemplate(Closure body) { - String dockerImageSecretName = Config.imageRepoSettings.get("auth_secret_name") - String imageAddress = Config.generalSettings.ci_build_container_repo - String containerName = Config.generalSettings.ci_build_container_name - if (dockerImageSecretName) { - podTemplate(containers: [ - containerTemplate( - name: containerName, - image: imageAddress, - ttyEnabled: true, - privileged: true, - resourceRequestCpu: Config.generalSettings.container_requests_cpu, - resourceLimitCpu: Config.generalSettings.container_limit_cpu, - resourceRequestMemory: Config.generalSettings.container_requests_memory, - resourceLimitMemory: Config.generalSettings.container_limit_memory, - ), - ], volumes: [ - secretVolume(secretName: dockerImageSecretName, mountPath: '/root/.docker') - ]) { - body.call() - } - } else { - podTemplate(containers: [ - containerTemplate( - name: containerName, - image: imageAddress, - ttyEnabled: true, - privileged: true, - resourceRequestCpu: Config.generalSettings.container_requests_cpu, - resourceLimitCpu: Config.generalSettings.container_limit_cpu, - resourceRequestMemory: Config.generalSettings.container_requests_memory, - resourceLimitMemory: Config.generalSettings.container_limit_memory, - ), - ]) { - body.call() - } - } -} - -// scannerTemplate is used for config scanner container -def scannerTemplate(Closure body) { - String imageAddress = Config.generalSettings.sonarqube_cli_container_repo - String containerName = Config.generalSettings.sonarqube_cli_container_name - podTemplate( - containers: [ - containerTemplate( - name: containerName, - image: imageAddress, - command: 'sleep', - args: '99d', - resourceRequestCpu: Config.generalSettings.container_requests_cpu, - resourceLimitCpu: Config.generalSettings.container_limit_cpu, - resourceRequestMemory: Config.generalSettings.container_requests_memory, - resourceLimitMemory: Config.generalSettings.container_limit_memory, - ), - ], - ) { - body.call() - } -} diff --git a/staging/dtm-jenkins-share-library/vars/post.groovy b/staging/dtm-jenkins-share-library/vars/post.groovy deleted file mode 100644 index 6aee225d2..000000000 --- a/staging/dtm-jenkins-share-library/vars/post.groovy +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env groovy - -def success() { - // config notify - if(Config.notifySettings) { - String successHeadMsg = "✅✅✅✅✅✅✅✅✅" - String successStatusMsg = "构建成功✅" - util.notify(successHeadMsg, successStatusMsg) - } -} - -def failure() { - // config notify - if(Config.notifySettings) { - String failureHeadMsg = "❌❌❌❌❌❌❌❌❌" - String failureStatusMsg = "构建失败❌" - util.notify(failureHeadMsg, failureStatusMsg) - } -} - -def aborted() { - // config notify - if(Config.notifySettings) { - String failureHeadMsg = "🟠🟠🟠🟠🟠🟠🟠" - String failureStatusMsg = "构建中断🟠" - util.notify(failureHeadMsg, failureStatusMsg) - } -} diff --git a/staging/dtm-jenkins-share-library/vars/runPipeline.groovy b/staging/dtm-jenkins-share-library/vars/runPipeline.groovy deleted file mode 100644 index 3f918ef23..000000000 --- a/staging/dtm-jenkins-share-library/vars/runPipeline.groovy +++ /dev/null @@ -1,72 +0,0 @@ -def call(Map config=[:]) { - setting.configGeneral(config) - templates { - entry() - } -} - -def entry() { - node(POD_LABEL) { - try { - controller.cloneCode() - parallel( - 'Test': {controller.testCode()}, - 'Sonar Scan': {controller.sonarScan()}, - ) - controller.pushCodeImage() - post.success() - } catch (org.jenkinsci.plugins.workflow.steps.FlowInterruptedException err) { - post.aborted() - throw err - } catch (Exception err) { - post.failure() - throw err - } - } -} - - - -def templates(Closure body) { - def s = Config.generalSettings - if (s.repo_type == "gitlab") { - gitlabCommitStatus { - testTemplate(body) - } - } else { - testTemplate(body) - } -} - -def testTemplate(Closure body) { - def s = Config.generalSettings - if (!s.enable_test) { - if (s.enable_sonarqube) { - pod.scannerTemplate { - pod.buildTemplate { - body.call() - } - } - } else { - pod.buildTemplate { - body.call() - } - } - } else { - if (s.enable_sonarqube) { - pod.testTemplate { - pod.scannerTemplate { - pod.buildTemplate { - body.call() - } - } - } - } else { - pod.testTemplate { - pod.buildTemplate { - body.call() - } - } - } - } -} diff --git a/staging/dtm-jenkins-share-library/vars/setting.groovy b/staging/dtm-jenkins-share-library/vars/setting.groovy deleted file mode 100644 index 5179d8f06..000000000 --- a/staging/dtm-jenkins-share-library/vars/setting.groovy +++ /dev/null @@ -1,69 +0,0 @@ -import com.devstream.ci.Language - -def configImageRepo(Map imageRepoConfig=[:]) { - imageSettings = defaultImageRepoSettings() + imageRepoConfig - Config.imageRepoSettings = imageSettings -} - -def configGeneral(Map config=[:]) { - defaultConfig = defaultSettings() - language = config['language'] - if (!language) { - language = defaultConfig['language'] - } - languageConfig = new Language() - languageDefaultConfig = languageConfig.selector(language) - Config.generalSettings = defaultConfig + languageDefaultConfig + config -} - -def configNotifyDingtalk(Map notifyConfig=[:]) { - notifyConfig['notify_type'] = 'dingding' - if (notifyConfig.containsKey("at_user")) { - notifyConfig['at_user'] = "" - } - Config.notifySettings = notifyConfig -} - -// config default settings -def defaultSettings() { - return [ - repo_type: "", - name: "", - language: "java", - // container resource for podTemplate - container_requests_cpu: "0.3", - container_requests_memory: "512Mi", - container_limit_cpu: "1", - container_limit_memory: "2Gi", - // ci related config - enable_test: true, - ci_test_command: "", - ci_test_options: "", - ci_test_container_repo: "", - ci_test_timeout_minutes: 20, - ci_test_container_name: "test-container", - ci_build_container_repo: "moby/buildkit:master", - ci_build_container_name: "build-container", - ci_build_timeout_minutes: 20, - // sonar related config - enable_sonarqube: false, - sonarqube_options: "", - sonarqube_qualitygate_enable: true, - sonarqube_timeout_minutes: 20, - sonarqube_cli_container_repo: "sonarsource/sonar-scanner-cli:latest", - sonarqube_cli_container_name: "scanner-sonar-container", - ] -} - -def defaultImageRepoSettings() { - return [ - auth_secret_name: "", - user: "", - image_repo: "", - defaultTag: "latest", - versionMethod: "commitID", - ] -} - - -return this diff --git a/staging/dtm-jenkins-share-library/vars/util.groovy b/staging/dtm-jenkins-share-library/vars/util.groovy deleted file mode 100644 index 25504f803..000000000 --- a/staging/dtm-jenkins-share-library/vars/util.groovy +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env groovy - -/* utils.groovy -This package is used for utils func -*/ -import com.devstream.notification.Dingtalk -import com.devstream.ci.Git - -/* -send notifucation -input params => -statusMessage: jenkins status message -headMessage: jenkins head message -*/ -def notify(String headMessage, String statusMessage) { - def gitUtils = new Git() - String changeString = gitUtils.getChangeString() - switch (Config.notifySettings.notify_type) { - case "dingding": - dingtalk = new Dingtalk() - dingtalk.send(changeString, headMessage, statusMessage) - break - default: - throw new Exception("jenkins notify type ${Config.notifySettings.notifyType} doesn't support") - } -} diff --git a/staging/dtm-jenkins-share-library/vars/variable.groovy b/staging/dtm-jenkins-share-library/vars/variable.groovy deleted file mode 100644 index 2eec36d9b..000000000 --- a/staging/dtm-jenkins-share-library/vars/variable.groovy +++ /dev/null @@ -1,18 +0,0 @@ -def buildUserName(){ - /* - Requeire 'build user vars' plugin, See https://plugins.jenkins.io/build-user-vars-plugin for more information - */ - wrap([$class: 'BuildUser']) { - return BUILD_USER - } -} - -def checkGitRepo() { - def repoType = Config.generalSettings.get("repo_type") - return repoType && repoType == "gitlab" -} - -def checkPushImage() { - String imageName = Config.imageRepoSettings.get("image_name") - return (imageName && imageName != "") -} diff --git a/staging/dtm-pipeline-templates/README.md b/staging/dtm-pipeline-templates/README.md deleted file mode 100644 index f186842a2..000000000 --- a/staging/dtm-pipeline-templates/README.md +++ /dev/null @@ -1,20 +0,0 @@ -# dtm-pipeline-templates - -This Repo is used to store devstream's best pipeline practice templates, If you like the template and want to use it, a fork is welcome. - -## Pipeline Templates - -It support pipeline templates below for now: - -| ciType | configPath | description | -| ---- | ---- | ---- | -| Github actions | github-actions/workflows | github actions template, support test, build, pushImage, notify stage | -| Gitlab ci | gitlab-ci/.gitlab-ci.yml | gitlab ci template, support test, build, pushImage, notify stage | -| Genkins pipeline | jenkins-pipeline/Jenkinsfile | jenkins-pipeline template, support test, build, pushImage, notify stage | -| Argocd helm | argocdapp/helm | argocdapp helm config | -| Argocd kustomize | argocdapp/kustomize | argocd kustomize config | - -## Where does this repo come from? - -`dtm-pipeline-templates` is synced from https://github.com/devstream-io/devstream/blob/main/staging/dtm-pipeline-templates. -Code changes are made in that location, merged into `devstream-io/devstream` and later synced here. diff --git a/staging/dtm-pipeline-templates/argocdapp/helm/.helmignore b/staging/dtm-pipeline-templates/argocdapp/helm/.helmignore deleted file mode 100644 index f0c131944..000000000 --- a/staging/dtm-pipeline-templates/argocdapp/helm/.helmignore +++ /dev/null @@ -1,21 +0,0 @@ -# Patterns to ignore when building packages. -# This supports shell glob matching, relative path matching, and -# negation (prefixed with !). Only one pattern per line. -.DS_Store -# Common VCS dirs -.git/ -.gitignore -.bzr/ -.bzrignore -.hg/ -.hgignore -.svn/ -# Common backup files -*.swp -*.bak -*.tmp -*~ -# Various IDEs -.project -.idea/ -*.tmproj diff --git a/staging/dtm-pipeline-templates/argocdapp/helm/Chart.yaml b/staging/dtm-pipeline-templates/argocdapp/helm/Chart.yaml deleted file mode 100644 index 762cdf721..000000000 --- a/staging/dtm-pipeline-templates/argocdapp/helm/Chart.yaml +++ /dev/null @@ -1,6 +0,0 @@ -apiVersion: v2 -name: [[ .App.Name ]] -description: A Helm chart for Kubernetes -type: application -version: 0.1.0 -appVersion: "0.0.1" diff --git a/staging/dtm-pipeline-templates/argocdapp/helm/templates/_helpers.tpl b/staging/dtm-pipeline-templates/argocdapp/helm/templates/_helpers.tpl deleted file mode 100644 index ead1c2a9d..000000000 --- a/staging/dtm-pipeline-templates/argocdapp/helm/templates/_helpers.tpl +++ /dev/null @@ -1,32 +0,0 @@ -{{/* vim: set filetype=mustache: */}} -{{/* -Expand the name of the chart. -*/}} -{{- define "app.name" -}} -{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Create a default fully qualified app name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -If release name contains chart name it will be used as a full name. -*/}} -{{- define "app.fullname" -}} -{{- if .Values.fullnameOverride -}} -{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} -{{- else -}} -{{- $name := default .Chart.Name .Values.nameOverride -}} -{{- if contains $name .Release.Name -}} -{{- .Release.Name | trunc 63 | trimSuffix "-" -}} -{{- else -}} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} -{{- end -}} -{{- end -}} -{{- end -}} - -{{/* -Create chart name and version as used by the chart label. -*/}} -{{- define "app.chart" -}} -{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} -{{- end -}} diff --git a/staging/dtm-pipeline-templates/argocdapp/helm/templates/deployment.yaml b/staging/dtm-pipeline-templates/argocdapp/helm/templates/deployment.yaml deleted file mode 100644 index 6010d9adf..000000000 --- a/staging/dtm-pipeline-templates/argocdapp/helm/templates/deployment.yaml +++ /dev/null @@ -1,30 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ template "app.fullname" . }} - labels: - app: {{ template "app.name" . }} - chart: {{ template "app.chart" . }} - release: {{ .Release.Name }} - heritage: {{ .Release.Service }} -spec: - replicas: {{ .Values.replicaCount }} - revisionHistoryLimit: 3 - selector: - matchLabels: - app: {{ template "app.name" . }} - release: {{ .Release.Name }} - template: - metadata: - labels: - app: {{ template "app.name" . }} - release: {{ .Release.Name }} - spec: - containers: - - name: {{ .Chart.Name }} - image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" - imagePullPolicy: {{ .Values.image.pullPolicy }} - ports: - - name: http - containerPort: 8080 - protocol: TCP diff --git a/staging/dtm-pipeline-templates/argocdapp/helm/templates/service.yaml b/staging/dtm-pipeline-templates/argocdapp/helm/templates/service.yaml deleted file mode 100644 index 12b134106..000000000 --- a/staging/dtm-pipeline-templates/argocdapp/helm/templates/service.yaml +++ /dev/null @@ -1,19 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: {{ template "app.fullname" . }} - labels: - app: {{ template "app.name" . }} - chart: {{ template "app.chart" . }} - release: {{ .Release.Name }} - heritage: {{ .Release.Service }} -spec: - type: {{ .Values.service.type }} - ports: - - port: {{ .Values.service.port }} - targetPort: http - protocol: TCP - name: http - selector: - app: {{ template "app.name" . }} - release: {{ .Release.Name }} diff --git a/staging/dtm-pipeline-templates/argocdapp/helm/values.yaml b/staging/dtm-pipeline-templates/argocdapp/helm/values.yaml deleted file mode 100644 index 00323e8f6..000000000 --- a/staging/dtm-pipeline-templates/argocdapp/helm/values.yaml +++ /dev/null @@ -1,10 +0,0 @@ -replicaCount: 1 - -image: - repository: [[ .ImageRepo.URL ]][[ .ImageRepo.User ]]/[[ .App.Name ]] - tag: [[ .ImageRepo.InitalTag ]] - pullPolicy: Always - -service: - type: ClusterIP - port: 8080 diff --git a/staging/dtm-pipeline-templates/argocdapp/kustomize/guestbook-ui-deployment.yaml b/staging/dtm-pipeline-templates/argocdapp/kustomize/guestbook-ui-deployment.yaml deleted file mode 100644 index 47cde835c..000000000 --- a/staging/dtm-pipeline-templates/argocdapp/kustomize/guestbook-ui-deployment.yaml +++ /dev/null @@ -1,20 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: guestbook-ui -spec: - replicas: 1 - revisionHistoryLimit: 3 - selector: - matchLabels: - app: guestbook-ui - template: - metadata: - labels: - app: guestbook-ui - spec: - containers: - - image: gcr.io/heptio-images/ks-guestbook-demo:0.1 - name: guestbook-ui - ports: - - containerPort: 8080 diff --git a/staging/dtm-pipeline-templates/argocdapp/kustomize/guestbook-ui-svc.yaml b/staging/dtm-pipeline-templates/argocdapp/kustomize/guestbook-ui-svc.yaml deleted file mode 100644 index 63551b76d..000000000 --- a/staging/dtm-pipeline-templates/argocdapp/kustomize/guestbook-ui-svc.yaml +++ /dev/null @@ -1,10 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: guestbook-ui -spec: - ports: - - port: 8080 - targetPort: 8080 - selector: - app: guestbook-ui diff --git a/staging/dtm-pipeline-templates/argocdapp/kustomize/kustomization.yaml b/staging/dtm-pipeline-templates/argocdapp/kustomize/kustomization.yaml deleted file mode 100644 index cbaba9021..000000000 --- a/staging/dtm-pipeline-templates/argocdapp/kustomize/kustomization.yaml +++ /dev/null @@ -1,7 +0,0 @@ -namePrefix: kustomize- - -resources: -- guestbook-ui-deployment.yaml -- guestbook-ui-svc.yaml -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization diff --git a/staging/dtm-pipeline-templates/github-actions/workflows/main.yml b/staging/dtm-pipeline-templates/github-actions/workflows/main.yml deleted file mode 100644 index b2e114712..000000000 --- a/staging/dtm-pipeline-templates/github-actions/workflows/main.yml +++ /dev/null @@ -1,127 +0,0 @@ -name: Main Branch Builder -on: - push: - branches: [ master, main ] -permissions: write-all -[[ define "dingNotifyFailedBlock" ]] - # dingTalk Notify Block - - name: Post result - if: failure() - uses: zcong1993/actions-ding@master - with: - dingToken: ${{ secrets.[[ .DingTalkSecretToken ]] }} - secret: ${{ secrets.[[ .DingTalkSecretKey ]] }} - body: | - { - "msgtype": "markdown", - "markdown": { - "title": "构建通知", - "text": "❌❌❌❌❌❌❌❌❌\n# 构建详情\n- 构建变更: ${{ github.event.head_commit.message }} \n- 构建结果: 构建失败❌\n# 构建日志\n [日志](${{ github.repositoryUrl }})" - } - } -[[ end -]] - -[[ define "dingNotifyBlock" ]] -[[ template "dingNotifyFailedBlock" . ]] - # dingTalk fail Notify Block - - name: Post result - if: success() - uses: zcong1993/actions-ding@master - with: - dingToken: ${{ secrets.[[ .DingTalkSecretToken ]] }} - secret: ${{ secrets.[[ .DingTalkSecretKey ]] }} - body: | - { - "msgtype": "markdown", - "markdown": { - "title": "构建通知", - "text": "✅✅✅✅✅✅✅✅✅\n# 构建详情\n- 构建变更: ${{ github.event.head_commit.message }} \n- 构建结果: 构建成功✅\n# 构建日志\n [日志](${{ github.repositoryUrl }})" - } - } -[[ end ]] -jobs: - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Set up [[ .language.name ]] - uses: actions/setup-[[ .language.name ]]@v3 - with: - [[ .language.name ]]-version: [[ .language.version ]] - [[ if eq .language.name "java" ]] - cache: maven - distribution: adopt - [[ end ]] - - name: Test - run: | - [[ range .test.command ]] - [[ . -]] - [[ end ]] - [[ if .dingTalk -]] - [[ template "dingNotifyFailedBlock" . ]] - [[- end ]] -[[ if .imageRepo ]] - tag: - name: Tag - needs: [test] - if: ${{ github.event_name == 'push' }} - runs-on: ubuntu-latest - outputs: - new_tag: ${{ steps.tag_version.outputs.new_tag }} - steps: - - uses: actions/checkout@v3 - - name: Bump version and push tag - id: tag_version - uses: mathieudutour/github-tag-action@v6.1 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - tag_prefix: "" - [[ if .dingTalk -]] - [[ template "dingNotifyFailedBlock" . ]] - [[- end ]] - image: - name: Build Docker Image - needs: [tag] - if: ${{ github.event_name == 'push' }} - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Login to Docker Hub - uses: docker/login-action@v2 - with: - username: [[ .imageRepo.user ]] - password: ${{ secrets.[[ .ImageRepoSecret ]] }} - - name: Build and push - id: docker_build - uses: docker/build-push-action@v3 - with: - push: true - tags: "[[ .imageRepo.user ]]/[[ .AppName ]]:${{needs.tag.outputs.new_tag}}" - [[ if .dingTalk -]] - [[ template "dingNotifyBlock" .]] - [[- end ]] - [[ end ]] - [[ if .sonarqube ]] - sonarqube: - name: SonarQube Scanner - runs-on: ubuntu-latest - steps: - - name: Checking out - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - name: Set up [[ .language.name ]] - uses: actions/setup-[[ .language.name ]]@v3 - with: - [[ .language.name ]]-version: [[ .language.version ]] - - name: Build - run: mvn install -DskipTests - - name: Setup sonarqube - uses: warchant/setup-sonar-scanner@v4 - - name: Sonarqube Scan - run: sonar-scanner - -Dsonar.java.binaries=target/classes - -Dsonar.projectKey=[[ .AppName ]] - -Dsonar.host.url=[[ .sonarqube.url ]] - -Dsonar.login=${{ secrets.[[ .SonarqubeSecretKey ]] }} - [[ end ]] diff --git a/staging/dtm-pipeline-templates/gitlab-ci/.gitlab-ci.yml b/staging/dtm-pipeline-templates/gitlab-ci/.gitlab-ci.yml deleted file mode 100644 index ac28e6559..000000000 --- a/staging/dtm-pipeline-templates/gitlab-ci/.gitlab-ci.yml +++ /dev/null @@ -1,46 +0,0 @@ -stages: - - test - - build - -variables: - # dingding notify - DINGTALK_ACCESS_TOKEN: "$DINGTALK_SECURITY_TOKEN" - DINGTALK_SECURITY_VALUE: "$DINGTALK_SECURITY_VALUE" - # image repo config - IMAGE_REPO_URL: "[[ .imageRepo.url ]]" - IMAGE_REPO_USER: "[[ .imageRepo.user ]]" - IMAGE_REPO_PASSWORD: "$IMAGE_REPO_SECRET" - # general config - INIT_IMAGE_TAG: "0.0.1" - APP_NAME: "[[ .AppName ]]" - - -test: - stage: test - image: - name: [[ .test.containerName ]] - script: | - [[ range .test.command ]] - [[ . -]] - [[ end ]] - -build: - stage: build - services: - - alias: buildkitd - name: moby/buildkit:rootless - command: - - "--oci-worker-no-process-sandbox" - - "--addr" - - "tcp://0.0.0.0:1234" - image: - name: moby/buildkit:master - entrypoint: [ "sh", "-c" ] - variables: - BUILDKIT_HOST: tcp://buildkitd:1234 - DOCKER_CONFIG: /home/user/.docker - script: - - mkdir -p $DOCKER_CONFIG - - echo "{\"auths\":{\"$IMAGE_REPO_URL\":{\"auth\":\"$(printf "%s:%s" "${IMAGE_REPO_USER}" "${IMAGE_REPO_PASSWORD}" | base64 | tr -d '\n')\"}}}" > $DOCKER_CONFIG/config.json - - buildctl build --frontend dockerfile.v0 --local context=. --local dockerfile=. --output type=image,name=$IMAGE_REPO_USER/$APP_NAME:${CI_COMMIT_SHA:0:8},push=true - - buildctl build --frontend dockerfile.v0 --local context=. --local dockerfile=. --output type=image,name=$IMAGE_REPO_USER/$APP_NAME:${INIT_IMAGE_TAG},push=true diff --git a/staging/dtm-pipeline-templates/jenkins-pipeline/README.md b/staging/dtm-pipeline-templates/jenkins-pipeline/README.md deleted file mode 100644 index 91991f61d..000000000 --- a/staging/dtm-pipeline-templates/jenkins-pipeline/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# dtm-jenkins-pipeline-example - -This repo contains templates used by DevStream plugin "jenkins-pipeline" (thereafter: the plugin). - -This repo isn't intended to be used directly without DevStream. It should only be consumed by the plugin automatically. - -The plugin (together with this repo of templates) can create a Jenkinsfile file in scm. diff --git a/staging/dtm-pipeline-templates/jenkins-pipeline/general/Jenkinsfile b/staging/dtm-pipeline-templates/jenkins-pipeline/general/Jenkinsfile deleted file mode 100644 index e84911cee..000000000 --- a/staging/dtm-pipeline-templates/jenkins-pipeline/general/Jenkinsfile +++ /dev/null @@ -1,25 +0,0 @@ -[[ if .dingTalk -]] -setting.configNotifyDingtalk([ - 'robot_id': "[[ .dingTalk.name ]]", -]) -[[ end -]] -[[ if .imageRepo -]] -setting.configImageRepo([ - 'image_repo': "[[ .imageRepo.url ]]", - 'user': "[[ .imageRepo.user ]]", - 'auth_secret_name': "[[ .ImageRepoDockerSecret ]]", -]) -[[ end -]] -runPipeline([ - 'repo_type': "[[ .RepoType ]]", - 'name': "[[ .AppName ]]", - [[ if .sonarqube -]] - 'sonarqube_enable': true, - [[ end -]] - [[ if .language -]] - 'language': "[[ .language.name ]]", - [[ end -]] - [[ if .test.enable -]] - 'test_enable': [[ .test.enable ]], - [[ end -]] -]) diff --git a/staging/dtm-pipeline-templates/jenkins-pipeline/springboot/Jenkinsfile b/staging/dtm-pipeline-templates/jenkins-pipeline/springboot/Jenkinsfile deleted file mode 100644 index 4976eb98f..000000000 --- a/staging/dtm-pipeline-templates/jenkins-pipeline/springboot/Jenkinsfile +++ /dev/null @@ -1,38 +0,0 @@ -version="1.0.${env.BUILD_NUMBER}" -repository="[[ .ImageRepoAddress ]]/[[ .ImageName ]]" -tag="latest" -image="${repository}:${version}" - -podTemplate(containers: [ - containerTemplate(name: 'maven', image: 'maven:3.8.6-openjdk-18', command: 'sleep', args: '99d'), - containerTemplate(name: 'buildkit', image: 'moby/buildkit:master', ttyEnabled: true, privileged: true), - ], volumes: [ - secretVolume(secretName: 'docker-config', mountPath: '/root/.docker') - ]) { - node(POD_LABEL) { - stage("Get Project") { - checkout scm - } - stage('Run Maven test') { - gitlabCommitStatus("test") { - container('maven') { - stage('run mvn test') { - sh 'mvn -B test' - } - } - } - } - stage("Build Docker image") { - gitlabCommitStatus("build image") { - container('buildkit') { - stage('build a Maven project') { - sh """ - buildctl build --frontend dockerfile.v0 --local context=. --local dockerfile=. --output type=image,name=${image},push=true,registry.insecure=true - buildctl build --frontend dockerfile.v0 --local context=. --local dockerfile=. --output type=image,name=${repository}:${tag},push=true,registry.insecure=true - """ - } - } - } - } - } -} diff --git a/staging/dtm-repo-scaffolding-golang-cli/.github/workflows/release.yml b/staging/dtm-repo-scaffolding-golang-cli/.github/workflows/release.yml deleted file mode 100644 index 2c3678469..000000000 --- a/staging/dtm-repo-scaffolding-golang-cli/.github/workflows/release.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: goreleaser - -on: - push: - tags: - - '*' - -jobs: - goreleaser: - runs-on: ubuntu-latest - steps: - - - name: Checkout - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - name: Set up Go - uses: actions/setup-go@v3 - - - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v3 - with: - # either 'goreleaser' (default) or 'goreleaser-pro' - distribution: goreleaser - version: latest - args: release --rm-dist - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # Your GoReleaser Pro key, if you are using the 'goreleaser-pro' distribution - # GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} diff --git a/staging/dtm-repo-scaffolding-golang-cli/.gitignore b/staging/dtm-repo-scaffolding-golang-cli/.gitignore deleted file mode 100644 index 4e86ee083..000000000 --- a/staging/dtm-repo-scaffolding-golang-cli/.gitignore +++ /dev/null @@ -1,27 +0,0 @@ -### Go template -# Binaries for programs and plugins -*.exe -*.exe~ -*.dll -*.so -*.dylib - -# Test binary, built with `go test -c` -*.test - -# Output of the go coverage tool, specifically when used with LiteIDE -*.out - -# Dependency directories (remove the comment below to include it) -# vendor/ - -### VisualStudioCode template -.vscode/* - -### JetBrains template -.idea/ - -### macOS template -.DS_Store - -dist/ diff --git a/staging/dtm-repo-scaffolding-golang-cli/LICENSE b/staging/dtm-repo-scaffolding-golang-cli/LICENSE deleted file mode 100644 index e69de29bb..000000000 diff --git a/staging/dtm-repo-scaffolding-golang-cli/Makefile.tpl b/staging/dtm-repo-scaffolding-golang-cli/Makefile.tpl deleted file mode 100644 index 8514c7ac7..000000000 --- a/staging/dtm-repo-scaffolding-golang-cli/Makefile.tpl +++ /dev/null @@ -1,22 +0,0 @@ -SHELL := /bin/bash -BASEDIR = $(shell pwd) - -.PHONY: help -help: - @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' - -.PHONY: install -install: ## Install dependencies - @go mod download - @go mod vendor - -.PHONY: dev -dev: ## Run with Dev - @go run cmd/root.go - -.PHONY: build -build: ## Build todomvc - @go build -o build/[[.AppName]] cmd/main.go - -clean: ### Remove build dir - @rm -rf build diff --git a/staging/dtm-repo-scaffolding-golang-cli/README.md b/staging/dtm-repo-scaffolding-golang-cli/README.md deleted file mode 100644 index 722d6ae09..000000000 --- a/staging/dtm-repo-scaffolding-golang-cli/README.md +++ /dev/null @@ -1,33 +0,0 @@ -# dtm-repo-scaffolding-golang-cli - -This repo contains templates used by DevStream plugin "repo-scaffolding" (thereafter: the plugin). - -This repo isn't intended to be used directly without DevStream. It should only be consumed by the plugin automatically. - -The plugin (together with this repo of templates) can create a repo in GitHub and set up the project layout and initialize the reop with necessary files that are typical for a Go cli app. The followings can be created automatically: - -- a Go cli app example (generated by [`cobra-cli init`](https://github.com/spf13/cobra-cli/blob/main/README.md)) -- [GoReleaser-Actions workflow](https://github.com/goreleaser/goreleaser-action)(Once you push a tag, it will build and release your app automatically) -- `.gitignore`, generated by [Toptal | gitignore.io](https://www.toptal.com/developers/gitignore/api/go,vim,macos,visualstudiocode) with minimum changes -- Makefile, with install/dev/build/clean - -## Usage - -- Render all files using go template whose name end with `.tpl` suffix. -- Files whose name don't end with `.tpl` extension don't need to be rendered. -- subdirectory "helm/**_app_name_**" (the **_app_name_** part) should be rendered with `AppName` -- subdicrectory "cmd/**_app_name_**" (the **_app_name_** part) should be rendered with `AppName` - -Example of required parameters to render these templates: - -```yaml -AppName: my-hello-world -Repo: - Owner: ironcore864 - Name: my-hello-world -``` - -## Where does this repo come from? - -`dtm-repo-scaffolding-golang-cli` is synced from https://github.com/devstream-io/devstream/blob/main/staging/dtm-repo-scaffolding-golang-cli. -Code changes are made in that location, merged into `devstream-io/devstream` and later synced here. diff --git a/staging/dtm-repo-scaffolding-golang-cli/README.md.tpl b/staging/dtm-repo-scaffolding-golang-cli/README.md.tpl deleted file mode 100644 index c063ec13a..000000000 --- a/staging/dtm-repo-scaffolding-golang-cli/README.md.tpl +++ /dev/null @@ -1,21 +0,0 @@ -# [[.AppName]] - -This is a repo for app [[.AppName]]; bootstrapped by DevStream. - -By default, the automatically generated scaffolding contains: - -- a piece of sample go cli app code using the [Cobra Commander Framework](https://github.com/spf13/cobra) -- [GoReleaser](https://goreleaser.com/) for building and releasing the app -- .gitignore -- Makefile - -## Automatic Releases - -Just push a tag to the repo and [GoReleaser](https://goreleaser.com/) will build and release the app. - -For example, to release version 0.1.0, run: - -```git -git tag -a v0.1.0 -m "First release" -git push origin v0.1.0 -``` diff --git a/staging/dtm-repo-scaffolding-golang-cli/cmd/root.go.tpl b/staging/dtm-repo-scaffolding-golang-cli/cmd/root.go.tpl deleted file mode 100644 index dde66104d..000000000 --- a/staging/dtm-repo-scaffolding-golang-cli/cmd/root.go.tpl +++ /dev/null @@ -1,51 +0,0 @@ -/* -Copyright © 2022 NAME HERE - -*/ -package cmd - -import ( - "os" - - "github.com/spf13/cobra" -) - - - -// rootCmd represents the base command when called without any subcommands -var rootCmd = &cobra.Command{ - Use: "[[.AppName]]", - Short: "A brief description of your application", - Long: `A longer description that spans multiple lines and likely contains -examples and usage of using your application. For example: - -Cobra is a CLI library for Go that empowers applications. -This application is a tool to generate the needed files -to quickly create a Cobra application.`, - // Uncomment the following line if your bare application - // has an action associated with it: - // Run: func(cmd *cobra.Command, args []string) { }, -} - -// Execute adds all child commands to the root command and sets flags appropriately. -// This is called by main.main(). It only needs to happen once to the rootCmd. -func Execute() { - err := rootCmd.Execute() - if err != nil { - os.Exit(1) - } -} - -func init() { - // Here you will define your flags and configuration settings. - // Cobra supports persistent flags, which, if defined here, - // will be global for your application. - - // rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.myapp.yaml)") - - // Cobra also supports local flags, which will only run - // when this action is called directly. - rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") -} - - diff --git a/staging/dtm-repo-scaffolding-golang-cli/go.mod.tpl b/staging/dtm-repo-scaffolding-golang-cli/go.mod.tpl deleted file mode 100644 index a72adf9fc..000000000 --- a/staging/dtm-repo-scaffolding-golang-cli/go.mod.tpl +++ /dev/null @@ -1,9 +0,0 @@ -module github.com/[[.Repo.Owner]]/[[.Repo.Name]] - -go 1.19 - -require ( - github.com/inconshreveable/mousetrap v1.0.1 // indirect - github.com/spf13/cobra v1.6.1 // indirect - github.com/spf13/pflag v1.0.5 // indirect -) diff --git a/staging/dtm-repo-scaffolding-golang-cli/go.sum.tpl b/staging/dtm-repo-scaffolding-golang-cli/go.sum.tpl deleted file mode 100644 index 442875a4e..000000000 --- a/staging/dtm-repo-scaffolding-golang-cli/go.sum.tpl +++ /dev/null @@ -1,10 +0,0 @@ -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= -github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= -github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/staging/dtm-repo-scaffolding-golang-cli/main.go.tpl b/staging/dtm-repo-scaffolding-golang-cli/main.go.tpl deleted file mode 100644 index 4170e82db..000000000 --- a/staging/dtm-repo-scaffolding-golang-cli/main.go.tpl +++ /dev/null @@ -1,11 +0,0 @@ -/* -Copyright © 2022 NAME HERE - -*/ -package main - -import "github.com/[[.Repo.Owner]]/[[.Repo.Name]]/cmd" - -func main() { - cmd.Execute() -} diff --git a/staging/dtm-repo-scaffolding-golang-gin/.gitignore b/staging/dtm-repo-scaffolding-golang-gin/.gitignore deleted file mode 100644 index 66fd13c90..000000000 --- a/staging/dtm-repo-scaffolding-golang-gin/.gitignore +++ /dev/null @@ -1,15 +0,0 @@ -# Binaries for programs and plugins -*.exe -*.exe~ -*.dll -*.so -*.dylib - -# Test binary, built with `go test -c` -*.test - -# Output of the go coverage tool, specifically when used with LiteIDE -*.out - -# Dependency directories (remove the comment below to include it) -# vendor/ diff --git a/staging/dtm-repo-scaffolding-golang-gin/Dockerfile.tpl b/staging/dtm-repo-scaffolding-golang-gin/Dockerfile.tpl deleted file mode 100644 index 9db616715..000000000 --- a/staging/dtm-repo-scaffolding-golang-gin/Dockerfile.tpl +++ /dev/null @@ -1,12 +0,0 @@ -FROM golang:alpine AS build-env -WORKDIR $GOPATH/src/github.com/[[.Repo.Owner]]/[[.Repo.Name]] -COPY . . -RUN apk add git -RUN go get ./... && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app cmd/[[.AppName]]/main.go - -FROM alpine -WORKDIR /app -COPY --from=build-env /go/src/github.com/[[.Repo.Owner]]/[[.Repo.Name]]/app /app/ -CMD ["./app"] -USER 1000 -EXPOSE 8080/tcp diff --git a/staging/dtm-repo-scaffolding-golang-gin/Makefile.tpl b/staging/dtm-repo-scaffolding-golang-gin/Makefile.tpl deleted file mode 100644 index 7cbb516c9..000000000 --- a/staging/dtm-repo-scaffolding-golang-gin/Makefile.tpl +++ /dev/null @@ -1,22 +0,0 @@ -SHELL := /bin/bash -BASEDIR = $(shell pwd) - -.PHONY: help -help: - @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' - -.PHONY: install -install: ## Install dependencies - @go mod download - @go mod vendor - -.PHONY: dev -dev: ## Run with Dev - @go run cmd/[[.AppName]]/main.go - -.PHONY: build -build: ## Build todomvc - @go build -o build/[[.AppName]] cmd/[[.AppName]]/main.go - -clean: ### Remove build dir - @rm -rf build diff --git a/staging/dtm-repo-scaffolding-golang-gin/README.md b/staging/dtm-repo-scaffolding-golang-gin/README.md deleted file mode 100644 index d52c8c3e3..000000000 --- a/staging/dtm-repo-scaffolding-golang-gin/README.md +++ /dev/null @@ -1,36 +0,0 @@ -# dtm-repo-scaffolding-golang-gin - -This repo contains templates used by DevStream plugin "repo-scaffolding" (thereafter: the plugin). - -This repo isn't intended to be used directly without DevStream. It should only be consumed by the plugin automatically. - -The plugin (together with this repo of templates) can create a repo in GitHub and set up the project layout and initialize the reop with necessary files that are typical for a Go web app. The followings can be created automatically: - -- a Go web app example (source code from [here](https://go.dev/doc/tutorial/web-service-gin)) with the [Gin Web Framework](https://github.com/gin-gonic/gin) -- directory structure, following [Standard Go Project Layout](https://github.com/golang-standards/project-layout) best practice -- `.gitignore`, generated by [Toptal | gitignore.io](https://www.toptal.com/developers/gitignore/api/go,vim,macos,visualstudiocode) with minimum changes -- Makefile, with install/dev/build/clean -- Dockerfile, with multistage build -- a simplified Helm chart with Deployment and Service - -## Usage - -- Render all files using go template whose name end with `.tpl` suffix. -- Files whose name don't end with `.tpl` extension don't need to be rendered. -- subdirectory "helm/**_app_name_**" (the **_app_name_** part) should be rendered with `AppName` -- subdicrectory "cmd/**_app_name_**" (the **_app_name_** part) should be rendered with `AppName` - -Example of required parameters to render these templates: - -```yaml -AppName: my-hello-world -Repo: - Owner: ironcore864 - Name: my-hello-world -ImageRepo: ironcore864/my-hello-world # dockerhub -``` - -## Where does this repo come from? - -`dtm-repo-scaffolding-golang-gin` is synced from https://github.com/devstream-io/devstream/blob/main/staging/dtm-repo-scaffolding-golang-gin. -Code changes are made in that location, merged into `devstream-io/devstream` and later synced here. diff --git a/staging/dtm-repo-scaffolding-golang-gin/README.md.tpl b/staging/dtm-repo-scaffolding-golang-gin/README.md.tpl deleted file mode 100644 index 1fc169e73..000000000 --- a/staging/dtm-repo-scaffolding-golang-gin/README.md.tpl +++ /dev/null @@ -1,11 +0,0 @@ -# [[.AppName]] - -This is a repo for app [[.AppName]]; bootstrapped by DevStream. - -By default, the automatically generated scaffolding contains: - -- a piece of sample go web app code using the [Gin Web Framework](https://github.com/gin-gonic/gin) -- .gitignore -- Makefile -- Dockerfile -- Helm chart diff --git a/staging/dtm-repo-scaffolding-golang-gin/cmd/_app_name_/main.go.tpl b/staging/dtm-repo-scaffolding-golang-gin/cmd/_app_name_/main.go.tpl deleted file mode 100644 index fffdc361d..000000000 --- a/staging/dtm-repo-scaffolding-golang-gin/cmd/_app_name_/main.go.tpl +++ /dev/null @@ -1,17 +0,0 @@ -package main - -import ( - "github.com/gin-gonic/gin" - - "github.com/[[.Repo.Owner]]/[[.Repo.Name]]/internal/pkg/album" -) - -func main() { - router := gin.Default() - - router.GET("/albums", album.GetAlbums) - router.GET("/albums/:id", album.GetAlbumByID) - router.POST("/albums", album.PostAlbums) - - router.Run("localhost:8080") -} diff --git a/staging/dtm-repo-scaffolding-golang-gin/go.mod.tpl b/staging/dtm-repo-scaffolding-golang-gin/go.mod.tpl deleted file mode 100644 index ffcea4bfa..000000000 --- a/staging/dtm-repo-scaffolding-golang-gin/go.mod.tpl +++ /dev/null @@ -1,22 +0,0 @@ -module github.com/[[.Repo.Owner]]/[[.Repo.Name]] - -go 1.17 - -require github.com/gin-gonic/gin v1.7.7 - -require ( - github.com/gin-contrib/sse v0.1.0 // indirect - github.com/go-playground/locales v0.13.0 // indirect - github.com/go-playground/universal-translator v0.17.0 // indirect - github.com/go-playground/validator/v10 v10.4.1 // indirect - github.com/golang/protobuf v1.3.3 // indirect - github.com/json-iterator/go v1.1.9 // indirect - github.com/leodido/go-urn v1.2.0 // indirect - github.com/mattn/go-isatty v0.0.12 // indirect - github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect - github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 // indirect - github.com/ugorji/go/codec v1.1.7 // indirect - golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect - golang.org/x/sys v0.0.0-20200116001909-b77594299b42 // indirect - gopkg.in/yaml.v2 v2.2.8 // indirect -) diff --git a/staging/dtm-repo-scaffolding-golang-gin/go.sum.tpl b/staging/dtm-repo-scaffolding-golang-gin/go.sum.tpl deleted file mode 100644 index 5ee9be125..000000000 --- a/staging/dtm-repo-scaffolding-golang-gin/go.sum.tpl +++ /dev/null @@ -1,54 +0,0 @@ -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= -github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs= -github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U= -github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= -github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= -github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= -github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= -github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= -github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= -github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= -github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= -github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= -github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= -github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/staging/dtm-repo-scaffolding-golang-gin/internal/pkg/album/album.go.tpl b/staging/dtm-repo-scaffolding-golang-gin/internal/pkg/album/album.go.tpl deleted file mode 100644 index acb7f2212..000000000 --- a/staging/dtm-repo-scaffolding-golang-gin/internal/pkg/album/album.go.tpl +++ /dev/null @@ -1,16 +0,0 @@ -package album - -// album represents data about a record album. -type album struct { - ID string `json:"id"` - Title string `json:"title"` - Artist string `json:"artist"` - Price float64 `json:"price"` -} - -// albums slice to seed record album data. -var albums = []album{ - {ID: "1", Title: "Blue Train", Artist: "John Coltrane", Price: 56.99}, - {ID: "2", Title: "Jeru", Artist: "Gerry Mulligan", Price: 17.99}, - {ID: "3", Title: "Sarah Vaughan and Clifford Brown", Artist: "Sarah Vaughan", Price: 39.99}, -} diff --git a/staging/dtm-repo-scaffolding-golang-gin/internal/pkg/album/get.go.tpl b/staging/dtm-repo-scaffolding-golang-gin/internal/pkg/album/get.go.tpl deleted file mode 100644 index a00e79d8c..000000000 --- a/staging/dtm-repo-scaffolding-golang-gin/internal/pkg/album/get.go.tpl +++ /dev/null @@ -1,28 +0,0 @@ -package album - -import ( - "net/http" - - "github.com/gin-gonic/gin" -) - -// GetAlbums responds with the list of all albums as JSON. -func GetAlbums(c *gin.Context) { - c.IndentedJSON(http.StatusOK, albums) -} - -// GetAlbumByID locates the album whose ID value matches the id -// parameter sent by the client, then returns that album as a response. -func GetAlbumByID(c *gin.Context) { - id := c.Param("id") - - // Loop through the list of albums, looking for - // an album whose ID value matches the parameter. - for _, a := range albums { - if a.ID == id { - c.IndentedJSON(http.StatusOK, a) - return - } - } - c.IndentedJSON(http.StatusNotFound, gin.H{"message": "album not found"}) -} diff --git a/staging/dtm-repo-scaffolding-golang-gin/internal/pkg/album/post.go.tpl b/staging/dtm-repo-scaffolding-golang-gin/internal/pkg/album/post.go.tpl deleted file mode 100644 index dbdddbfd4..000000000 --- a/staging/dtm-repo-scaffolding-golang-gin/internal/pkg/album/post.go.tpl +++ /dev/null @@ -1,22 +0,0 @@ -package album - -import ( - "net/http" - - "github.com/gin-gonic/gin" -) - -// PostAlbums adds an album from JSON received in the request body. -func PostAlbums(c *gin.Context) { - var newAlbum album - - // Call BindJSON to bind the received JSON to - // newAlbum. - if err := c.BindJSON(&newAlbum); err != nil { - return - } - - // Add the new album to the slice. - albums = append(albums, newAlbum) - c.IndentedJSON(http.StatusCreated, newAlbum) -} diff --git a/staging/dtm-repo-scaffolding-java-springboot/.gitignore b/staging/dtm-repo-scaffolding-java-springboot/.gitignore deleted file mode 100644 index 96d089c53..000000000 --- a/staging/dtm-repo-scaffolding-java-springboot/.gitignore +++ /dev/null @@ -1,24 +0,0 @@ -# Compiled class file -*.class - -# Log file -*.log - -# BlueJ files -*.ctxt - -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - -# Package Files # -*.jar -*.war -*.nar -*.ear -*.zip -*.tar.gz -*.rar - -# virtual machine crash logs, see https://www.java.com/en/download/help/error_hotspot.html -hs_err_pid* -replay_pid* diff --git a/staging/dtm-repo-scaffolding-java-springboot/Dockerfile b/staging/dtm-repo-scaffolding-java-springboot/Dockerfile deleted file mode 100644 index 98110c1eb..000000000 --- a/staging/dtm-repo-scaffolding-java-springboot/Dockerfile +++ /dev/null @@ -1,16 +0,0 @@ -FROM maven:3.8.6-openjdk-18 as build -WORKDIR /workspace/app - -COPY pom.xml . -COPY src src - -RUN mvn install -DskipTests -RUN mkdir -p target/dependency && (cd target/dependency; jar -xf ../*.jar) - -FROM openjdk:18.0.2.1 -VOLUME /tmp -ARG DEPENDENCY=/workspace/app/target/dependency -COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib -COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF -COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app -ENTRYPOINT ["java","-cp","app:app/lib/*","com.example.springboot.Application"] diff --git a/staging/dtm-repo-scaffolding-java-springboot/README.md b/staging/dtm-repo-scaffolding-java-springboot/README.md deleted file mode 100644 index 0f72c0992..000000000 --- a/staging/dtm-repo-scaffolding-java-springboot/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# dtm-repo-scaffolding-java-springboot - -This repo contains templates used by DevStream plugin "repo-scaffolding" (thereafter: the plugin). - -This repo isn't intended to be used directly without DevStream. It should only be consumed by the plugin automatically. - -The plugin (together with this repo of templates) can create a repo in GitHub and set up the project layout and initialize the reop with necessary files that are typical for a Spring web app. The followings can be created automatically: - -- a Java Spring app example -- directory structure -- Dockerfile, with multistage build - -## Where does this repo come from? - -`dtm-repo-scaffolding-java-springboot` is synced from https://github.com/devstream-io/devstream/blob/main/staging/dtm-repo-scaffolding-java-springboot. -Code changes are made in that location, merged into `devstream-io/devstream` and later synced here. diff --git a/staging/dtm-repo-scaffolding-java-springboot/README.md.tpl b/staging/dtm-repo-scaffolding-java-springboot/README.md.tpl deleted file mode 100644 index a9430bcc1..000000000 --- a/staging/dtm-repo-scaffolding-java-springboot/README.md.tpl +++ /dev/null @@ -1,9 +0,0 @@ -# [[.AppName]] - -This is a repo for app [[.AppName]]; bootstrapped by DevStream. - -By default, the automatically generated scaffolding contains: - -- a piece of sample java web app code using the [SpringBoot](https://spring.io/projects/spring-boot/) -- Dockerfile -- .gitignore diff --git a/staging/dtm-repo-scaffolding-java-springboot/build.gradle b/staging/dtm-repo-scaffolding-java-springboot/build.gradle deleted file mode 100644 index 4497674b0..000000000 --- a/staging/dtm-repo-scaffolding-java-springboot/build.gradle +++ /dev/null @@ -1,27 +0,0 @@ -plugins { - id 'org.springframework.boot' version '2.7.1' - id 'io.spring.dependency-management' version '1.0.11.RELEASE' - id 'java' -} - -group = 'com.example' -version = '0.0.1-SNAPSHOT' -sourceCompatibility = '1.8' - -repositories { - mavenCentral() -} - -dependencies { - // tag::actuator[] - implementation 'org.springframework.boot:spring-boot-starter-actuator' - // end::actuator[] - implementation 'org.springframework.boot:spring-boot-starter-web' - // tag::tests[] - testImplementation('org.springframework.boot:spring-boot-starter-test') - // end::tests[] -} - -test { - useJUnitPlatform() -} diff --git a/staging/dtm-repo-scaffolding-java-springboot/gradle/wrapper/gradle-wrapper.properties b/staging/dtm-repo-scaffolding-java-springboot/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index aa991fcea..000000000 --- a/staging/dtm-repo-scaffolding-java-springboot/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,5 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/staging/dtm-repo-scaffolding-java-springboot/gradlew b/staging/dtm-repo-scaffolding-java-springboot/gradlew deleted file mode 100755 index 1b6c78733..000000000 --- a/staging/dtm-repo-scaffolding-java-springboot/gradlew +++ /dev/null @@ -1,234 +0,0 @@ -#!/bin/sh - -# -# Copyright © 2015-2021 the original authors. -# -# 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 -# -# https://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. -# - -############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" -APP_BASE_NAME=${0##*/} - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/staging/dtm-repo-scaffolding-java-springboot/gradlew.bat b/staging/dtm-repo-scaffolding-java-springboot/gradlew.bat deleted file mode 100644 index 107acd32c..000000000 --- a/staging/dtm-repo-scaffolding-java-springboot/gradlew.bat +++ /dev/null @@ -1,89 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/staging/dtm-repo-scaffolding-java-springboot/mvnw b/staging/dtm-repo-scaffolding-java-springboot/mvnw deleted file mode 100755 index 9ee2a0c89..000000000 --- a/staging/dtm-repo-scaffolding-java-springboot/mvnw +++ /dev/null @@ -1,305 +0,0 @@ -#!/bin/sh -# ---------------------------------------------------------------------------- -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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 -# -# https://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. -# ---------------------------------------------------------------------------- - -# ---------------------------------------------------------------------------- -# Maven2 Start Up Batch script -# -# Required ENV vars: -# ------------------ -# JAVA_HOME - location of a JDK home dir -# -# Optional ENV vars -# ----------------- -# M2_HOME - location of maven2's installed home dir -# MAVEN_OPTS - parameters passed to the Java VM when running Maven -# e.g. to debug Maven itself, use -# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -# MAVEN_SKIP_RC - flag to disable loading of mavenrc files -# ---------------------------------------------------------------------------- - -if [ -z "$MAVEN_SKIP_RC" ] ; then - - if [ -f /etc/mavenrc ] ; then - . /etc/mavenrc - fi - - if [ -f "$HOME/.mavenrc" ] ; then - . "$HOME/.mavenrc" - fi - -fi - -# OS specific support. $var _must_ be set to either true or false. -cygwin=false; -darwin=false; -mingw=false -case "`uname`" in - CYGWIN*) cygwin=true ;; - MINGW*) mingw=true;; - Darwin*) darwin=true - # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home - # See https://developer.apple.com/library/mac/qa/qa1170/_index.html - if [ -z "$JAVA_HOME" ]; then - if [ -x "/usr/libexec/java_home" ]; then - export JAVA_HOME="`/usr/libexec/java_home`" - else - export JAVA_HOME="/Library/Java/Home" - fi - fi - ;; -esac - -if [ -z "$JAVA_HOME" ] ; then - if [ -r /etc/gentoo-release ] ; then - JAVA_HOME=`java-config --jre-home` - fi -fi - -if [ -z "$M2_HOME" ] ; then - ## resolve links - $0 may be a link to maven's home - PRG="$0" - - # need this for relative symlinks - while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG="`dirname "$PRG"`/$link" - fi - done - - saveddir=`pwd` - - M2_HOME=`dirname "$PRG"`/.. - - # make it fully qualified - M2_HOME=`cd "$M2_HOME" && pwd` - - cd "$saveddir" - # echo Using m2 at $M2_HOME -fi - -# For Cygwin, ensure paths are in UNIX format before anything is touched -if $cygwin ; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --unix "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --unix "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --unix "$CLASSPATH"` -fi - -# For Mingw, ensure paths are in UNIX format before anything is touched -if $mingw ; then - [ -n "$M2_HOME" ] && - M2_HOME="`(cd "$M2_HOME"; pwd)`" - [ -n "$JAVA_HOME" ] && - JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" -fi - -if [ -z "$JAVA_HOME" ]; then - javaExecutable="`which javac`" - if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then - # readlink(1) is not available as standard on Solaris 10. - readLink=`which readlink` - if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then - if $darwin ; then - javaHome="`dirname \"$javaExecutable\"`" - javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" - else - javaExecutable="`readlink -f \"$javaExecutable\"`" - fi - javaHome="`dirname \"$javaExecutable\"`" - javaHome=`expr "$javaHome" : '\(.*\)/bin'` - JAVA_HOME="$javaHome" - export JAVA_HOME - fi - fi -fi - -if [ -z "$JAVACMD" ] ; then - if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - else - JAVACMD="`which java`" - fi -fi - -if [ ! -x "$JAVACMD" ] ; then - echo "Error: JAVA_HOME is not defined correctly." >&2 - echo " We cannot execute $JAVACMD" >&2 - exit 1 -fi - -if [ -z "$JAVA_HOME" ] ; then - echo "Warning: JAVA_HOME environment variable is not set." -fi - -CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher - -# traverses directory structure from process work directory to filesystem root -# first directory with .mvn subdirectory is considered project base directory -find_maven_basedir() { - - if [ -z "$1" ] - then - echo "Path not specified to find_maven_basedir" - return 1 - fi - - basedir="$1" - wdir="$1" - while [ "$wdir" != '/' ] ; do - if [ -d "$wdir"/.mvn ] ; then - basedir=$wdir - break - fi - # workaround for JBEAP-8937 (on Solaris 10/Sparc) - if [ -d "${wdir}" ]; then - wdir=`cd "$wdir/.."; pwd` - fi - # end of workaround - done - echo "${basedir}" -} - -# concatenates all lines of a file -concat_lines() { - if [ -f "$1" ]; then - echo "$(tr -s '\n' ' ' < "$1")" - fi -} - -BASE_DIR=`find_maven_basedir "$(pwd)"` -if [ -z "$BASE_DIR" ]; then - exit 1; -fi - -########################################################################################## -# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -# This allows using the maven wrapper in projects that prohibit checking in binary data. -########################################################################################## -if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found .mvn/wrapper/maven-wrapper.jar" - fi -else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." - fi - if [ "$MVNW_REPOURL" = true]; then - jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.2/maven-wrapper-0.5.2.jar" - else - jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.2/maven-wrapper-0.5.2.jar" - fi - while IFS="=" read key value; do - case "$key" in (wrapperUrl) jarUrl="$value"; break ;; - esac - done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" - if [ "$MVNW_VERBOSE" = true ]; then - echo "Downloading from: $jarUrl" - fi - wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" - if $cygwin; then - wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` - fi - - if command -v wget > /dev/null; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found wget ... using wget" - fi - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - wget "$jarUrl" -O "$wrapperJarPath" - else - wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" - fi - elif command -v curl > /dev/null; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found curl ... using curl" - fi - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - curl -o "$wrapperJarPath" "$jarUrl" -f - else - curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f - fi - - else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Falling back to using Java to download" - fi - javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" - # For Cygwin, switch paths to Windows format before running javac - if $cygwin; then - javaClass=`cygpath --path --windows "$javaClass"` - fi - if [ -e "$javaClass" ]; then - if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Compiling MavenWrapperDownloader.java ..." - fi - # Compiling the Java class - ("$JAVA_HOME/bin/javac" "$javaClass") - fi - if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then - # Running the downloader - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Running MavenWrapperDownloader.java ..." - fi - ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") - fi - fi - fi -fi -########################################################################################## -# End of extension -########################################################################################## - -export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} -if [ "$MVNW_VERBOSE" = true ]; then - echo $MAVEN_PROJECTBASEDIR -fi -MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" - -# For Cygwin, switch paths to Windows format before running java -if $cygwin; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --path --windows "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --windows "$CLASSPATH"` - [ -n "$MAVEN_PROJECTBASEDIR" ] && - MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` -fi - -WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -exec "$JAVACMD" \ - $MAVEN_OPTS \ - -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ - "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ - ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/staging/dtm-repo-scaffolding-java-springboot/mvnw.cmd b/staging/dtm-repo-scaffolding-java-springboot/mvnw.cmd deleted file mode 100755 index 3a28d750c..000000000 --- a/staging/dtm-repo-scaffolding-java-springboot/mvnw.cmd +++ /dev/null @@ -1,172 +0,0 @@ -@REM ---------------------------------------------------------------------------- -@REM Licensed to the Apache Software Foundation (ASF) under one -@REM or more contributor license agreements. See the NOTICE file -@REM distributed with this work for additional information -@REM regarding copyright ownership. The ASF licenses this file -@REM to you under the Apache License, Version 2.0 (the -@REM "License"); you may not use this file except in compliance -@REM with the License. You may obtain a copy of the License at -@REM -@REM https://www.apache.org/licenses/LICENSE-2.0 -@REM -@REM Unless required by applicable law or agreed to in writing, -@REM software distributed under the License is distributed on an -@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -@REM KIND, either express or implied. See the License for the -@REM specific language governing permissions and limitations -@REM under the License. -@REM ---------------------------------------------------------------------------- - -@REM ---------------------------------------------------------------------------- -@REM Maven2 Start Up Batch script -@REM -@REM Required ENV vars: -@REM JAVA_HOME - location of a JDK home dir -@REM -@REM Optional ENV vars -@REM M2_HOME - location of maven2's installed home dir -@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands -@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending -@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven -@REM e.g. to debug Maven itself, use -@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files -@REM ---------------------------------------------------------------------------- - -@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' -@echo off -@REM set title of command window -title %0 -@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' -@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% - -@REM set %HOME% to equivalent of $HOME -if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") - -@REM Execute a user defined script before this one -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre -@REM check for pre script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" -if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" -:skipRcPre - -@setlocal - -set ERROR_CODE=0 - -@REM To isolate internal variables from possible post scripts, we use another setlocal -@setlocal - -@REM ==== START VALIDATION ==== -if not "%JAVA_HOME%" == "" goto OkJHome - -echo. -echo Error: JAVA_HOME not found in your environment. >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -:OkJHome -if exist "%JAVA_HOME%\bin\java.exe" goto init - -echo. -echo Error: JAVA_HOME is set to an invalid directory. >&2 -echo JAVA_HOME = "%JAVA_HOME%" >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -@REM ==== END VALIDATION ==== - -:init - -@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". -@REM Fallback to current working directory if not found. - -set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% -IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir - -set EXEC_DIR=%CD% -set WDIR=%EXEC_DIR% -:findBaseDir -IF EXIST "%WDIR%"\.mvn goto baseDirFound -cd .. -IF "%WDIR%"=="%CD%" goto baseDirNotFound -set WDIR=%CD% -goto findBaseDir - -:baseDirFound -set MAVEN_PROJECTBASEDIR=%WDIR% -cd "%EXEC_DIR%" -goto endDetectBaseDir - -:baseDirNotFound -set MAVEN_PROJECTBASEDIR=%EXEC_DIR% -cd "%EXEC_DIR%" - -:endDetectBaseDir - -IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig - -@setlocal EnableExtensions EnableDelayedExpansion -for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a -@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% - -:endReadAdditionalConfig - -SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" -set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" -set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.2/maven-wrapper-0.5.2.jar" - -FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( - IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B -) - -@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -@REM This allows using the maven wrapper in projects that prohibit checking in binary data. -if exist %WRAPPER_JAR% ( - echo Found %WRAPPER_JAR% -) else ( - if not "%MVNW_REPOURL%" == "" ( - SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.2/maven-wrapper-0.5.2.jar" - ) - echo Couldn't find %WRAPPER_JAR%, downloading it ... - echo Downloading from: %DOWNLOAD_URL% - - powershell -Command "&{"^ - "$webclient = new-object System.Net.WebClient;"^ - "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ - "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ - "}"^ - "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ - "}" - echo Finished downloading %WRAPPER_JAR% -) -@REM End of extension - -%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* -if ERRORLEVEL 1 goto error -goto end - -:error -set ERROR_CODE=1 - -:end -@endlocal & set ERROR_CODE=%ERROR_CODE% - -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost -@REM check for post script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" -if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" -:skipRcPost - -@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' -if "%MAVEN_BATCH_PAUSE%" == "on" pause - -if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% - -exit /B %ERROR_CODE% diff --git a/staging/dtm-repo-scaffolding-java-springboot/pom.xml b/staging/dtm-repo-scaffolding-java-springboot/pom.xml deleted file mode 100644 index 3f885c41e..000000000 --- a/staging/dtm-repo-scaffolding-java-springboot/pom.xml +++ /dev/null @@ -1,52 +0,0 @@ - - - 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 2.7.1 - - - com.example - spring-boot-complete - 0.0.1-SNAPSHOT - spring-boot-complete - Demo project for Spring Boot - - - 1.8 - - - - - org.springframework.boot - spring-boot-starter-web - - - - - org.springframework.boot - spring-boot-starter-actuator - - - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/staging/dtm-repo-scaffolding-java-springboot/settings.gradle b/staging/dtm-repo-scaffolding-java-springboot/settings.gradle deleted file mode 100644 index 19b520441..000000000 --- a/staging/dtm-repo-scaffolding-java-springboot/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = 'spring-boot' diff --git a/staging/dtm-repo-scaffolding-java-springboot/src/main/java/com/example/springboot/Application.java b/staging/dtm-repo-scaffolding-java-springboot/src/main/java/com/example/springboot/Application.java deleted file mode 100644 index 42b36896e..000000000 --- a/staging/dtm-repo-scaffolding-java-springboot/src/main/java/com/example/springboot/Application.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.example.springboot; - -import java.util.Arrays; - -import org.springframework.boot.CommandLineRunner; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; - -@SpringBootApplication -public class Application { - - public static void main(String[] args) { - SpringApplication.run(Application.class, args); - } - - @Bean - public CommandLineRunner commandLineRunner(ApplicationContext ctx) { - return args -> { - - System.out.println("Let's inspect the beans provided by Spring Boot:"); - - String[] beanNames = ctx.getBeanDefinitionNames(); - Arrays.sort(beanNames); - for (String beanName : beanNames) { - System.out.println(beanName); - } - - }; - } - -} diff --git a/staging/dtm-repo-scaffolding-java-springboot/src/main/java/com/example/springboot/HelloController.java b/staging/dtm-repo-scaffolding-java-springboot/src/main/java/com/example/springboot/HelloController.java deleted file mode 100644 index e4677a0c0..000000000 --- a/staging/dtm-repo-scaffolding-java-springboot/src/main/java/com/example/springboot/HelloController.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.example.springboot; - -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -public class HelloController { - - @GetMapping("/") - public String index() { - return "Greetings from Spring Boot!"; - } - -} diff --git a/staging/dtm-repo-scaffolding-java-springboot/src/test/java/com/example/springboot/HelloControllerIT.java b/staging/dtm-repo-scaffolding-java-springboot/src/test/java/com/example/springboot/HelloControllerIT.java deleted file mode 100644 index caee4766a..000000000 --- a/staging/dtm-repo-scaffolding-java-springboot/src/test/java/com/example/springboot/HelloControllerIT.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.example.springboot; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.http.ResponseEntity; - -import static org.assertj.core.api.Assertions.assertThat; - -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -public class HelloControllerIT { - - @Autowired - private TestRestTemplate template; - - @Test - public void getHello() throws Exception { - ResponseEntity response = template.getForEntity("/", String.class); - assertThat(response.getBody()).isEqualTo("Greetings from Spring Boot!"); - } -} diff --git a/staging/dtm-repo-scaffolding-java-springboot/src/test/java/com/example/springboot/HelloControllerTest.java b/staging/dtm-repo-scaffolding-java-springboot/src/test/java/com/example/springboot/HelloControllerTest.java deleted file mode 100644 index 81262f4c1..000000000 --- a/staging/dtm-repo-scaffolding-java-springboot/src/test/java/com/example/springboot/HelloControllerTest.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.example.springboot; - -import static org.hamcrest.Matchers.equalTo; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.MediaType; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; - -@SpringBootTest -@AutoConfigureMockMvc -public class HelloControllerTest { - - @Autowired - private MockMvc mvc; - - @Test - public void getHello() throws Exception { - mvc.perform(MockMvcRequestBuilders.get("/").accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(content().string(equalTo("Greetings from Spring Boot!"))); - } -} diff --git a/staging/dtm-repo-scaffolding-python-flask/.gitignore b/staging/dtm-repo-scaffolding-python-flask/.gitignore deleted file mode 100644 index 1d9734bd8..000000000 --- a/staging/dtm-repo-scaffolding-python-flask/.gitignore +++ /dev/null @@ -1,14 +0,0 @@ -.venv/ - -*.pyc -__pycache__/ - -instance/ - -.pytest_cache/ -.coverage -htmlcov/ - -dist/ -build/ -*.egg-info/ diff --git a/staging/dtm-repo-scaffolding-python-flask/Dockerfile.tpl b/staging/dtm-repo-scaffolding-python-flask/Dockerfile.tpl deleted file mode 100644 index cbc91717f..000000000 --- a/staging/dtm-repo-scaffolding-python-flask/Dockerfile.tpl +++ /dev/null @@ -1,14 +0,0 @@ -FROM python:3.10-alpine - -WORKDIR /code - -COPY ./requirements.txt /code/requirements.txt - -RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt - -COPY ./wsgi.py /code/ -COPY ./app /code/app - -USER 1000 - -CMD ["gunicorn", "--conf", "app/gunicorn.conf.py", "--bind", "0.0.0.0:8080", "wsgi:app"] diff --git a/staging/dtm-repo-scaffolding-python-flask/README.md b/staging/dtm-repo-scaffolding-python-flask/README.md deleted file mode 100644 index 5c0190958..000000000 --- a/staging/dtm-repo-scaffolding-python-flask/README.md +++ /dev/null @@ -1,35 +0,0 @@ -# dtm-repo-scaffolding-python-flask - -This repo contains templates used by DevStream plugin "repo-scaffolding" (thereafter: the plugin). - -This repo isn't intended to be used directly without DevStream. It should only be consumed by the plugin automatically. - -The plugin (together with this repo of templates) can create a repo in GitHub and set up the project layout and initialize the reop with necessary files that are typical for a Go web app. The followings can be created automatically: - -- a Python web app example using the Flask framework -- directory structure, following Flask/Python best practice -- `.gitignore`, suggested by Flask -- Dockerfile with Python alpline -- a simplified Helm chart with Deployment and Service - -## Usage - -- Render all files using go template whose name end with `.tpl` suffix. -- Files whose name don't end with `.tpl` extension don't need to be rendered. -- subdirectory "helm/**_app_name_**" (the **_app_name_** part) should be rendered with `AppName` -- subdicrectory "cmd/**_app_name_**" (the **_app_name_** part) should be rendered with `AppName` - -Example of required parameters to render these templates: - -```yaml -AppName: hello -Repo: - Owner: ironcore864 - Name: hello -imageRepo: ironcore864/hello # dockerhub -``` - -## Where does this repo come from? - -`dtm-repo-scaffolding-python-flask` is synced from https://github.com/devstream-io/devstream/blob/main/staging/dtm-repo-scaffolding-python-flask. -Code changes are made in that location, merged into `devstream-io/devstream` and later synced here. diff --git a/staging/dtm-repo-scaffolding-python-flask/README.md.tpl b/staging/dtm-repo-scaffolding-python-flask/README.md.tpl deleted file mode 100644 index f9345f737..000000000 --- a/staging/dtm-repo-scaffolding-python-flask/README.md.tpl +++ /dev/null @@ -1,19 +0,0 @@ -# [[.AppName]] - -This is a repo for app [[.AppName]]; bootstrapped by DevStream. - -By default, the automatically generated scaffolding contains: - -- a piece of sample Python [Flask](https://flask.palletsprojects.com/en/2.2.0/) web app -- sample unittest -- .gitignore -- requirements.txt -- wsgi.py -- Dockerfile -- Helm chart - -## Test - -```shell -python3 -m unittest -``` diff --git a/staging/dtm-repo-scaffolding-python-flask/app/_app_name_.py.tpl b/staging/dtm-repo-scaffolding-python-flask/app/_app_name_.py.tpl deleted file mode 100644 index 368408f45..000000000 --- a/staging/dtm-repo-scaffolding-python-flask/app/_app_name_.py.tpl +++ /dev/null @@ -1,12 +0,0 @@ -from flask import Flask - -app = Flask(__name__) - - -@app.route("/") -def hello(): - return "Hello, World!" - - -if __name__ == "__main__": - app.run(host='0.0.0.0') diff --git a/staging/dtm-repo-scaffolding-python-flask/app/gunicorn.conf.py.tpl b/staging/dtm-repo-scaffolding-python-flask/app/gunicorn.conf.py.tpl deleted file mode 100644 index cad345508..000000000 --- a/staging/dtm-repo-scaffolding-python-flask/app/gunicorn.conf.py.tpl +++ /dev/null @@ -1,9 +0,0 @@ -# Gunicorn config variables -loglevel = "info" -errorlog = "-" # stderr -accesslog = "-" # stdout -worker_tmp_dir = "/dev/shm" -graceful_timeout = 120 -timeout = 120 -keepalive = 5 -threads = 3 diff --git a/staging/dtm-repo-scaffolding-python-flask/requirements.txt.tpl b/staging/dtm-repo-scaffolding-python-flask/requirements.txt.tpl deleted file mode 100644 index 9cc47abb3..000000000 --- a/staging/dtm-repo-scaffolding-python-flask/requirements.txt.tpl +++ /dev/null @@ -1,2 +0,0 @@ -Flask==2.2.0 -gunicorn==20.1.0 diff --git a/staging/dtm-repo-scaffolding-python-flask/test/__init__.py b/staging/dtm-repo-scaffolding-python-flask/test/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/staging/dtm-repo-scaffolding-python-flask/test/__init__.py.tpl b/staging/dtm-repo-scaffolding-python-flask/test/__init__.py.tpl deleted file mode 100644 index 8b1378917..000000000 --- a/staging/dtm-repo-scaffolding-python-flask/test/__init__.py.tpl +++ /dev/null @@ -1 +0,0 @@ - diff --git a/staging/dtm-repo-scaffolding-python-flask/test/test__app_name_.py.tpl b/staging/dtm-repo-scaffolding-python-flask/test/test__app_name_.py.tpl deleted file mode 100644 index 090ac296b..000000000 --- a/staging/dtm-repo-scaffolding-python-flask/test/test__app_name_.py.tpl +++ /dev/null @@ -1,12 +0,0 @@ -import unittest - -from app.[[.AppName]] import hello - - -class TestHello(unittest.TestCase): - def test_hello(self): - self.assertEqual(hello(), "Hello, World!") - - -if __name__ == '__main__': - unittest.main() diff --git a/staging/dtm-repo-scaffolding-python-flask/wsgi.py.tpl b/staging/dtm-repo-scaffolding-python-flask/wsgi.py.tpl deleted file mode 100644 index 20aa74286..000000000 --- a/staging/dtm-repo-scaffolding-python-flask/wsgi.py.tpl +++ /dev/null @@ -1,4 +0,0 @@ -from app.[[.AppName]] import app - -if __name__ == "__main__": - app.run() diff --git a/test/e2e/yaml/e2e-apps.yaml b/test/e2e/yaml/e2e-apps.yaml deleted file mode 100644 index bc4be1cf9..000000000 --- a/test/e2e/yaml/e2e-apps.yaml +++ /dev/null @@ -1,35 +0,0 @@ -config: - state: - backend: local - options: - stateFile: devstream.state - -vars: - defaultBranch: main - repoOwner: devstream-io - repoName: dtm-e2e-go - imageRepoOwner: dtme2etest - -tools: -- name: helm-installer - instanceID: argocd - -apps: -- name: dtm-e2e-go - spec: - framework: gin - language: golang - repo: - org: [[ repoOwner ]] - scmType: github - token: [[ env GITHUB_TOKEN ]] - repoTemplate: - url: github.com/devstream-io/dtm-repo-scaffolding-golang-gin - ci: - - type: github-actions - options: - imageRepo: - user: [[ imageRepoOwner ]] - password: [[ env IMAGE_REPO_PASSWORD ]] - cd: - - type: argocdapp diff --git a/test/e2e/yaml/e2e-gitlabci-argocd.yaml b/test/e2e/yaml/e2e-gitlabci-argocd.yaml deleted file mode 100644 index 2fdf65672..000000000 --- a/test/e2e/yaml/e2e-gitlabci-argocd.yaml +++ /dev/null @@ -1,41 +0,0 @@ -config: - state: - backend: local - options: - stateFile: devstream-e2e-gitlabci-argocd.state - -vars: - appName: dtme2egitlabciargocd - gitlabURL: https://gitlab.com - gitlabUsername: aFlyBird - defaultBranch: main - dockerhubUser: dtme2etest - -tools: -- name: helm-installer - instanceID: argocd -apps: -- name: [[ appName ]] - spec: - language: python - framework: flask - repo: - url: [[ gitlabURL ]]/[[ gitlabUsername ]]/[[ appName ]].git - branch: [[ defaultBranch ]] - token: [[ env GITLAB_TOKEN ]] # use "GITLAB_TOKEN" env var - repoTemplate: - url: https://github.com/devstream-io/dtm-repo-scaffolding-python-flask.git - ci: - - type: template - templateName: ci-pipeline - cd: - - type: argocdapp -pipelineTemplates: -- name: ci-pipeline - type: gitlab-ci - options: - imageRepo: - user: [[ dockerhubUser ]] - password: [[ env DOCKERHUB_TOKEN]] - runner: - enable: true diff --git a/test/e2e/yaml/e2e-test-local.yaml b/test/e2e/yaml/e2e-test-local.yaml deleted file mode 100644 index 86c63ea9e..000000000 --- a/test/e2e/yaml/e2e-test-local.yaml +++ /dev/null @@ -1,65 +0,0 @@ -config: - state: - backend: local - options: - stateFile: devstream.state - -vars: - defaultBranch: main - githubUsername: GITHUBUSERNAME - repoName: dtm-e2e-test-golang - dockerRegistryUserName: DOCKERUSERNAME - argocdNameSpace: argocd - githubActionConfigLocation: https://raw.githubusercontent.com/devstream-io/ci-template/main/github-actions/workflows/main.yml - -tools: -- name: repo-scaffolding - instanceID: golang-github - options: - destinationRepo: - owner: [[ githubUsername ]] - name: [[ repoName ]] - branch: [[ defaultBranch ]] - scmType: github - token: [[ env GITHUB_TOKEN ]] - sourceRepo: - org: devstream-io - name: dtm-repo-scaffolding-golang-gin - scmType: github -- name: github-actions - instanceID: default - dependsOn: ["repo-scaffolding.golang-github"] - options: - pipeline: - configLocation: [[ githubActionConfigLocation ]] - imageRepo: - user: [[ dockerRegistryUserName ]] - password: [[ env IMAGE_REPO_PASSWORD ]] - language: - name: go - framework: gin - scm: - org: ${{repo-scaffolding.golang-github.outputs.owner}} - name: ${{repo-scaffolding.golang-github.outputs.repo}} - branch: [[ defaultBranch ]] - token: [[ env GITHUB_TOKEN ]] - scmType: github -- name: helm-installer - instanceID: argocd-001 -- name: argocdapp - instanceID: default - dependsOn: ["helm-installer.argocd-001", "repo-scaffolding.golang-github"] - options: - app: - name: ${{repo-scaffolding.golang-github.outputs.repo}} - namespace: [[ argocdNameSpace ]] - destination: - server: https://kubernetes.default.svc - namespace: default - source: - valuefile: values.yaml - path: helm/${{repo-scaffolding.golang-github.outputs.repo}} - repoURL: ${{repo-scaffolding.golang-github.outputs.repoURL}} - token: [[ env GITHUB_TOKEN ]] - imageRepo: - user: [[ dockerRegistryUserName ]] diff --git a/test/e2e/yaml/e2e-tools.yaml b/test/e2e/yaml/e2e-tools.yaml deleted file mode 100644 index d04855173..000000000 --- a/test/e2e/yaml/e2e-tools.yaml +++ /dev/null @@ -1,64 +0,0 @@ -config: - state: - backend: local - options: - stateFile: devstream.state - -vars: - defaultBranch: main - githubOrganization: devstream-io - repoName: dtme2epython - dockerRegistryUserName: dtme2etest - githubActionConfigLocation: https://raw.githubusercontent.com/devstream-io/dtm-pipeline-templates/main/github-actions/workflows/main.yml - -tools: -- name: repo-scaffolding - instanceID: python-github - options: - destinationRepo: - org: [[ githubOrganization ]] - name: [[ repoName ]] - branch: [[ defaultBranch ]] - token: [[ env GITHUB_TOKEN ]] - scmType: github - sourceRepo: - org: devstream-io - name: dtm-repo-scaffolding-python-flask - scmType: github -- name: github-actions - instanceID: default - dependsOn: ["repo-scaffolding.python-github"] - options: - pipeline: - configLocation: [[ githubActionConfigLocation ]] - imageRepo: - user: [[ dockerRegistryUserName ]] - password: [[ env IMAGE_REPO_PASSWORD ]] - language: - name: python - framework: flask - scm: - org: [[ githubOrganization ]] - token: [[ env GITHUB_TOKEN ]] - name: [[ repoName ]] - branch: [[ defaultBranch ]] - scmType: github -- name: helm-installer - instanceID: argocd -- name: argocdapp - instanceID: default - dependsOn: ["helm-installer.argocd", "repo-scaffolding.python-github"] - options: - app: - name: [[ repoName ]] - namespace: argocd - destination: - server: https://kubernetes.default.svc - namespace: default - source: - valuefile: values.yaml - path: helm/[[ repoName ]] - repoURL: ${{repo-scaffolding.python-github.outputs.repoURL}} - token: [[ env GITHUB_TOKEN ]] - imageRepo: - user: [[ dockerRegistryUserName ]] diff --git a/test/e2e/yaml/e2e-trello-config.yaml b/test/e2e/yaml/e2e-trello-config.yaml deleted file mode 100644 index a2603c722..000000000 --- a/test/e2e/yaml/e2e-trello-config.yaml +++ /dev/null @@ -1,25 +0,0 @@ -config: - state: - backend: local - options: - stateFile: devstream.state - -vars: - githubOrganization: devstream-io - repoName: dtm-e2e-go - kanbanBoardName: dtmKanbanTest - -tools: -- name: trello - instanceID: default - dependsOn: [ ] - options: - scm: - org: [[ githubOrganization ]] - name: [[ repoName ]] - scmType: github - token: [[ env GITHUB_TOKEN ]] - board: - name: [[ kanbanBoardName ]] - apikey: [[ env TRELLO_API_KEY ]] - token: [[ env TRELLO_TOKEN ]] diff --git a/test/state/drifted_test.go b/test/state/drifted_test.go deleted file mode 100644 index 3ebb1127c..000000000 --- a/test/state/drifted_test.go +++ /dev/null @@ -1,66 +0,0 @@ -package state_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/devstream-io/devstream/internal/pkg/configmanager" - "github.com/devstream-io/devstream/internal/pkg/plugin/installer/docker" - "github.com/devstream-io/devstream/internal/pkg/pluginengine" - "github.com/devstream-io/devstream/internal/pkg/statemanager" - dockerUtil "github.com/devstream-io/devstream/pkg/util/docker" -) - -var _ = Describe("ResourceDrifted func", func() { - It("should not be drifted", func() { - statusFromState := resourceStatusFromState() - statusFromRead, err := resourceStatusFromRead() - Expect(err).To(Succeed()) - - drifted, err := pluginengine.ResourceDrifted(statusFromState, statusFromRead) - - Expect(err).To(Succeed()) - Expect(drifted).To(Equal(false)) - }) -}) - -func resourceStatusFromState() map[string]interface{} { - configFile := "test_drifted.yaml" - cfg, err := configmanager.NewManager(configFile).LoadConfig() - if err != nil { - panic(err) - } - - smgr, err := statemanager.NewManager(*cfg.Config.State) - if err != nil { - panic(err) - } - - tool := cfg.Tools[0] - - state := smgr.GetState(statemanager.GenerateStateKeyByToolNameAndInstanceID(tool.Name, tool.InstanceID)) - - return state.ResourceStatus -} - -func resourceStatusFromRead() (map[string]interface{}, error) { - volumes := []string{ - "/srv/gitlab/config", - "/srv/gitlab/data", - "/srv/gitlab/logs", - } - - portPublishes := []dockerUtil.PortPublish{ - {HostPort: 2022, ContainerPort: 22}, - {HostPort: 8080, ContainerPort: 80}, - {HostPort: 443, ContainerPort: 443}, - } - resStatus := &docker.State{ - ContainerRunning: true, - Volumes: volumes, - Hostname: "gitlab.example.com", - PortPublishes: portPublishes, - } - - return resStatus.ToMap() -} diff --git a/test/state/state_suite_test.go b/test/state/state_suite_test.go deleted file mode 100644 index 4431b60a8..000000000 --- a/test/state/state_suite_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package state_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestState(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "State Suite") -} diff --git a/test/state/test_drifted.state b/test/state/test_drifted.state deleted file mode 100644 index be37f9621..000000000 --- a/test/state/test_drifted.state +++ /dev/null @@ -1,26 +0,0 @@ -gitlab-ce-docker_default: - name: gitlab-ce-docker - instanceID: default - dependsOn: [] - options: - gitlabHome: /srv/gitlab - hostname: gitlab.example.com - httpPort: 8080 - httpsPort: 443 - imageTag: rc - rmDataAfterDelete: false - sshPort: 2022 - resourceStatus: - ContainerRunning: true - Hostname: gitlab.example.com - PortPublishes: - - hostport: 2022 - containerport: 22 - - hostport: 8080 - containerport: 80 - - hostport: 443 - containerport: 443 - Volumes: - - /srv/gitlab/config - - /srv/gitlab/data - - /srv/gitlab/logs diff --git a/test/state/test_drifted.yaml b/test/state/test_drifted.yaml deleted file mode 100644 index 9ab369be0..000000000 --- a/test/state/test_drifted.yaml +++ /dev/null @@ -1,15 +0,0 @@ -config: - state: # state config, backend can be local or s3 - backend: local - options: - stateFile: test_drifted.state - -tools: -# name of the tool -- name: gitlab-ce-docker - # id of the tool instance - instanceID: default - # format: name.instanceID; If specified, dtm will make sure the dependency is applied first before handling this tool. - dependsOn: [ ] - # options for the plugin - options: