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.
Files changed (92) hide show
  1. gitops-1.6.0/.github/workflows/release-please.yaml +110 -0
  2. gitops-1.6.0/.github/workflows/test.yml +116 -0
  3. {gitops-1.5.1 → gitops-1.6.0}/.mise.toml +2 -2
  4. {gitops-1.5.1 → gitops-1.6.0}/.pre-commit-config.yaml +3 -3
  5. gitops-1.6.0/.release-please-manifest.json +5 -0
  6. {gitops-1.5.1 → gitops-1.6.0}/CHANGELOG.md +23 -0
  7. {gitops-1.5.1 → gitops-1.6.0}/PKG-INFO +1 -1
  8. {gitops-1.5.1 → gitops-1.6.0}/charts/gitops/CHANGELOG.md +7 -0
  9. {gitops-1.5.1 → gitops-1.6.0}/charts/gitops/Chart.yaml +1 -1
  10. {gitops-1.5.1 → gitops-1.6.0}/charts/gitops/templates/deployment.yml +27 -0
  11. gitops-1.6.0/docs/plans/2026-03-13-kms-encryption-design.md +176 -0
  12. {gitops-1.5.1 → gitops-1.6.0}/gitops/__init__.py +2 -2
  13. {gitops-1.5.1 → gitops-1.6.0}/gitops/common/app.py +31 -15
  14. {gitops-1.5.1 → gitops-1.6.0}/gitops/common/utils.py +4 -3
  15. {gitops-1.5.1 → gitops-1.6.0}/gitops/core.py +123 -43
  16. {gitops-1.5.1 → gitops-1.6.0}/gitops/shorthands.py +25 -13
  17. {gitops-1.5.1 → gitops-1.6.0}/gitops/utils/__init__.py +4 -3
  18. {gitops-1.5.1 → gitops-1.6.0}/gitops/utils/apps.py +4 -0
  19. {gitops-1.5.1 → gitops-1.6.0}/gitops/utils/async_runner.py +16 -6
  20. {gitops-1.5.1 → gitops-1.6.0}/gitops/utils/cli.py +1 -1
  21. {gitops-1.5.1 → gitops-1.6.0}/gitops/utils/images.py +3 -3
  22. {gitops-1.5.1 → gitops-1.6.0}/gitops/utils/kube.py +27 -12
  23. {gitops-1.5.1 → gitops-1.6.0}/gitops/utils/tags.py +5 -4
  24. {gitops-1.5.1 → gitops-1.6.0}/gitops/utils/yaml.py +9 -7
  25. gitops-1.6.0/gitops_server/app.py +38 -0
  26. {gitops-1.5.1 → gitops-1.6.0}/gitops_server/main.py +4 -15
  27. {gitops-1.5.1 → gitops-1.6.0}/gitops_server/settings.py +2 -0
  28. {gitops-1.5.1 → gitops-1.6.0}/gitops_server/types.py +1 -1
  29. {gitops-1.5.1 → gitops-1.6.0}/gitops_server/utils/__init__.py +1 -1
  30. {gitops-1.5.1 → gitops-1.6.0}/gitops_server/utils/git.py +1 -1
  31. {gitops-1.5.1 → gitops-1.6.0}/gitops_server/utils/github.py +3 -3
  32. {gitops-1.5.1 → gitops-1.6.0}/gitops_server/utils/slack.py +14 -3
  33. gitops-1.6.0/gitops_server/version.py +1 -0
  34. gitops-1.6.0/gitops_server/workers/__init__.py +3 -0
  35. gitops-1.6.0/gitops_server/workers/deployer/__init__.py +4 -0
  36. {gitops-1.5.1 → gitops-1.6.0}/gitops_server/workers/deployer/deploy.py +16 -5
  37. {gitops-1.5.1 → gitops-1.6.0}/gitops_server/workers/deployer/hooks.py +3 -3
  38. {gitops-1.5.1 → gitops-1.6.0}/pyproject.toml +4 -2
  39. {gitops-1.5.1 → gitops-1.6.0}/tests/test_deploy.py +35 -0
  40. {gitops-1.5.1 → gitops-1.6.0}/uv.lock +577 -495
  41. gitops-1.5.1/.github/workflows/release-please.yaml +0 -80
  42. gitops-1.5.1/.github/workflows/test.yml +0 -40
  43. gitops-1.5.1/.release-please-manifest.json +0 -5
  44. gitops-1.5.1/gitops_server/app.py +0 -18
  45. gitops-1.5.1/gitops_server/version.py +0 -1
  46. gitops-1.5.1/gitops_server/workers/__init__.py +0 -1
  47. gitops-1.5.1/gitops_server/workers/deployer/__init__.py +0 -2
  48. {gitops-1.5.1 → gitops-1.6.0}/.dockerignore +0 -0
  49. {gitops-1.5.1 → gitops-1.6.0}/.gitignore +0 -0
  50. {gitops-1.5.1 → gitops-1.6.0}/CLAUDE.md +0 -0
  51. {gitops-1.5.1 → gitops-1.6.0}/Dockerfile +0 -0
  52. {gitops-1.5.1 → gitops-1.6.0}/LICENSE +0 -0
  53. {gitops-1.5.1 → gitops-1.6.0}/README.md +0 -0
  54. {gitops-1.5.1 → gitops-1.6.0}/charts/gitops/.helmignore +0 -0
  55. {gitops-1.5.1 → gitops-1.6.0}/charts/gitops/README.md +0 -0
  56. {gitops-1.5.1 → gitops-1.6.0}/charts/gitops/templates/_helpers.tpl +0 -0
  57. {gitops-1.5.1 → gitops-1.6.0}/charts/gitops/templates/certificate.yml +0 -0
  58. {gitops-1.5.1 → gitops-1.6.0}/charts/gitops/templates/environment.yml +0 -0
  59. {gitops-1.5.1 → gitops-1.6.0}/charts/gitops/templates/ingress.yml +0 -0
  60. {gitops-1.5.1 → gitops-1.6.0}/charts/gitops/templates/issuer.yml +0 -0
  61. {gitops-1.5.1 → gitops-1.6.0}/charts/gitops/templates/pdb.yml +0 -0
  62. {gitops-1.5.1 → gitops-1.6.0}/charts/gitops/templates/secrets.yml +0 -0
  63. {gitops-1.5.1 → gitops-1.6.0}/charts/gitops/templates/service.yml +0 -0
  64. {gitops-1.5.1 → gitops-1.6.0}/charts/gitops/templates/serviceaccount.yaml +0 -0
  65. {gitops-1.5.1 → gitops-1.6.0}/charts/gitops/values.yaml +0 -0
  66. {gitops-1.5.1 → gitops-1.6.0}/docker-compose.yml +0 -0
  67. {gitops-1.5.1 → gitops-1.6.0}/gitops/common/__init__.py +0 -0
  68. {gitops-1.5.1 → gitops-1.6.0}/gitops/main.py +0 -0
  69. {gitops-1.5.1 → gitops-1.6.0}/gitops/py.typed +0 -0
  70. {gitops-1.5.1 → gitops-1.6.0}/gitops/settings.py +0 -0
  71. {gitops-1.5.1 → gitops-1.6.0}/gitops/utils/exceptions.py +0 -0
  72. {gitops-1.5.1 → gitops-1.6.0}/gitops_server/CHANGELOG.md +0 -0
  73. {gitops-1.5.1 → gitops-1.6.0}/gitops_server/README.md +0 -0
  74. {gitops-1.5.1 → gitops-1.6.0}/gitops_server/__init__.py +0 -0
  75. {gitops-1.5.1 → gitops-1.6.0}/gitops_server/workers/deployer/semaphore_manager.py +0 -0
  76. {gitops-1.5.1 → gitops-1.6.0}/gitops_server/workers/deployer/worker.py +0 -0
  77. {gitops-1.5.1 → gitops-1.6.0}/release-please-config.json +0 -0
  78. {gitops-1.5.1 → gitops-1.6.0}/release.py +0 -0
  79. {gitops-1.5.1 → gitops-1.6.0}/ruff.toml +0 -0
  80. {gitops-1.5.1 → gitops-1.6.0}/secrets.example.env +0 -0
  81. {gitops-1.5.1 → gitops-1.6.0}/setup.cfg +0 -0
  82. {gitops-1.5.1 → gitops-1.6.0}/tests/__init__.py +0 -0
  83. {gitops-1.5.1 → gitops-1.6.0}/tests/conftest.py +0 -0
  84. {gitops-1.5.1 → gitops-1.6.0}/tests/sample_data.py +0 -0
  85. {gitops-1.5.1 → gitops-1.6.0}/tests/test_app.py +0 -0
  86. {gitops-1.5.1 → gitops-1.6.0}/tests/test_core.py +0 -0
  87. {gitops-1.5.1 → gitops-1.6.0}/tests/test_deploy_concurrent.py +0 -0
  88. {gitops-1.5.1 → gitops-1.6.0}/tests/test_git.py +0 -0
  89. {gitops-1.5.1 → gitops-1.6.0}/tests/test_gitops_server.py +0 -0
  90. {gitops-1.5.1 → gitops-1.6.0}/tests/test_utils.py +0 -0
  91. {gitops-1.5.1 → gitops-1.6.0}/tests/utils.py +0 -0
  92. {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,6 +1,6 @@
1
1
  [tools]
2
- python = { version = "3.12.1", venv = ".venv" }
3
- uv = { version = "0.4.7"}
2
+ python = { version = "3.12.13", venv = ".venv" }
3
+ uv = { version = "0.9.9"}
4
4
 
5
5
  [env]
6
6
  _.python.venv = { path = "{{config_root}}/.venv", create = true }
@@ -1,16 +1,16 @@
1
1
  repos:
2
2
  - repo: https://github.com/charliermarsh/ruff-pre-commit
3
- rev: v0.6.3
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.6.3
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: v3.14.1
13
+ rev: 7179a42b3544e953e3683a670056ad385892365d # frozen: v4.13.8
14
14
  hooks:
15
15
  - id: commitizen
16
16
  stages:
@@ -0,0 +1,5 @@
1
+ {
2
+ ".": "1.6.0",
3
+ "gitops_server": "0.15.1",
4
+ "charts/gitops": "1.2.1"
5
+ }
@@ -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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gitops
3
- Version: 1.5.1
3
+ Version: 1.6.0
4
4
  Summary: Manage multiple apps across one or more k8s clusters.
5
5
  Author-email: Jarek Głowacki <jarekwg@gmail.com>
6
6
  License-File: LICENSE
@@ -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
 
@@ -2,4 +2,4 @@ apiVersion: v1
2
2
  appVersion: "1.0"
3
3
  description: GitOps Server Helm chart.
4
4
  name: gitops
5
- version: 1.2.0
5
+ version: 1.2.1
@@ -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.5.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 deployment_config["images"]["template"].format(
99
- account_id=self.account_id,
100
- tag=deployment_config["image-tag"],
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
- return deployment_config.get("image", "")
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
- return self.values.get("cluster", "")
145
+ cluster = self.values.get("cluster", "")
146
+ return str(cluster)
143
147
 
144
148
  @property
145
149
  def tags(self) -> list[str]:
146
- return self.values.get("tags", [])
150
+ tags = self.values.get("tags", [])
151
+ return list(tags)
147
152
 
148
153
  @property
149
154
  def service_account_name(self) -> str:
150
- return self.values.get("serviceAccount", {}).get("name") or self.values.get("serviceAccountName") or "default"
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
- return self.values.get("secrets", {})
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
- of
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")