gitops 1.5.1__tar.gz → 1.6.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gitops-1.6.0/.github/workflows/release-please.yaml +110 -0
- gitops-1.6.0/.github/workflows/test.yml +116 -0
- {gitops-1.5.1 → gitops-1.6.0}/.mise.toml +2 -2
- {gitops-1.5.1 → gitops-1.6.0}/.pre-commit-config.yaml +3 -3
- gitops-1.6.0/.release-please-manifest.json +5 -0
- {gitops-1.5.1 → gitops-1.6.0}/CHANGELOG.md +23 -0
- {gitops-1.5.1 → gitops-1.6.0}/PKG-INFO +1 -1
- {gitops-1.5.1 → gitops-1.6.0}/charts/gitops/CHANGELOG.md +7 -0
- {gitops-1.5.1 → gitops-1.6.0}/charts/gitops/Chart.yaml +1 -1
- {gitops-1.5.1 → gitops-1.6.0}/charts/gitops/templates/deployment.yml +27 -0
- gitops-1.6.0/docs/plans/2026-03-13-kms-encryption-design.md +176 -0
- {gitops-1.5.1 → gitops-1.6.0}/gitops/__init__.py +2 -2
- {gitops-1.5.1 → gitops-1.6.0}/gitops/common/app.py +31 -15
- {gitops-1.5.1 → gitops-1.6.0}/gitops/common/utils.py +4 -3
- {gitops-1.5.1 → gitops-1.6.0}/gitops/core.py +123 -43
- {gitops-1.5.1 → gitops-1.6.0}/gitops/shorthands.py +25 -13
- {gitops-1.5.1 → gitops-1.6.0}/gitops/utils/__init__.py +4 -3
- {gitops-1.5.1 → gitops-1.6.0}/gitops/utils/apps.py +4 -0
- {gitops-1.5.1 → gitops-1.6.0}/gitops/utils/async_runner.py +16 -6
- {gitops-1.5.1 → gitops-1.6.0}/gitops/utils/cli.py +1 -1
- {gitops-1.5.1 → gitops-1.6.0}/gitops/utils/images.py +3 -3
- {gitops-1.5.1 → gitops-1.6.0}/gitops/utils/kube.py +27 -12
- {gitops-1.5.1 → gitops-1.6.0}/gitops/utils/tags.py +5 -4
- {gitops-1.5.1 → gitops-1.6.0}/gitops/utils/yaml.py +9 -7
- gitops-1.6.0/gitops_server/app.py +38 -0
- {gitops-1.5.1 → gitops-1.6.0}/gitops_server/main.py +4 -15
- {gitops-1.5.1 → gitops-1.6.0}/gitops_server/settings.py +2 -0
- {gitops-1.5.1 → gitops-1.6.0}/gitops_server/types.py +1 -1
- {gitops-1.5.1 → gitops-1.6.0}/gitops_server/utils/__init__.py +1 -1
- {gitops-1.5.1 → gitops-1.6.0}/gitops_server/utils/git.py +1 -1
- {gitops-1.5.1 → gitops-1.6.0}/gitops_server/utils/github.py +3 -3
- {gitops-1.5.1 → gitops-1.6.0}/gitops_server/utils/slack.py +14 -3
- gitops-1.6.0/gitops_server/version.py +1 -0
- gitops-1.6.0/gitops_server/workers/__init__.py +3 -0
- gitops-1.6.0/gitops_server/workers/deployer/__init__.py +4 -0
- {gitops-1.5.1 → gitops-1.6.0}/gitops_server/workers/deployer/deploy.py +16 -5
- {gitops-1.5.1 → gitops-1.6.0}/gitops_server/workers/deployer/hooks.py +3 -3
- {gitops-1.5.1 → gitops-1.6.0}/pyproject.toml +4 -2
- {gitops-1.5.1 → gitops-1.6.0}/tests/test_deploy.py +35 -0
- {gitops-1.5.1 → gitops-1.6.0}/uv.lock +577 -495
- gitops-1.5.1/.github/workflows/release-please.yaml +0 -80
- gitops-1.5.1/.github/workflows/test.yml +0 -40
- gitops-1.5.1/.release-please-manifest.json +0 -5
- gitops-1.5.1/gitops_server/app.py +0 -18
- gitops-1.5.1/gitops_server/version.py +0 -1
- gitops-1.5.1/gitops_server/workers/__init__.py +0 -1
- gitops-1.5.1/gitops_server/workers/deployer/__init__.py +0 -2
- {gitops-1.5.1 → gitops-1.6.0}/.dockerignore +0 -0
- {gitops-1.5.1 → gitops-1.6.0}/.gitignore +0 -0
- {gitops-1.5.1 → gitops-1.6.0}/CLAUDE.md +0 -0
- {gitops-1.5.1 → gitops-1.6.0}/Dockerfile +0 -0
- {gitops-1.5.1 → gitops-1.6.0}/LICENSE +0 -0
- {gitops-1.5.1 → gitops-1.6.0}/README.md +0 -0
- {gitops-1.5.1 → gitops-1.6.0}/charts/gitops/.helmignore +0 -0
- {gitops-1.5.1 → gitops-1.6.0}/charts/gitops/README.md +0 -0
- {gitops-1.5.1 → gitops-1.6.0}/charts/gitops/templates/_helpers.tpl +0 -0
- {gitops-1.5.1 → gitops-1.6.0}/charts/gitops/templates/certificate.yml +0 -0
- {gitops-1.5.1 → gitops-1.6.0}/charts/gitops/templates/environment.yml +0 -0
- {gitops-1.5.1 → gitops-1.6.0}/charts/gitops/templates/ingress.yml +0 -0
- {gitops-1.5.1 → gitops-1.6.0}/charts/gitops/templates/issuer.yml +0 -0
- {gitops-1.5.1 → gitops-1.6.0}/charts/gitops/templates/pdb.yml +0 -0
- {gitops-1.5.1 → gitops-1.6.0}/charts/gitops/templates/secrets.yml +0 -0
- {gitops-1.5.1 → gitops-1.6.0}/charts/gitops/templates/service.yml +0 -0
- {gitops-1.5.1 → gitops-1.6.0}/charts/gitops/templates/serviceaccount.yaml +0 -0
- {gitops-1.5.1 → gitops-1.6.0}/charts/gitops/values.yaml +0 -0
- {gitops-1.5.1 → gitops-1.6.0}/docker-compose.yml +0 -0
- {gitops-1.5.1 → gitops-1.6.0}/gitops/common/__init__.py +0 -0
- {gitops-1.5.1 → gitops-1.6.0}/gitops/main.py +0 -0
- {gitops-1.5.1 → gitops-1.6.0}/gitops/py.typed +0 -0
- {gitops-1.5.1 → gitops-1.6.0}/gitops/settings.py +0 -0
- {gitops-1.5.1 → gitops-1.6.0}/gitops/utils/exceptions.py +0 -0
- {gitops-1.5.1 → gitops-1.6.0}/gitops_server/CHANGELOG.md +0 -0
- {gitops-1.5.1 → gitops-1.6.0}/gitops_server/README.md +0 -0
- {gitops-1.5.1 → gitops-1.6.0}/gitops_server/__init__.py +0 -0
- {gitops-1.5.1 → gitops-1.6.0}/gitops_server/workers/deployer/semaphore_manager.py +0 -0
- {gitops-1.5.1 → gitops-1.6.0}/gitops_server/workers/deployer/worker.py +0 -0
- {gitops-1.5.1 → gitops-1.6.0}/release-please-config.json +0 -0
- {gitops-1.5.1 → gitops-1.6.0}/release.py +0 -0
- {gitops-1.5.1 → gitops-1.6.0}/ruff.toml +0 -0
- {gitops-1.5.1 → gitops-1.6.0}/secrets.example.env +0 -0
- {gitops-1.5.1 → gitops-1.6.0}/setup.cfg +0 -0
- {gitops-1.5.1 → gitops-1.6.0}/tests/__init__.py +0 -0
- {gitops-1.5.1 → gitops-1.6.0}/tests/conftest.py +0 -0
- {gitops-1.5.1 → gitops-1.6.0}/tests/sample_data.py +0 -0
- {gitops-1.5.1 → gitops-1.6.0}/tests/test_app.py +0 -0
- {gitops-1.5.1 → gitops-1.6.0}/tests/test_core.py +0 -0
- {gitops-1.5.1 → gitops-1.6.0}/tests/test_deploy_concurrent.py +0 -0
- {gitops-1.5.1 → gitops-1.6.0}/tests/test_git.py +0 -0
- {gitops-1.5.1 → gitops-1.6.0}/tests/test_gitops_server.py +0 -0
- {gitops-1.5.1 → gitops-1.6.0}/tests/test_utils.py +0 -0
- {gitops-1.5.1 → gitops-1.6.0}/tests/utils.py +0 -0
- {gitops-1.5.1 → gitops-1.6.0}/tests/webhook_sample_data.py +0 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
name: Release Please
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches:
|
|
5
|
+
- develop
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
actions: read
|
|
9
|
+
contents: write
|
|
10
|
+
issues: write
|
|
11
|
+
pull-requests: write
|
|
12
|
+
id-token: write
|
|
13
|
+
|
|
14
|
+
env:
|
|
15
|
+
MISE_PYTHON_COMPILE: false
|
|
16
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
release-please:
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
outputs:
|
|
22
|
+
cli-release-created: ${{ steps.release.outputs['release_created'] }}
|
|
23
|
+
server-release-created: ${{ steps.release.outputs['release_created'] }}
|
|
24
|
+
helm-release-created: ${{ steps.release.outputs['charts/gitops--release_created'] }}
|
|
25
|
+
server-version: ${{ steps.release.outputs['tag_name'] }}
|
|
26
|
+
helm-version: ${{ steps.release.outputs['charts/gitops--tag_name'] }}
|
|
27
|
+
sha: ${{ steps.release.outputs.sha }}
|
|
28
|
+
steps:
|
|
29
|
+
- uses: googleapis/release-please-action@16a9c90856f42705d54a6fda1823352bdc62cf38 # ratchet:googleapis/release-please-action@v4
|
|
30
|
+
id: release
|
|
31
|
+
with: {}
|
|
32
|
+
|
|
33
|
+
publish_helm_chart:
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
needs: release-please
|
|
36
|
+
if: ${{ needs.release-please.outputs.helm-version }}
|
|
37
|
+
steps:
|
|
38
|
+
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # ratchet:actions/checkout@v6.0.1
|
|
39
|
+
- name: Publish Helm charts
|
|
40
|
+
uses: stefanprodan/helm-gh-pages@89c6698c192e70ed0e495bee7d3d1ca5b477fe82 # ratchet:stefanprodan/helm-gh-pages@master
|
|
41
|
+
with:
|
|
42
|
+
token: ${{ secrets.GITHUB_TOKEN }}
|
|
43
|
+
|
|
44
|
+
publish_to_pypi:
|
|
45
|
+
name: Publishes tag to pypi
|
|
46
|
+
runs-on: ubuntu-latest
|
|
47
|
+
needs: release-please
|
|
48
|
+
if: ${{ needs.release-please.outputs.cli-release-created }}
|
|
49
|
+
steps:
|
|
50
|
+
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # ratchet:actions/checkout@v6.0.1
|
|
51
|
+
|
|
52
|
+
- uses: jdx/mise-action@146a28175021df8ca24f8ee1828cc2a60f980bd5 # ratchet:jdx/mise-action@v3.5.1
|
|
53
|
+
with:
|
|
54
|
+
install: true
|
|
55
|
+
cache: true
|
|
56
|
+
experimental: true
|
|
57
|
+
|
|
58
|
+
- run: mise run build
|
|
59
|
+
shell: bash
|
|
60
|
+
|
|
61
|
+
- name: Publish Pypi Package
|
|
62
|
+
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # ratchet:pypa/gh-action-pypi-publish@release/v1
|
|
63
|
+
|
|
64
|
+
publish_docker_image:
|
|
65
|
+
name: Build and Push Docker Image
|
|
66
|
+
runs-on: ubuntu-24.04
|
|
67
|
+
needs: release-please
|
|
68
|
+
if: ${{ needs.release-please.outputs.server-release-created }}
|
|
69
|
+
steps:
|
|
70
|
+
- name: Checkout Code
|
|
71
|
+
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # ratchet:actions/checkout@v6.0.1
|
|
72
|
+
with:
|
|
73
|
+
fetch-depth: 2
|
|
74
|
+
|
|
75
|
+
- name: Set environment variables
|
|
76
|
+
run: |
|
|
77
|
+
echo "GIT_SHORT_HASH=$(echo ${{ github.sha }} | cut -c -7)" >> $GITHUB_ENV
|
|
78
|
+
|
|
79
|
+
- name: Decode cluster key
|
|
80
|
+
env:
|
|
81
|
+
SECRET_ENV: ${{ secrets.CLUSTER_KEY }}
|
|
82
|
+
run: echo $SECRET_ENV | base64 -d > cluster.key
|
|
83
|
+
|
|
84
|
+
- name: Configure AWS Credentials
|
|
85
|
+
uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 # ratchet:aws-actions/configure-aws-credentials@v5.1.1
|
|
86
|
+
with:
|
|
87
|
+
role-to-assume: arn:aws:iam::610829907584:role/default-github-actions-ci-role
|
|
88
|
+
role-session-name: ${{ github.repository_owner }}-${{ github.event.repository.name }}
|
|
89
|
+
aws-region: ap-southeast-2
|
|
90
|
+
|
|
91
|
+
- name: Set up Docker Buildx
|
|
92
|
+
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # ratchet:docker/setup-buildx-action@v3.12.0
|
|
93
|
+
|
|
94
|
+
- name: Login to Amazon ECR
|
|
95
|
+
uses: aws-actions/amazon-ecr-login@062b18b96a7aff071d4dc91bc00c4c1a7945b076 # ratchet:aws-actions/amazon-ecr-login@v2
|
|
96
|
+
|
|
97
|
+
- name: Build and push image
|
|
98
|
+
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # ratchet:docker/build-push-action@v6
|
|
99
|
+
with:
|
|
100
|
+
context: "."
|
|
101
|
+
file: Dockerfile
|
|
102
|
+
cache-from: type=gha
|
|
103
|
+
cache-to: type=gha, mode=max
|
|
104
|
+
provenance: false
|
|
105
|
+
platforms: linux/amd64,linux/arm64
|
|
106
|
+
push: true
|
|
107
|
+
tags: |
|
|
108
|
+
610829907584.dkr.ecr.ap-southeast-2.amazonaws.com/gitops:${{ env.GIT_SHORT_HASH }}
|
|
109
|
+
610829907584.dkr.ecr.ap-southeast-2.amazonaws.com/gitops:latest
|
|
110
|
+
610829907584.dkr.ecr.ap-southeast-2.amazonaws.com/gitops:${{ needs.release-please.outputs.server-version }}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
name: Test
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches:
|
|
5
|
+
- develop
|
|
6
|
+
pull_request: ~
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
actions: read
|
|
10
|
+
contents: write
|
|
11
|
+
issues: write
|
|
12
|
+
pull-requests: write
|
|
13
|
+
id-token: write
|
|
14
|
+
|
|
15
|
+
env:
|
|
16
|
+
PYTHONUNBUFFERED: 1
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
ci:
|
|
20
|
+
runs-on: ubuntu-24.04
|
|
21
|
+
steps:
|
|
22
|
+
- name: Checkout Code
|
|
23
|
+
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # ratchet:actions/checkout@v6.0.1
|
|
24
|
+
with:
|
|
25
|
+
fetch-depth: 2
|
|
26
|
+
|
|
27
|
+
- name: Install Python
|
|
28
|
+
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # ratchet:actions/setup-python@v6.1.0
|
|
29
|
+
with:
|
|
30
|
+
python-version: "3.12"
|
|
31
|
+
|
|
32
|
+
- name: Setup mise
|
|
33
|
+
uses: jdx/mise-action@146a28175021df8ca24f8ee1828cc2a60f980bd5 # ratchet:jdx/mise-action@v3.5.1
|
|
34
|
+
with:
|
|
35
|
+
install: true
|
|
36
|
+
cache: true
|
|
37
|
+
experimental: true
|
|
38
|
+
|
|
39
|
+
- name: Run CI
|
|
40
|
+
env:
|
|
41
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
42
|
+
run: |
|
|
43
|
+
mise run install
|
|
44
|
+
mise run ci
|
|
45
|
+
|
|
46
|
+
build:
|
|
47
|
+
name: Build and Push Docker Image
|
|
48
|
+
runs-on: ubuntu-24.04
|
|
49
|
+
steps:
|
|
50
|
+
- name: Checkout Code
|
|
51
|
+
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # ratchet:actions/checkout@v6.0.1
|
|
52
|
+
with:
|
|
53
|
+
fetch-depth: 2
|
|
54
|
+
|
|
55
|
+
- name: Set environment variables
|
|
56
|
+
run: |
|
|
57
|
+
if [[ ${{ github.event_name }} != 'pull_request' ]]; then
|
|
58
|
+
echo "GIT_SHORT_HASH=$(echo ${{ github.sha }} | cut -c -7)" >> $GITHUB_ENV
|
|
59
|
+
else
|
|
60
|
+
echo "GIT_SHORT_HASH=$(echo ${{ github.event.pull_request.head.sha }} | cut -c -7)" >> $GITHUB_ENV
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
- name: Decode cluster key
|
|
64
|
+
env:
|
|
65
|
+
SECRET_ENV: ${{ secrets.CLUSTER_KEY }}
|
|
66
|
+
run: echo $SECRET_ENV | base64 -d > cluster.key
|
|
67
|
+
|
|
68
|
+
- name: Configure AWS Credentials
|
|
69
|
+
uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 # ratchet:aws-actions/configure-aws-credentials@v5.1.1
|
|
70
|
+
with:
|
|
71
|
+
role-to-assume: arn:aws:iam::610829907584:role/default-github-actions-ci-role
|
|
72
|
+
role-session-name: ${{ github.repository_owner }}-${{ github.event.repository.name }}
|
|
73
|
+
aws-region: ap-southeast-2
|
|
74
|
+
|
|
75
|
+
- name: Set up Docker Buildx
|
|
76
|
+
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # ratchet:docker/setup-buildx-action@v3.12.0
|
|
77
|
+
|
|
78
|
+
- name: Login to Amazon ECR
|
|
79
|
+
uses: aws-actions/amazon-ecr-login@062b18b96a7aff071d4dc91bc00c4c1a7945b076 # ratchet:aws-actions/amazon-ecr-login@v2
|
|
80
|
+
|
|
81
|
+
- name: Build and push image
|
|
82
|
+
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # ratchet:docker/build-push-action@v6
|
|
83
|
+
with:
|
|
84
|
+
context: "."
|
|
85
|
+
file: Dockerfile
|
|
86
|
+
cache-from: type=gha
|
|
87
|
+
cache-to: type=gha, mode=max
|
|
88
|
+
provenance: false
|
|
89
|
+
platforms: linux/amd64,linux/arm64
|
|
90
|
+
push: true
|
|
91
|
+
tags: |
|
|
92
|
+
610829907584.dkr.ecr.ap-southeast-2.amazonaws.com/gitops:test-${{ env.GIT_SHORT_HASH }}
|
|
93
|
+
|
|
94
|
+
- name: Prepare Docker image comment
|
|
95
|
+
id: docker_comment
|
|
96
|
+
if: github.event_name == 'pull_request'
|
|
97
|
+
run: |
|
|
98
|
+
{
|
|
99
|
+
echo "comment<<EOF"
|
|
100
|
+
echo "## Docker Images"
|
|
101
|
+
echo ""
|
|
102
|
+
echo "**Commit:** \`${{ github.event.pull_request.head.sha }}\`"
|
|
103
|
+
echo ""
|
|
104
|
+
echo "| Tag |"
|
|
105
|
+
echo "| --- |"
|
|
106
|
+
echo "| \`610829907584.dkr.ecr.ap-southeast-2.amazonaws.com/gitops:test-${{ env.GIT_SHORT_HASH }}\` |"
|
|
107
|
+
echo "EOF"
|
|
108
|
+
} >> "$GITHUB_OUTPUT"
|
|
109
|
+
|
|
110
|
+
- name: Comment Docker images on PR
|
|
111
|
+
if: github.event_name == 'pull_request'
|
|
112
|
+
uses: marocchino/sticky-pull-request-comment@67d0dec7b07ed060a405f9b2a64b8ab319fdd7db # ratchet:marocchino/sticky-pull-request-comment@v2.9.2
|
|
113
|
+
with:
|
|
114
|
+
header: docker-image-tags
|
|
115
|
+
message: ${{ steps.docker_comment.outputs.comment }}
|
|
116
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
repos:
|
|
2
2
|
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
|
3
|
-
rev: v0.
|
|
3
|
+
rev: 0839f92796ae388643a08a21640a029b322be5c2 # frozen: v0.15.2
|
|
4
4
|
hooks:
|
|
5
5
|
- id: ruff
|
|
6
6
|
args:
|
|
7
7
|
- --fix
|
|
8
8
|
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
|
9
|
-
rev: v0.
|
|
9
|
+
rev: 0839f92796ae388643a08a21640a029b322be5c2 # frozen: v0.15.2
|
|
10
10
|
hooks:
|
|
11
11
|
- id: ruff-format
|
|
12
12
|
- repo: https://github.com/commitizen-tools/commitizen
|
|
13
|
-
rev:
|
|
13
|
+
rev: 7179a42b3544e953e3683a670056ad385892365d # frozen: v4.13.8
|
|
14
14
|
hooks:
|
|
15
15
|
- id: commitizen
|
|
16
16
|
stages:
|
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.6.0](https://github.com/uptick/gitops/compare/gitops-v1.5.1...gitops-v1.6.0) (2026-05-06)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* allow deploy alerts to go to a different channel ([78d9d1a](https://github.com/uptick/gitops/commit/78d9d1a9945933858dd3ef1a43c5a4042eb935a3))
|
|
9
|
+
* **cli:** add --limit flag to cap number of apps per command ([f85ce08](https://github.com/uptick/gitops/commit/f85ce081cb2732578a5d2fd764295f7447b56b9f))
|
|
10
|
+
* **cli:** add --limit flag to cap number of apps per command ([309ad2b](https://github.com/uptick/gitops/commit/309ad2bf3d45cbb73786f9285978fa00ac9d4605))
|
|
11
|
+
* **cli:** add dedicated redeploy command ([69d35ef](https://github.com/uptick/gitops/commit/69d35ef0b8699348449bb786a684ee7df600dd21))
|
|
12
|
+
* **deploy:** add subpath support for git-based helm charts ([4016ee0](https://github.com/uptick/gitops/commit/4016ee03424860ba72069b8be26309090c698dc9))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* add OTEL environment variables to helm chart and addDEFAULT_LOGGING_CONFIG_DICT ([fd07319](https://github.com/uptick/gitops/commit/fd07319a2160af58647556ef7ee377f1212be71d))
|
|
18
|
+
* **cli:** run git commit in apps directory for all commands ([6f02e00](https://github.com/uptick/gitops/commit/6f02e00ed11f337dc89c7d7aa0ee3fcd2e7816e6))
|
|
19
|
+
* **cli:** run git commit in apps directory for all commands ([c4b8b70](https://github.com/uptick/gitops/commit/c4b8b70f0681da31277152a060d818f370244e70))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Documentation
|
|
23
|
+
|
|
24
|
+
* add KMS encryption design for gitops ([bb46610](https://github.com/uptick/gitops/commit/bb466100e098f6747ee1f7675b8c4045c8ca0e8f))
|
|
25
|
+
|
|
3
26
|
## [1.5.1](https://github.com/uptick/gitops/compare/gitops-v1.5.0...gitops-v1.5.1) (2025-08-04)
|
|
4
27
|
|
|
5
28
|
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.2.1](https://github.com/uptick/gitops/compare/helm-v1.2.0...helm-v1.2.1) (2026-05-06)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* add OTEL environment variables to helm chart and addDEFAULT_LOGGING_CONFIG_DICT ([fd07319](https://github.com/uptick/gitops/commit/fd07319a2160af58647556ef7ee377f1212be71d))
|
|
9
|
+
|
|
3
10
|
## [1.2.0](https://github.com/uptick/gitops/compare/helm-v1.1.2...helm-v1.2.0) (2025-05-09)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -70,6 +70,33 @@ spec:
|
|
|
70
70
|
secretKeyRef:
|
|
71
71
|
name: {{ .Release.Name }}-secret
|
|
72
72
|
key: GITHUB_WEBHOOK_KEY
|
|
73
|
+
- name: NODE_IP
|
|
74
|
+
valueFrom:
|
|
75
|
+
fieldRef:
|
|
76
|
+
fieldPath: status.hostIP
|
|
77
|
+
- name: OTEL_EXPORTER_OTLP_ENDPOINT
|
|
78
|
+
value: "http://$(NODE_IP):4317"
|
|
79
|
+
- name: OTEL_PYTHON_LOG_CORRELATION
|
|
80
|
+
value: "True"
|
|
81
|
+
- name: OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED
|
|
82
|
+
value: "True"
|
|
83
|
+
- name: OTEL_LOGS_EXPORTER
|
|
84
|
+
value: "otlp"
|
|
85
|
+
- name: OTEL_METRICS_EXPORTER
|
|
86
|
+
value: "none"
|
|
87
|
+
- name: OTEL_TRACES_EXPORTER
|
|
88
|
+
value: "otlp"
|
|
89
|
+
- name: OTEL_SERVICE_NAME
|
|
90
|
+
value: "gitops"
|
|
91
|
+
- name: OTEL_EXPORTER_OTLP_TIMEOUT
|
|
92
|
+
value: "1"
|
|
93
|
+
- name: OTEL_RESOURCE_ATTRIBUTES:
|
|
94
|
+
value: "cluster={{.Values.cluster}}"
|
|
95
|
+
# We want to limit the length of the attributes so that we don't store too much
|
|
96
|
+
- name: OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT
|
|
97
|
+
value: "512"
|
|
98
|
+
- name: OTEL_PYTHON_EXCLUDED_URLS
|
|
99
|
+
value: "pingz,livez,readyz,metrics,robots.txt,favicon.ico"
|
|
73
100
|
volumeMounts:
|
|
74
101
|
- name: git-crypt-key
|
|
75
102
|
mountPath: "/etc/gitops"
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# KMS Encryption for GitOps
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Replace git-crypt with a SOPS-inspired envelope encryption system using AWS KMS. Files are encrypted inline (value-level) so YAML structure remains readable and diffable. A crypt daemon caches unwrapped DEKs in memory to minimise KMS calls.
|
|
6
|
+
|
|
7
|
+
## Phase 1: Manual CLI Commands
|
|
8
|
+
|
|
9
|
+
### Core Encryption Model
|
|
10
|
+
|
|
11
|
+
Envelope encryption with AES-256-GCM and KMS-wrapped DEKs.
|
|
12
|
+
|
|
13
|
+
Each encrypted file gets one DEK (Data Encryption Key). The DEK is generated via KMS `GenerateDataKey`, giving both plaintext and ciphertext (wrapped) forms. Individual YAML values are encrypted locally using AES-256-GCM with the plaintext DEK — each value gets a unique IV and auth tag.
|
|
14
|
+
|
|
15
|
+
The wrapped DEK is stored as a YAML comment header in the file, base64-encoded JSON containing one wrapped copy per KMS key in the rule set.
|
|
16
|
+
|
|
17
|
+
**Deterministic IV:** The IV for each value is derived via `HMAC-SHA256(DEK, plaintext)` truncated to 12 bytes. This means identical plaintext values encrypted with the same DEK produce identical ciphertext, enabling diff and duplicate detection. This is safe because the DEK is unique per file — the (key, IV) pair never repeats across different files.
|
|
18
|
+
|
|
19
|
+
### File Format
|
|
20
|
+
|
|
21
|
+
After encryption:
|
|
22
|
+
|
|
23
|
+
```yaml
|
|
24
|
+
# gitops-crypt: eyJ2ZXJzaW9uIjogMSwgImRla3MiOiB7InByb2R1Y3Rpb24iOiAiYmFzZTY0Li4uIiwgImNpIjogImJhc2U2NC4uLiJ9fQ==
|
|
25
|
+
secrets:
|
|
26
|
+
DB_PASSWORD: ENC[data:U2FsdGVk==,iv:AAAAAAAAAA==,tag:BBBBBBBBBB==]
|
|
27
|
+
API_KEY: ENC[data:Y2lwaGVy==,iv:CCCCCCCCCC==,tag:DDDDDDDDDD==]
|
|
28
|
+
NEW_SECRET: my-plaintext-value
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Header payload (base64-decoded):
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"version": 1,
|
|
36
|
+
"deks": {
|
|
37
|
+
"production": "base64-wrapped-dek...",
|
|
38
|
+
"ci": "base64-wrapped-dek..."
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
- `version` — format version for future changes
|
|
44
|
+
- `deks` — the same DEK wrapped by each KMS key, keyed by name from `.gitops.toml`
|
|
45
|
+
|
|
46
|
+
Encrypted value format:
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
ENC[data:<base64>,iv:<base64>,tag:<base64>]
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
- `data` — AES-256-GCM ciphertext
|
|
53
|
+
- `iv` — 12-byte deterministic IV (HMAC-derived)
|
|
54
|
+
- `tag` — 16-byte GCM authentication tag
|
|
55
|
+
|
|
56
|
+
### Configuration: `.gitops.toml`
|
|
57
|
+
|
|
58
|
+
```toml
|
|
59
|
+
[encryption]
|
|
60
|
+
daemon_idle_timeout = 3600 # seconds, default 1 hour
|
|
61
|
+
|
|
62
|
+
[encryption.keys]
|
|
63
|
+
production = "arn:aws:kms:ap-southeast-2:123456789:alias/gitops-prod"
|
|
64
|
+
ci = "arn:aws:kms:ap-southeast-2:123456789:alias/gitops-ci"
|
|
65
|
+
internal = "arn:aws:kms:ap-southeast-2:123456789:alias/gitops-internal"
|
|
66
|
+
|
|
67
|
+
[[encryption.rules]]
|
|
68
|
+
pattern = "apps/internal/*/secrets.yml"
|
|
69
|
+
keys = ["internal", "ci"]
|
|
70
|
+
|
|
71
|
+
[[encryption.rules]]
|
|
72
|
+
pattern = "apps/*/secrets.yml"
|
|
73
|
+
keys = ["production", "ci"]
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
- First matching rule wins (order matters — most specific first)
|
|
77
|
+
- `--keys` CLI flag overrides the rule's key list for that invocation
|
|
78
|
+
- Files not matching any rule are ignored
|
|
79
|
+
|
|
80
|
+
### CLI Interface
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# Encrypt all files matching rules in .gitops.toml
|
|
84
|
+
gitops encrypt
|
|
85
|
+
|
|
86
|
+
# Decrypt all files matching rules
|
|
87
|
+
gitops decrypt
|
|
88
|
+
|
|
89
|
+
# Operate on specific files
|
|
90
|
+
gitops encrypt apps/billing/secrets.yml
|
|
91
|
+
gitops decrypt apps/billing/secrets.yml
|
|
92
|
+
|
|
93
|
+
# Override which keys wrap the DEK
|
|
94
|
+
gitops encrypt apps/billing/secrets.yml --keys production,ci
|
|
95
|
+
|
|
96
|
+
# Re-wrap DEK with a different key set (values unchanged)
|
|
97
|
+
gitops encrypt apps/billing/secrets.yml --keys production,ci,new-team --rekey
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Behaviour:
|
|
101
|
+
- `encrypt` — encrypts plaintext values, skips already-encrypted ones (`ENC[...]` detection)
|
|
102
|
+
- `decrypt` — decrypts `ENC[...]` values to plaintext, skips already-plaintext ones
|
|
103
|
+
- `--rekey` — unwraps DEK with an existing key, re-wraps with the new key set. Values untouched.
|
|
104
|
+
- Both connect to the daemon for DEK caching, auto-starting it if needed
|
|
105
|
+
- Progress output consistent with existing CLI style
|
|
106
|
+
|
|
107
|
+
### Crypt Daemon
|
|
108
|
+
|
|
109
|
+
Auto-starts on first `encrypt`/`decrypt` call. Caches unwrapped DEKs in memory keyed by hash of wrapped DEK ciphertext.
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
┌─────────────┐ Unix Socket ┌──────────────────┐
|
|
113
|
+
│ gitops CLI │ <────────────────────> │ crypt-daemon │
|
|
114
|
+
│ │ request: wrapped DEK │ │
|
|
115
|
+
│ encrypt/ │ response: plain DEK │ In-memory cache │
|
|
116
|
+
│ decrypt │ │ {hash → DEK} │
|
|
117
|
+
└─────────────┘ │ │
|
|
118
|
+
│ KMS client │
|
|
119
|
+
└────────┬─────────┘
|
|
120
|
+
│ only on cache miss
|
|
121
|
+
v
|
|
122
|
+
┌──────────────────┐
|
|
123
|
+
│ AWS KMS │
|
|
124
|
+
└──────────────────┘
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
- Socket: `~/.gitops/crypt-daemon.sock`
|
|
128
|
+
- PID file: `~/.gitops/crypt-daemon.pid`
|
|
129
|
+
- Auto-starts when CLI can't connect to socket
|
|
130
|
+
- Auto-exits after configurable idle timeout (default 1 hour)
|
|
131
|
+
- DEKs only in memory, never written to disk
|
|
132
|
+
- Supports batch requests — CLI sends all unique wrapped DEKs in one call, daemon resolves cache hits instantly and makes concurrent KMS calls for misses
|
|
133
|
+
|
|
134
|
+
### Batch Pipeline
|
|
135
|
+
|
|
136
|
+
`gitops decrypt` (no args) flow:
|
|
137
|
+
|
|
138
|
+
1. Scan all files matching `.gitops.toml` rules
|
|
139
|
+
2. Parse headers, collect all unique wrapped DEKs
|
|
140
|
+
3. Send all unique wrapped DEKs to daemon in one batch request
|
|
141
|
+
4. Daemon returns all plaintext DEKs (cache hits instant, misses go to KMS concurrently)
|
|
142
|
+
5. Decrypt all values locally with concurrency (implementation TBD — profile to pick threads vs multiprocessing vs asyncio)
|
|
143
|
+
|
|
144
|
+
100 files sharing 3 unique DEKs = 3 KMS calls max on cold cache, 0 on warm.
|
|
145
|
+
|
|
146
|
+
### Server Integration
|
|
147
|
+
|
|
148
|
+
The gitops server (`gitops_server/`) runs continuously and decrypts on every webhook-triggered deploy. It uses the daemon for DEK caching — the daemon runs alongside the server process.
|
|
149
|
+
|
|
150
|
+
Post-clone flow:
|
|
151
|
+
1. Clone repo (as today)
|
|
152
|
+
2. Run `gitops decrypt` (replaces both `git-crypt unlock` and `helm-secrets` decryption)
|
|
153
|
+
3. Deploy from plaintext working tree
|
|
154
|
+
|
|
155
|
+
Helm-secrets is no longer needed — all decryption happens via `gitops decrypt` before helm runs.
|
|
156
|
+
|
|
157
|
+
### Migration from git-crypt
|
|
158
|
+
|
|
159
|
+
Incremental, file-by-file. Both systems coexist during migration:
|
|
160
|
+
|
|
161
|
+
1. Add `.gitops.toml` with encryption rules
|
|
162
|
+
2. For each file: `git-crypt unlock` → file is plaintext → `gitops encrypt` → commit
|
|
163
|
+
3. Update server's `clone_repo()` to run `gitops decrypt` after clone
|
|
164
|
+
4. Once all files migrated, remove `git-crypt` dependency and `GIT_CRYPT_KEY_FILE` config
|
|
165
|
+
|
|
166
|
+
## Phase 2: Git Filter Integration (Future)
|
|
167
|
+
|
|
168
|
+
Wire up `.gitattributes` for automatic encrypt-on-add / decrypt-on-checkout:
|
|
169
|
+
|
|
170
|
+
```
|
|
171
|
+
apps/*/secrets.yml filter=gitops-crypt diff=gitops-crypt
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Use git's **long-running filter process** protocol (`filter.gitops-crypt.process`) rather than per-file smudge/clean invocations. A single persistent process handles all files in a checkout via stdin/stdout, avoiding process startup overhead per file. The crypt daemon serves double duty — it acts as both the DEK cache and the long-running filter process.
|
|
175
|
+
|
|
176
|
+
The phase 1 design (`.gitops.toml` config, file format, daemon) is built to accommodate this without rework.
|
|
@@ -2,11 +2,11 @@ import os
|
|
|
2
2
|
import sys
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
|
|
5
|
-
from gitops.utils.apps import App, get_app_details, get_apps
|
|
5
|
+
from gitops.utils.apps import App, get_app_details, get_apps # type: ignore[attr-defined]
|
|
6
6
|
|
|
7
7
|
from .utils.cli import success, warning
|
|
8
8
|
|
|
9
|
-
__version__ = "1.
|
|
9
|
+
__version__ = "1.6.0"
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
# Checking gitops version matches cluster repo version.
|
|
@@ -21,8 +21,8 @@ class App:
|
|
|
21
21
|
self,
|
|
22
22
|
name: str,
|
|
23
23
|
path: str | None = None,
|
|
24
|
-
deployments: dict | None = None,
|
|
25
|
-
secrets: dict | None = None,
|
|
24
|
+
deployments: dict[str, Any] | None = None,
|
|
25
|
+
secrets: dict[str, str] | None = None,
|
|
26
26
|
load_secrets: bool = True,
|
|
27
27
|
encode_secrets: bool = True,
|
|
28
28
|
# deprecated
|
|
@@ -75,7 +75,7 @@ class App:
|
|
|
75
75
|
current_dict = current_dict.setdefault(key, {})
|
|
76
76
|
current_dict[keys[-1]] = value
|
|
77
77
|
|
|
78
|
-
def _make_values(self, deployments: dict, secrets: dict[str, str]) -> dict:
|
|
78
|
+
def _make_values(self, deployments: dict[str, Any], secrets: dict[str, str]) -> dict[str, Any]:
|
|
79
79
|
def encode(value: str) -> str:
|
|
80
80
|
return b64encode(str(value).encode()).decode() if self.encode_secrets else value
|
|
81
81
|
|
|
@@ -93,14 +93,17 @@ class App:
|
|
|
93
93
|
values.pop("images", None)
|
|
94
94
|
return values
|
|
95
95
|
|
|
96
|
-
def _make_image(self, deployment_config: dict) -> str:
|
|
96
|
+
def _make_image(self, deployment_config: dict[str, Any]) -> str:
|
|
97
97
|
if "image-tag" in deployment_config:
|
|
98
|
-
return
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
return str(
|
|
99
|
+
deployment_config["images"]["template"].format(
|
|
100
|
+
account_id=self.account_id,
|
|
101
|
+
tag=deployment_config["image-tag"],
|
|
102
|
+
)
|
|
101
103
|
)
|
|
102
104
|
else:
|
|
103
|
-
|
|
105
|
+
image = deployment_config.get("image", "")
|
|
106
|
+
return str(image) if image else ""
|
|
104
107
|
|
|
105
108
|
@property
|
|
106
109
|
def image(self) -> str:
|
|
@@ -109,7 +112,7 @@ class App:
|
|
|
109
112
|
if isinstance(image, dict):
|
|
110
113
|
return f"{image['repository']}:{image.get('tag', 'latest')}"
|
|
111
114
|
else:
|
|
112
|
-
return image
|
|
115
|
+
return str(image) if image else ""
|
|
113
116
|
|
|
114
117
|
@property
|
|
115
118
|
def image_repository_name(self) -> str:
|
|
@@ -139,20 +142,31 @@ class App:
|
|
|
139
142
|
|
|
140
143
|
@property
|
|
141
144
|
def cluster(self) -> str:
|
|
142
|
-
|
|
145
|
+
cluster = self.values.get("cluster", "")
|
|
146
|
+
return str(cluster)
|
|
143
147
|
|
|
144
148
|
@property
|
|
145
149
|
def tags(self) -> list[str]:
|
|
146
|
-
|
|
150
|
+
tags = self.values.get("tags", [])
|
|
151
|
+
return list(tags)
|
|
147
152
|
|
|
148
153
|
@property
|
|
149
154
|
def service_account_name(self) -> str:
|
|
150
|
-
|
|
155
|
+
service_account = self.values.get("serviceAccount", {})
|
|
156
|
+
if isinstance(service_account, dict):
|
|
157
|
+
name = service_account.get("name")
|
|
158
|
+
if name:
|
|
159
|
+
return str(name)
|
|
160
|
+
service_account_name = self.values.get("serviceAccountName")
|
|
161
|
+
if service_account_name:
|
|
162
|
+
return str(service_account_name)
|
|
163
|
+
return "default"
|
|
151
164
|
|
|
152
165
|
@property
|
|
153
166
|
def secrets(self) -> dict[str, str]:
|
|
154
167
|
# TODO: This should be a first class property
|
|
155
|
-
|
|
168
|
+
secrets = self.values.get("secrets", {})
|
|
169
|
+
return dict(secrets)
|
|
156
170
|
|
|
157
171
|
|
|
158
172
|
class Chart:
|
|
@@ -165,8 +179,9 @@ class Chart:
|
|
|
165
179
|
type: git
|
|
166
180
|
git_sha: develop
|
|
167
181
|
git_repo_url: https://github.com/uptick/workforce
|
|
182
|
+
path: charts/myapp # optional subpath within the repo
|
|
168
183
|
|
|
169
|
-
|
|
184
|
+
or
|
|
170
185
|
chart:
|
|
171
186
|
type: helm
|
|
172
187
|
helm_repo: brigade
|
|
@@ -177,12 +192,13 @@ class Chart:
|
|
|
177
192
|
chart: https://github.com/uptick/workforce
|
|
178
193
|
"""
|
|
179
194
|
|
|
180
|
-
def __init__(self, definition: dict | str):
|
|
195
|
+
def __init__(self, definition: dict[str, Any] | str):
|
|
181
196
|
if isinstance(definition, str):
|
|
182
197
|
# for backwards compat, any chart definition which is a string, is a git repo
|
|
183
198
|
self.type = "git"
|
|
184
199
|
self.git_sha = None
|
|
185
200
|
self.git_repo_url = definition or None
|
|
201
|
+
self.path = None
|
|
186
202
|
elif isinstance(definition, dict):
|
|
187
203
|
self.type = definition["type"]
|
|
188
204
|
self.git_sha = definition.get("git_sha")
|