gitops 0.13.2__tar.gz → 0.14.1__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 (86) hide show
  1. {gitops-0.13.2 → gitops-0.14.1}/.github/workflows/release-please.yaml +5 -6
  2. {gitops-0.13.2 → gitops-0.14.1}/.github/workflows/test.yml +3 -5
  3. gitops-0.14.1/.release-please-manifest.json +5 -0
  4. {gitops-0.13.2 → gitops-0.14.1}/CHANGELOG.md +15 -0
  5. {gitops-0.13.2 → gitops-0.14.1}/PKG-INFO +2 -1
  6. {gitops-0.13.2/charts/gitops → gitops-0.14.1}/charts/gitops/CHANGELOG.md +15 -0
  7. {gitops-0.13.2 → gitops-0.14.1}/charts/gitops/Chart.yaml +1 -1
  8. {gitops-0.13.2 → gitops-0.14.1}/charts/gitops/templates/deployment.yml +3 -7
  9. {gitops-0.13.2 → gitops-0.14.1}/charts/gitops/values.yaml +2 -1
  10. {gitops-0.13.2 → gitops-0.14.1}/gitops/__init__.py +1 -1
  11. {gitops-0.13.2 → gitops-0.14.1}/gitops/common/app.py +9 -6
  12. {gitops-0.13.2 → gitops-0.14.1}/gitops/utils/images.py +1 -1
  13. {gitops-0.13.2/gitops_server → gitops-0.14.1}/gitops_server/CHANGELOG.md +14 -0
  14. gitops-0.14.1/gitops_server/settings.py +15 -0
  15. {gitops-0.13.2 → gitops-0.14.1}/gitops_server/utils/slack.py +31 -20
  16. {gitops-0.13.2 → gitops-0.14.1}/gitops_server/workers/deployer/deploy.py +75 -58
  17. {gitops-0.13.2 → gitops-0.14.1}/pyproject.toml +2 -1
  18. {gitops-0.13.2 → gitops-0.14.1}/release-please-config.json +2 -2
  19. {gitops-0.13.2 → gitops-0.14.1}/secrets.example.env +1 -1
  20. {gitops-0.13.2 → gitops-0.14.1}/tests/test_deploy.py +1 -21
  21. {gitops-0.13.2 → gitops-0.14.1}/uv.lock +12 -1
  22. gitops-0.13.2/.github/workflows/publish.yml +0 -62
  23. gitops-0.13.2/.release-please-manifest.json +0 -5
  24. gitops-0.13.2/charts/gitops/CHANGELOG.md +0 -0
  25. gitops-0.13.2/gitops_server/CHANGELOG.md +0 -0
  26. gitops-0.13.2/gitops_server/settings.py +0 -7
  27. {gitops-0.13.2 → gitops-0.14.1}/.dockerignore +0 -0
  28. {gitops-0.13.2 → gitops-0.14.1}/.gitignore +0 -0
  29. {gitops-0.13.2 → gitops-0.14.1}/.mise.toml +0 -0
  30. {gitops-0.13.2 → gitops-0.14.1}/.pre-commit-config.yaml +0 -0
  31. {gitops-0.13.2 → gitops-0.14.1}/Dockerfile +0 -0
  32. {gitops-0.13.2 → gitops-0.14.1}/LICENSE +0 -0
  33. {gitops-0.13.2 → gitops-0.14.1}/README.md +0 -0
  34. {gitops-0.13.2 → gitops-0.14.1}/charts/gitops/.helmignore +0 -0
  35. {gitops-0.13.2 → gitops-0.14.1}/charts/gitops/README.md +0 -0
  36. {gitops-0.13.2 → gitops-0.14.1}/charts/gitops/templates/_helpers.tpl +0 -0
  37. {gitops-0.13.2 → gitops-0.14.1}/charts/gitops/templates/certificate.yml +0 -0
  38. {gitops-0.13.2 → gitops-0.14.1}/charts/gitops/templates/environment.yml +0 -0
  39. {gitops-0.13.2 → gitops-0.14.1}/charts/gitops/templates/ingress.yml +0 -0
  40. {gitops-0.13.2 → gitops-0.14.1}/charts/gitops/templates/issuer.yml +0 -0
  41. {gitops-0.13.2 → gitops-0.14.1}/charts/gitops/templates/secrets.yml +0 -0
  42. {gitops-0.13.2 → gitops-0.14.1}/charts/gitops/templates/service.yml +0 -0
  43. {gitops-0.13.2 → gitops-0.14.1}/charts/gitops/templates/serviceaccount.yaml +0 -0
  44. {gitops-0.13.2 → gitops-0.14.1}/docker-compose.yml +0 -0
  45. {gitops-0.13.2 → gitops-0.14.1}/gitops/common/__init__.py +0 -0
  46. {gitops-0.13.2 → gitops-0.14.1}/gitops/common/utils.py +0 -0
  47. {gitops-0.13.2 → gitops-0.14.1}/gitops/core.py +0 -0
  48. {gitops-0.13.2 → gitops-0.14.1}/gitops/main.py +0 -0
  49. {gitops-0.13.2 → gitops-0.14.1}/gitops/py.typed +0 -0
  50. {gitops-0.13.2 → gitops-0.14.1}/gitops/settings.py +0 -0
  51. {gitops-0.13.2 → gitops-0.14.1}/gitops/shorthands.py +0 -0
  52. {gitops-0.13.2 → gitops-0.14.1}/gitops/utils/__init__.py +0 -0
  53. {gitops-0.13.2 → gitops-0.14.1}/gitops/utils/apps.py +0 -0
  54. {gitops-0.13.2 → gitops-0.14.1}/gitops/utils/async_runner.py +0 -0
  55. {gitops-0.13.2 → gitops-0.14.1}/gitops/utils/cli.py +0 -0
  56. {gitops-0.13.2 → gitops-0.14.1}/gitops/utils/exceptions.py +0 -0
  57. {gitops-0.13.2 → gitops-0.14.1}/gitops/utils/kube.py +0 -0
  58. {gitops-0.13.2 → gitops-0.14.1}/gitops/utils/tags.py +0 -0
  59. {gitops-0.13.2 → gitops-0.14.1}/gitops/utils/yaml.py +0 -0
  60. {gitops-0.13.2 → gitops-0.14.1}/gitops_server/README.md +0 -0
  61. {gitops-0.13.2 → gitops-0.14.1}/gitops_server/__init__.py +0 -0
  62. {gitops-0.13.2 → gitops-0.14.1}/gitops_server/app.py +0 -0
  63. {gitops-0.13.2 → gitops-0.14.1}/gitops_server/main.py +0 -0
  64. {gitops-0.13.2 → gitops-0.14.1}/gitops_server/types.py +0 -0
  65. {gitops-0.13.2 → gitops-0.14.1}/gitops_server/utils/__init__.py +1 -1
  66. {gitops-0.13.2 → gitops-0.14.1}/gitops_server/utils/git.py +0 -0
  67. {gitops-0.13.2 → gitops-0.14.1}/gitops_server/utils/github.py +0 -0
  68. {gitops-0.13.2 → gitops-0.14.1}/gitops_server/workers/__init__.py +0 -0
  69. {gitops-0.13.2 → gitops-0.14.1}/gitops_server/workers/deployer/__init__.py +0 -0
  70. {gitops-0.13.2 → gitops-0.14.1}/gitops_server/workers/deployer/hooks.py +0 -0
  71. {gitops-0.13.2 → gitops-0.14.1}/gitops_server/workers/deployer/worker.py +0 -0
  72. {gitops-0.13.2 → gitops-0.14.1}/gitops_server/workers/status_updater/__init__.py +0 -0
  73. {gitops-0.13.2 → gitops-0.14.1}/gitops_server/workers/status_updater/worker.py +0 -0
  74. {gitops-0.13.2 → gitops-0.14.1}/release.py +0 -0
  75. {gitops-0.13.2 → gitops-0.14.1}/ruff.toml +0 -0
  76. {gitops-0.13.2 → gitops-0.14.1}/setup.cfg +0 -0
  77. {gitops-0.13.2 → gitops-0.14.1}/tests/__init__.py +0 -0
  78. {gitops-0.13.2 → gitops-0.14.1}/tests/conftest.py +0 -0
  79. {gitops-0.13.2 → gitops-0.14.1}/tests/sample_data.py +0 -0
  80. {gitops-0.13.2 → gitops-0.14.1}/tests/test_app.py +0 -0
  81. {gitops-0.13.2 → gitops-0.14.1}/tests/test_core.py +0 -0
  82. {gitops-0.13.2 → gitops-0.14.1}/tests/test_git.py +0 -0
  83. {gitops-0.13.2 → gitops-0.14.1}/tests/test_gitops_server.py +0 -0
  84. {gitops-0.13.2 → gitops-0.14.1}/tests/test_utils.py +0 -0
  85. {gitops-0.13.2 → gitops-0.14.1}/tests/utils.py +0 -0
  86. {gitops-0.13.2 → gitops-0.14.1}/tests/webhook_sample_data.py +0 -0
@@ -38,11 +38,10 @@ jobs:
38
38
  steps:
39
39
  - uses: actions/checkout@v4
40
40
  - name: Publish Helm charts
41
- uses: helm/chart-releaser-action@v1.6.0
41
+ uses: stefanprodan/helm-gh-pages@master
42
42
  with:
43
- charts_dir: charts/gitops
44
- env:
45
- CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
43
+ token: ${{ secrets.GITHUB_TOKEN }}
44
+
46
45
  publish_to_pypi:
47
46
  name: Publishes tag to pypi
48
47
  runs-on: ubuntu-latest
@@ -72,11 +71,11 @@ jobs:
72
71
  SECRET_ENV: "${{ secrets.CLUSTER_KEY }}"
73
72
  #https://github.com/uptick/actions/blob/main/.github/workflows/ci.yaml
74
73
  with:
75
- aws-iam-role-arn: "arn:aws:iam::305686791668:role/default-github-actions-ci-role"
74
+ aws-iam-role-arn: "arn:aws:iam::610829907584:role/default-github-actions-ci-role"
76
75
  docker-enabled: true
77
76
  docker-context: "."
78
77
  docker-tag: "${{ needs.release-please.outputs.server-version }}"
79
78
  docker-tag-latest: true
80
79
  docker-image-platforms: linux/amd64
81
- docker-repository: "305686791668.dkr.ecr.ap-southeast-2.amazonaws.com/gitops"
80
+ docker-repository: "610829907584.dkr.ecr.ap-southeast-2.amazonaws.com/gitops"
82
81
  command: echo $SECRET_ENV | base64 -d > cluster.key
@@ -2,7 +2,7 @@ name: Test
2
2
  on:
3
3
  push:
4
4
  branches:
5
- - develop
5
+ - develop
6
6
  pull_request: ~
7
7
 
8
8
  permissions:
@@ -31,13 +31,11 @@ jobs:
31
31
  SECRET_ENV: "${{ secrets.CLUSTER_KEY }}"
32
32
  #https://github.com/uptick/actions/blob/main/.github/workflows/ci.yaml
33
33
  with:
34
- aws-iam-role-arn: "arn:aws:iam::305686791668:role/default-github-actions-ci-role"
34
+ aws-iam-role-arn: "arn:aws:iam::610829907584:role/default-github-actions-ci-role"
35
35
  docker-enabled: true
36
36
  docker-context: "."
37
37
  docker-prefix: test
38
38
  docker-tag-latest: false
39
39
  docker-image-platforms: linux/amd64
40
- docker-push: false
41
- docker-repository: "305686791668.dkr.ecr.ap-southeast-2.amazonaws.com/gitops"
40
+ docker-repository: "610829907584.dkr.ecr.ap-southeast-2.amazonaws.com/gitops"
42
41
  command: echo $SECRET_ENV | base64 -d > cluster.key
43
-
@@ -0,0 +1,5 @@
1
+ {
2
+ ".": "0.14.1",
3
+ "gitops_server": "0.15.1",
4
+ "charts/gitops": "0.13.2"
5
+ }
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.14.1](https://github.com/uptick/gitops/compare/cli-v0.14.0...cli-v0.14.1) (2024-12-06)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * change charts directory for helm ci ([105357a](https://github.com/uptick/gitops/commit/105357a831f9efdf4f743fc5abc7937aa0d266f0))
9
+ * revert our helm chart publisher action ([3f558d8](https://github.com/uptick/gitops/commit/3f558d85d631e3384a2417a20fd32f25a94fc13e))
10
+
11
+ ## [0.14.0](https://github.com/uptick/gitops/compare/cli-v0.13.2...cli-v0.14.0) (2024-12-06)
12
+
13
+
14
+ ### Features
15
+
16
+ * **server:** add an updating emoji during deployments ([002d160](https://github.com/uptick/gitops/commit/002d160daa4a90ebf158cd3e0fc224cb3854baba))
17
+
3
18
  ## [0.13.2](https://github.com/uptick/gitops/compare/cli-v0.13.1...cli-v0.13.2) (2024-12-05)
4
19
 
5
20
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: gitops
3
- Version: 0.13.2
3
+ Version: 0.14.1
4
4
  Summary: Manage multiple apps across one or more k8s clusters.
5
5
  Author-email: Jarek Głowacki <jarekwg@gmail.com>
6
6
  Requires-Python: >=3.12
@@ -17,6 +17,7 @@ Requires-Dist: fastapi==0.109.2; extra == 'server'
17
17
  Requires-Dist: httpx<0.24.0,>=0.23.0; extra == 'server'
18
18
  Requires-Dist: kubernetes-asyncio>=25.1.2; extra == 'server'
19
19
  Requires-Dist: sentry-sdk>=1.3.0; extra == 'server'
20
+ Requires-Dist: slack-sdk>=3.33.5; extra == 'server'
20
21
  Requires-Dist: uptick-observability[fastapi]>=0.2.2; extra == 'server'
21
22
  Requires-Dist: uvicorn>=0.17.6; extra == 'server'
22
23
  Description-Content-Type: text/markdown
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.13.2](https://github.com/uptick/gitops/compare/helm-v0.13.1...helm-v0.13.2) (2024-12-06)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * change charts directory for helm ci ([105357a](https://github.com/uptick/gitops/commit/105357a831f9efdf4f743fc5abc7937aa0d266f0))
9
+ * revert our helm chart publisher action ([3f558d8](https://github.com/uptick/gitops/commit/3f558d85d631e3384a2417a20fd32f25a94fc13e))
10
+
11
+ ## [0.13.1](https://github.com/uptick/gitops/compare/helm-v0.13.0...helm-v0.13.1) (2024-12-06)
12
+
13
+
14
+ ### Bug Fixes
15
+
16
+ * add more memory to gitops ([3ca722f](https://github.com/uptick/gitops/commit/3ca722f1f1e03131979a4de66e17f23052a14c24))
17
+
3
18
  ## [0.13.0](https://github.com/uptick/gitops/compare/helm-v0.12.1...helm-v0.13.0) (2024-11-15)
4
19
 
5
20
 
@@ -2,4 +2,4 @@ apiVersion: v1
2
2
  appVersion: "1.0"
3
3
  description: GitOps Server Helm chart.
4
4
  name: gitops
5
- version: 0.13.0
5
+ version: 0.13.2
@@ -39,18 +39,14 @@ spec:
39
39
  - containerPort: 8000
40
40
  resources:
41
41
  requests:
42
- memory: "200Mi"
42
+ memory: "750Mi"
43
43
  limits:
44
- memory: "1000Mi"
44
+ memory: "1500Mi"
45
+
45
46
  envFrom:
46
47
  - configMapRef:
47
48
  name: {{ .Release.Name }}-environment
48
49
  env:
49
- - name: SLACK_URL
50
- valueFrom:
51
- secretKeyRef:
52
- name: {{ .Release.Name }}-secret
53
- key: SLACK_URL
54
50
  - name: SLACK_TOKEN
55
51
  valueFrom:
56
52
  secretKeyRef:
@@ -7,10 +7,11 @@ environment:
7
7
  CLUSTER_NAME: "test"
8
8
  # Specify the timezone for logging purposes. EG: Australia/Melbourne
9
9
  TZ: "UTC"
10
+ # The channel id to send slack messages to
11
+ SLACK_CHANNEL_ID: ""
10
12
 
11
13
  secrets:
12
14
  ACCOUNT_ID: ""
13
- SLACK_URL: ""
14
15
  SLACK_TOKEN: ""
15
16
  GITHUB_OAUTH_TOKEN: ""
16
17
  GITHUB_WEBHOOK_KEY: ""
@@ -6,7 +6,7 @@ from gitops.utils.apps import App, get_app_details, get_apps
6
6
 
7
7
  from .utils.cli import success, warning
8
8
 
9
- __version__ = "0.13.2"
9
+ __version__ = "0.14.1"
10
10
 
11
11
 
12
12
  # Checking gitops version matches cluster repo version.
@@ -79,7 +79,10 @@ class App:
79
79
  def encode(value: str) -> str:
80
80
  return b64encode(str(value).encode()).decode() if self.encode_secrets else value
81
81
 
82
- values = {**deployments, "secrets": {**{k: encode(v) for k, v in secrets.items()}}}
82
+ values = {
83
+ **deployments,
84
+ "secrets": {**{k: encode(v) for k, v in secrets.items()}},
85
+ }
83
86
 
84
87
  image = self._make_image(deployments)
85
88
  if image:
@@ -101,7 +104,7 @@ class App:
101
104
 
102
105
  @property
103
106
  def image(self) -> str:
104
- """[305686791668.dkr.ecr.ap-southeast-2.amazonaws.com/uptick:yoink-9f03ac80f3]"""
107
+ """[610829907584.dkr.ecr.ap-southeast-2.amazonaws.com/uptick:yoink-9f03ac80f3]"""
105
108
  image = self.values.get("image", "")
106
109
  if isinstance(image, dict):
107
110
  return f"{image['repository']}:{image.get('tag','latest')}"
@@ -110,23 +113,23 @@ class App:
110
113
 
111
114
  @property
112
115
  def image_repository_name(self) -> str:
113
- """305686791668.dkr.ecr.ap-southeast-2.amazonaws.com/[uptick]:yoink-9f03ac80f3"""
116
+ """610829907584.dkr.ecr.ap-southeast-2.amazonaws.com/[uptick]:yoink-9f03ac80f3"""
114
117
  return self.image.split(":")[0].split("/")[-1]
115
118
 
116
119
  @property
117
120
  def image_repository(self) -> str:
118
- """[305686791668.dkr.ecr.ap-southeast-2.amazonaws.com]/uptick:yoink-9f03ac80f3"""
121
+ """[610829907584.dkr.ecr.ap-southeast-2.amazonaws.com]/uptick:yoink-9f03ac80f3"""
119
122
  return self.image.split("/")[0]
120
123
 
121
124
  @property
122
125
  def image_tag(self) -> str:
123
- """305686791668.dkr.ecr.ap-southeast-2.amazonaws.com/uptick:[yoink-9f03ac80f3]"""
126
+ """610829907584.dkr.ecr.ap-southeast-2.amazonaws.com/uptick:[yoink-9f03ac80f3]"""
124
127
  return self.image.split(":")[-1]
125
128
 
126
129
  @property
127
130
  def image_prefix(self) -> str:
128
131
  """Gets the image prefix portion of {prefix}-{git hash}
129
- 305686791668.dkr.ecr.ap-southeast-2.amazonaws.com/uptick:[yoink]-9f03ac80f3
132
+ 610829907584.dkr.ecr.ap-southeast-2.amazonaws.com/uptick:[yoink]-9f03ac80f3
130
133
 
131
134
  eg: qa-server-12345 -> qa-server
132
135
  """
@@ -28,7 +28,7 @@ def get_image(tag: str) -> str:
28
28
  def get_latest_image(repository_name: str, prefix: str, ecr_repository: str | None = None) -> str | None: # noqa:C901
29
29
  """Finds latest image in ECR with the given prefix and returns the image tag
30
30
 
31
- param ecr_repository is expected in this format: 305686791668.dkr.ecr.ap-southeast-2.amazonaws.com
31
+ param ecr_repository is expected in this format: 610829907584.dkr.ecr.ap-southeast-2.amazonaws.com
32
32
  """
33
33
  describe_image_args = {}
34
34
  region_name = None
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.15.1](https://github.com/uptick/gitops/compare/server-v0.15.0...server-v0.15.1) (2024-12-06)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * deploy new gitops fix ([4994131](https://github.com/uptick/gitops/commit/49941316d85f320a77a3abf9f08970871f2fd3dc))
9
+
10
+ ## [0.15.0](https://github.com/uptick/gitops/compare/server-v0.14.0...server-v0.15.0) (2024-12-06)
11
+
12
+
13
+ ### Features
14
+
15
+ * **server:** add an updating emoji during deployments ([002d160](https://github.com/uptick/gitops/commit/002d160daa4a90ebf158cd3e0fc224cb3854baba))
16
+
3
17
  ## [0.14.0](https://github.com/uptick/gitops/compare/server-v0.13.0...server-v0.14.0) (2024-11-26)
4
18
 
5
19
 
@@ -0,0 +1,15 @@
1
+ import os
2
+
3
+ CLUSTER_NAME = os.getenv("CLUSTER_NAME", "")
4
+ # Namespace to search/deploy into
5
+ CLUSTER_NAMESPACE = os.getenv("CLUSTER_NAMESPACE", "")
6
+ ACCOUNT_ID = os.getenv("ACCOUNT_ID", "")
7
+ GITHUB_WEBHOOK_KEY = os.getenv("GITHUB_WEBHOOK_KEY", "")
8
+
9
+ SLACK_API_USERS_URL = os.environ.get("SLACK_API_USERS_URL", "https://slack.com/api/users.list")
10
+ SLACK_TOKEN = os.environ.get("SLACK_TOKEN")
11
+ # The slack channel ID starts with C
12
+ SLACK_CHANNEL_ID = os.environ.get("SLACK_CHANNEL_ID", "")
13
+
14
+ # How many parallel deploys can be done at once
15
+ GITOPS_MAX_PARALLEL_DEPLOYS = os.environ.get("GITOPS_MAX_PARALLEL_DEPLOYS", "5")
@@ -1,15 +1,14 @@
1
1
  import dataclasses
2
2
  import logging
3
- import os
4
3
  from collections.abc import Iterable
5
4
 
6
5
  import httpx
6
+ import slack_sdk
7
7
 
8
- logger = logging.getLogger("gitops")
8
+ from gitops_server import settings
9
9
 
10
- SLACK_URL = os.environ.get("SLACK_URL")
11
- SLACK_API_USERS_URL = os.environ.get("SLACK_API_USERS_URL", "https://slack.com/api/users.list")
12
- SLACK_TOKEN = os.environ.get("SLACK_TOKEN")
10
+ logger = logging.getLogger("gitops")
11
+ client = slack_sdk.WebClient(token=settings.SLACK_TOKEN)
13
12
 
14
13
 
15
14
  @dataclasses.dataclass
@@ -28,31 +27,43 @@ class SlackGroup(SlackUser):
28
27
  return f"<!subteam^{self.id}|{self.name}>"
29
28
 
30
29
 
31
- async def post(message: str) -> None:
32
- """Post a message to a slack channel
30
+ async def post(message: str) -> str | None:
31
+ """Post a message to a slack channel"""
32
+ logger.info("POSTING TO SLACK")
33
+ if settings.SLACK_TOKEN and settings.SLACK_CHANNEL_ID:
34
+ response = client.chat_postMessage(channel=settings.SLACK_CHANNEL_ID, text=message)
35
+ if response.status_code >= 300:
36
+ logger.warning("Failed to post a message to slack (see below):")
37
+ logger.error(f"{message}", exc_info=True)
38
+ else:
39
+ return response.data["ts"] # type: ignore
40
+ return None
41
+
33
42
 
34
- Uses the environment variable `SLACK_URL` to know which channel to post to.
35
- This URL is obtained by registering an integration with Slack.
43
+ async def update(*, ts: str, message: str) -> str | None:
44
+ """Update an existing message to a slack channel
45
+
46
+ https://api.slack.com/methods/chat.update
36
47
  """
37
- logger.info("POSTING TO SLACK")
38
- data = {"text": message}
39
- async with httpx.AsyncClient() as client:
40
- if SLACK_URL:
41
- response = await client.post(SLACK_URL, json=data)
42
- if response.status_code >= 300:
43
- logger.warning("Failed to post a message to slack (see below):")
44
- logger.error(f"{message}", exc_info=True)
48
+ if settings.SLACK_TOKEN and settings.SLACK_CHANNEL_ID:
49
+ response = client.chat_update(channel=settings.SLACK_CHANNEL_ID, ts=ts, text=message)
50
+ if response.status_code >= 300:
51
+ logger.warning("Failed to post a message to slack (see below):")
52
+ logger.error(f"{message}", exc_info=True)
53
+ else:
54
+ return response.data["ts"] # type: ignore
55
+ return None
45
56
 
46
57
 
47
58
  async def find_commiter_slack_user(name: str, email: str) -> SlackUser | None:
48
59
  """Find a slack user by name or email using the slack API"""
49
- if not SLACK_TOKEN:
60
+ if not settings.SLACK_TOKEN:
50
61
  return None
51
62
 
52
63
  async with httpx.AsyncClient() as client:
53
64
  response = await client.get(
54
- f"{SLACK_API_USERS_URL}?limit=5&pretty=1",
55
- headers={"Authorization": f"Bearer {SLACK_TOKEN}"},
65
+ f"{settings.SLACK_API_USERS_URL}?limit=5&pretty=1",
66
+ headers={"Authorization": f"Bearer {settings.SLACK_TOKEN}"},
56
67
  params={"name": name, "email": email},
57
68
  )
58
69
  data = response.json()
@@ -5,6 +5,7 @@ import os
5
5
  import tempfile
6
6
  import time
7
7
  import uuid
8
+ from typing import Any
8
9
 
9
10
  from opentelemetry import trace
10
11
 
@@ -21,38 +22,9 @@ tracer = trace.get_tracer(__name__)
21
22
  BASE_REPO_DIR = "/var/gitops/repos"
22
23
  ROLE_ARN = f"arn:aws:iam::{settings.ACCOUNT_ID}:role/GitopsAccess"
23
24
  logger = logging.getLogger("gitops")
24
- GITOPS_MAX_PARALLEL_DEPLOYS = os.environ.get("GITOPS_MAX_PARALLEL_DEPLOYS", "5")
25
25
  MAX_HELM_HISTORY = 3
26
26
 
27
27
 
28
- @tracer.start_as_current_span("post_init_summary")
29
- async def post_init_summary(source, username, added_apps, updated_apps, removed_apps, commit_message):
30
- deltas = ""
31
- for typ, d in [("Adding", added_apps), ("Updating", updated_apps), ("Removing", removed_apps)]:
32
- if d:
33
- deltas += f"\n\t• {typ}: {', '.join(f'`{app}`' for app in sorted(d))}"
34
- await slack.post(
35
- f"A deployment from `{source}` has been initiated by *{username}* for cluster"
36
- f" `{settings.CLUSTER_NAME}`, the following apps will be updated:{deltas}\nCommit Message:"
37
- f" {commit_message}"
38
- )
39
-
40
-
41
- @tracer.start_as_current_span("post_result")
42
- async def post_result(app: App, result: UpdateAppResult, deployer: "Deployer", **kwargs):
43
- if result["exit_code"] != 0:
44
- deploy_result = await handle_failed_deploy(app, result, deployer, **kwargs)
45
- message = (
46
- deploy_result["slack_message"]
47
- or f"Failed to deploy app `{result['app_name']}` for cluster `{settings.CLUSTER_NAME}`:\n>>>{result['output']}"
48
- )
49
-
50
- await slack.post(message)
51
-
52
- else:
53
- await handle_successful_deploy(app, result, deployer)
54
-
55
-
56
28
  @tracer.start_as_current_span("post_result_summary")
57
29
  async def post_result_summary(source: str, results: list[UpdateAppResult]):
58
30
  n_success = sum([r["exit_code"] == 0 for r in results])
@@ -91,7 +63,13 @@ class Deployer:
91
63
  self.skip_migrations = skip_migrations
92
64
  # Max parallel helm installs at a time
93
65
  # Kube api may rate limit otherwise
94
- self.semaphore = asyncio.Semaphore(int(GITOPS_MAX_PARALLEL_DEPLOYS))
66
+ self.semaphore = asyncio.Semaphore(int(settings.GITOPS_MAX_PARALLEL_DEPLOYS))
67
+
68
+ # Track which ones we've deployed and which ones failed
69
+ self.successful_apps: set[str] = set()
70
+ self.failed_apps: set[str] = set()
71
+
72
+ self.post_ts: str | None = None
95
73
 
96
74
  @classmethod
97
75
  async def from_push_event(cls, push_event):
@@ -116,35 +94,28 @@ class Deployer:
116
94
  )
117
95
 
118
96
  async def deploy(self):
119
- added_apps, updated_apps, removed_apps = self.calculate_app_deltas()
97
+ self.added_apps, self.updated_apps, self.removed_apps = self.calculate_app_deltas()
120
98
  current_span = trace.get_current_span()
121
99
  if current_span:
122
- current_span.set_attribute("gitops.added_apps", len(added_apps))
123
- current_span.set_attribute("gitops.updated_aps", len(updated_apps))
124
- current_span.set_attribute("gitops.removed_app", len(removed_apps))
125
- if not (added_apps | updated_apps | removed_apps):
100
+ current_span.set_attribute("gitops.added_apps", len(self.added_apps))
101
+ current_span.set_attribute("gitops.updated_aps", len(self.updated_apps))
102
+ current_span.set_attribute("gitops.removed_app", len(self.removed_apps))
103
+ if not (self.added_apps | self.updated_apps | self.removed_apps):
126
104
  logger.info("No deltas; aborting.")
127
105
  return
128
106
  logger.info(
129
- f"Running deployment for these deltas: A{list(added_apps)}, U{list(updated_apps)},"
130
- f" R{list(removed_apps)}"
131
- )
132
- await post_init_summary(
133
- source=self.current_app_definitions.name,
134
- username=self.author_name,
135
- added_apps=added_apps,
136
- updated_apps=updated_apps,
137
- removed_apps=removed_apps,
138
- commit_message=self.commit_message,
107
+ f"Running deployment for these deltas: A{list(self.added_apps)}, U{list(self.updated_apps)},"
108
+ f" R{list(self.removed_apps)}"
139
109
  )
110
+ await self.post_deploy_summary()
140
111
  update_results = await asyncio.gather(
141
112
  *[
142
113
  self.update_app_deployment(self.current_app_definitions.apps[app_name])
143
- for app_name in (added_apps | updated_apps)
114
+ for app_name in (self.added_apps | self.updated_apps)
144
115
  ]
145
116
  )
146
117
  uninstall_results = await asyncio.gather(
147
- *[self.uninstall_app(self.previous_app_definitions.apps[app_name]) for app_name in removed_apps]
118
+ *[self.uninstall_app(self.previous_app_definitions.apps[app_name]) for app_name in self.removed_apps]
148
119
  )
149
120
  await post_result_summary(self.current_app_definitions.name, update_results + uninstall_results)
150
121
 
@@ -155,7 +126,7 @@ class Deployer:
155
126
  result = await run(f"helm uninstall {app.name} -n {app.namespace}", suppress_errors=True)
156
127
  if result:
157
128
  update_result = UpdateAppResult(app_name=app.name, slack_message="", **result)
158
- await post_result(
129
+ await self.post_result(
159
130
  app=app,
160
131
  result=update_result,
161
132
  deployer=self,
@@ -163,13 +134,12 @@ class Deployer:
163
134
  return update_result
164
135
 
165
136
  async def update_app_deployment(self, app: App) -> UpdateAppResult | None:
166
- with tracer.start_as_current_span("update_app_deployment", attributes={"app": app.name}) as span:
167
- app.set_value("deployment.labels.gitops/deploy_id", self.deploy_id)
168
- app.set_value("deployment.labels.gitops/status", github.STATUSES.in_progress)
169
- if github_deployment_url := app.values.get("github/deployment_url"):
170
- app.set_value("deployment.annotations.github/deployment_url", github_deployment_url)
171
-
172
- async with self.semaphore:
137
+ app.set_value("deployment.labels.gitops/deploy_id", self.deploy_id)
138
+ app.set_value("deployment.labels.gitops/status", github.STATUSES.in_progress)
139
+ if github_deployment_url := app.values.get("github/deployment_url"):
140
+ app.set_value("deployment.annotations.github/deployment_url", github_deployment_url)
141
+ async with self.semaphore:
142
+ with tracer.start_as_current_span("update_app_deployment", attributes={"app": app.name}) as span:
173
143
  logger.info(f"Deploying app {app.name!r}.")
174
144
  from_timestamp = time.time()
175
145
  if app.chart.type == "git":
@@ -226,10 +196,10 @@ class Deployer:
226
196
 
227
197
  update_result = UpdateAppResult(app_name=app.name, slack_message="", **result)
228
198
 
229
- await post_result(app=app, result=update_result, deployer=self, from_timestamp=from_timestamp)
199
+ await self.post_result(app=app, result=update_result, deployer=self, from_timestamp=from_timestamp)
230
200
  return update_result
231
201
 
232
- def calculate_app_deltas(self):
202
+ def calculate_app_deltas(self) -> tuple[set[str], set[str], set[str]]:
233
203
  cur = self.current_app_definitions.apps.keys()
234
204
  prev = self.previous_app_definitions.apps.keys()
235
205
 
@@ -237,7 +207,7 @@ class Deployer:
237
207
  common = cur & prev
238
208
  removed = prev - cur
239
209
 
240
- updated = set()
210
+ updated: set[str] = set()
241
211
  for app_name in common:
242
212
  cur_app = self.current_app_definitions.apps[app_name]
243
213
  prev_app = self.previous_app_definitions.apps[app_name]
@@ -247,3 +217,50 @@ class Deployer:
247
217
  continue
248
218
  updated.add(app_name)
249
219
  return added, updated, removed
220
+
221
+ @tracer.start_as_current_span("post_result")
222
+ async def post_result(self, app: App, result: UpdateAppResult, deployer: "Deployer", **kwargs: Any) -> None:
223
+ if result["exit_code"] != 0:
224
+ self.failed_apps.add(app.name)
225
+ deploy_result = await handle_failed_deploy(app, result, deployer, **kwargs)
226
+ message = (
227
+ deploy_result["slack_message"]
228
+ or f"Failed to deploy app `{result['app_name']}` for cluster `{settings.CLUSTER_NAME}`:\n>>>{result['output']}"
229
+ )
230
+ await slack.post(message)
231
+ else:
232
+ self.successful_apps.add(app.name)
233
+ await handle_successful_deploy(app, result, deployer)
234
+
235
+ # TODO: Fix this in the future. We want to update the message with the latest status.
236
+ # But we can't because of the messge length (when messaging > 4000 characters)
237
+ # We need to figure out a better way to update the current status (especially when it is split over multiple messages)
238
+ # Maybe a grafana dashboard? Or maybe a count / eta...
239
+
240
+ # await self.post_deploy_summary()
241
+
242
+ @tracer.start_as_current_span("post_init_summary")
243
+ async def post_deploy_summary(self) -> None:
244
+ def get_app_formatted(app: str) -> str:
245
+ if app in self.successful_apps:
246
+ return f":tick: `{app}`"
247
+ elif app in self.failed_apps:
248
+ return f":x: `{app}`"
249
+ else:
250
+ return f"`{app}`"
251
+
252
+ deltas = ""
253
+ for typ, d in [("Adding", self.added_apps), ("Updating", self.updated_apps), ("Removing", self.removed_apps)]:
254
+ if d:
255
+ deltas += f"\n\t• {typ}: {', '.join(get_app_formatted(app) for app in sorted(d))}"
256
+
257
+ message = (
258
+ f"A deployment from `{self.current_app_definitions.name}` has been initiated by *{self.author_email}* for cluster"
259
+ f" `{settings.CLUSTER_NAME}`, the following apps will be updated:{deltas}\nCommit Message:"
260
+ f" {self.commit_message}"
261
+ )
262
+ # We have a problem here; we can't update a message if it is > 4000 characters long.
263
+ if not self.post_ts:
264
+ self.post_ts = await slack.post(message=message)
265
+ else:
266
+ await slack.update(ts=self.post_ts, message=message)
@@ -1,7 +1,7 @@
1
1
 
2
2
  [project]
3
3
  name = "gitops"
4
- version = "0.13.2"
4
+ version = "0.14.1"
5
5
  description = "Manage multiple apps across one or more k8s clusters."
6
6
  requires-python = ">=3.12"
7
7
  readme = "README.md"
@@ -29,6 +29,7 @@ server = [
29
29
  "kubernetes_asyncio>=25.1.2",
30
30
  "sentry-sdk>=1.3.0",
31
31
  "uptick-observability[fastapi]>=0.2.2",
32
+ "slack-sdk>=3.33.5",
32
33
  ]
33
34
 
34
35
  [build-system]
@@ -13,7 +13,7 @@
13
13
  "gitops_server": {
14
14
  "package-name": "server",
15
15
  "release-type": "python",
16
- "changelog-path": "gitops_server/CHANGELOG.md",
16
+ "changelog-path": "CHANGELOG.md",
17
17
  "extra-files": [
18
18
  {
19
19
  "type": "yaml",
@@ -25,7 +25,7 @@
25
25
  "charts/gitops": {
26
26
  "package-name": "helm",
27
27
  "release-type": "helm",
28
- "changelog-path": "charts/gitops/CHANGELOG.md"
28
+ "changelog-path": "CHANGELOG.md"
29
29
  }
30
30
  }
31
31
  }
@@ -1,4 +1,4 @@
1
- SLACK_URL=
2
1
  GITHUB_OAUTH_TOKEN=
3
2
  GITHUB_WEBHOOK_KEY=
4
3
  GIT_CRYPT_KEY_FILE=
4
+ SLACK_CHANNEL_ID=
@@ -41,19 +41,9 @@ class TestDeploy:
41
41
  run_mock.call_args_list[3][0][0],
42
42
  )
43
43
  assert post_mock.call_count == 2
44
- check_in_run_mock = [
45
- (0, "mock-repo"),
46
- (0, "Author Fullname"),
47
- (0, "sample-app-1"),
48
- (0, "sample-app-2"),
49
- (1, "2 succeeded"),
50
- (1, "0 failed"),
51
- ]
52
- for where, check in check_in_run_mock:
53
- assert check in post_mock.call_args_list[where][0][0]
54
44
 
55
45
  @patch("gitops_server.workers.deployer.deploy.run")
56
- @patch("gitops_server.workers.deployer.deploy.post_result")
46
+ @patch("gitops_server.workers.deployer.deploy.Deployer.post_result")
57
47
  @patch("gitops_server.workers.deployer.deploy.load_app_definitions", mock_load_app_definitions)
58
48
  @patch("gitops_server.workers.deployer.deploy.temp_repo")
59
49
  async def test_deployer_update_helm_app(self, temp_repo_mock, post_mock, run_mock):
@@ -111,16 +101,6 @@ class TestDeploy:
111
101
  run_mock.call_args_list[3][0][0],
112
102
  )
113
103
  assert post_mock.call_count == 2
114
- check_in_run_mock = [
115
- (0, "mock-repo"),
116
- (0, "Author Fullname"),
117
- (0, "sample-app-1"),
118
- (0, "sample-app-2"),
119
- (1, "2 succeeded"),
120
- (1, "0 failed"),
121
- ]
122
- for where, check in check_in_run_mock:
123
- assert check in post_mock.call_args_list[where][0][0]
124
104
 
125
105
 
126
106
  class TestLoadAppDefinitions:
@@ -277,7 +277,7 @@ wheels = [
277
277
 
278
278
  [[package]]
279
279
  name = "gitops"
280
- version = "0.11.3"
280
+ version = "0.14.0"
281
281
  source = { editable = "." }
282
282
  dependencies = [
283
283
  { name = "boto3" },
@@ -296,6 +296,7 @@ server = [
296
296
  { name = "httpx" },
297
297
  { name = "kubernetes-asyncio" },
298
298
  { name = "sentry-sdk" },
299
+ { name = "slack-sdk" },
299
300
  { name = "uptick-observability", extra = ["fastapi"] },
300
301
  { name = "uvicorn" },
301
302
  ]
@@ -325,6 +326,7 @@ requires-dist = [
325
326
  { name = "packaging", specifier = ">=24.1" },
326
327
  { name = "pyyaml", specifier = ">=6.0.2" },
327
328
  { name = "sentry-sdk", marker = "extra == 'server'", specifier = ">=1.3.0" },
329
+ { name = "slack-sdk", marker = "extra == 'server'", specifier = ">=3.33.5" },
328
330
  { name = "tabulate", specifier = ">=0.8.9" },
329
331
  { name = "uptick-observability", extras = ["fastapi"], marker = "extra == 'server'", specifier = ">=0.2.2" },
330
332
  { name = "uvicorn", marker = "extra == 'server'", specifier = ">=0.17.6" },
@@ -1090,6 +1092,15 @@ wheels = [
1090
1092
  { url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", size = 11053 },
1091
1093
  ]
1092
1094
 
1095
+ [[package]]
1096
+ name = "slack-sdk"
1097
+ version = "3.33.5"
1098
+ source = { registry = "https://pypi.org/simple" }
1099
+ sdist = { url = "https://files.pythonhosted.org/packages/48/06/2b567dbd5869b4a27e0612bf0cd5a8b852a94a2fc877a4488f420b8506ed/slack_sdk-3.33.5.tar.gz", hash = "sha256:a5e74c00c99dc844ad93e501ab764a20d86fa8184bbc9432af217496f632c4ee", size = 233265 }
1100
+ wheels = [
1101
+ { url = "https://files.pythonhosted.org/packages/fc/1c/b8e4bc3c26ebcdf360fd70c58a7bc01552f440fd119523204fa005681a40/slack_sdk-3.33.5-py2.py3-none-any.whl", hash = "sha256:b8cccadfa3d4005a5e6529f52000d25c583f46173fda8e9136fdd2bc58923ff6", size = 292279 },
1102
+ ]
1103
+
1093
1104
  [[package]]
1094
1105
  name = "sniffio"
1095
1106
  version = "1.3.1"
@@ -1,62 +0,0 @@
1
- name: Release
2
- on:
3
- release:
4
- types: [published]
5
- workflow_dispatch: {}
6
-
7
-
8
- permissions:
9
- actions: read # Read the metrics
10
- contents: write # to be able to publish a GitHub release
11
- issues: write # to be able to comment on released issues
12
- pull-requests: write # to be able to comment on released pull requests
13
- id-token: write # to enable use of OIDC for npm provenance / AWS
14
-
15
- env:
16
- MISE_PYTHON_COMPILE: false
17
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
18
-
19
- jobs:
20
- build:
21
- name: Build and Push Docker Image
22
- uses: uptick/actions/.github/workflows/ci.yaml@main
23
- secrets:
24
- SECRET_ENV: "${{ secrets.CLUSTER_KEY }}"
25
- #https://github.com/uptick/actions/blob/main/.github/workflows/ci.yaml
26
- with:
27
- aws-iam-role-arn: "arn:aws:iam::305686791668:role/default-github-actions-ci-role"
28
- docker-enabled: true
29
- docker-context: "."
30
- docker-tag: ${{github.event.release.tag_name}}
31
- docker-tag-latest: true
32
- docker-image-platforms: linux/amd64
33
- docker-repository: "305686791668.dkr.ecr.ap-southeast-2.amazonaws.com/gitops"
34
- command: echo $SECRET_ENV | base64 -d > cluster.key
35
-
36
- publish_helm_chart:
37
- runs-on: ubuntu-latest
38
- steps:
39
- - uses: actions/checkout@v3
40
- - name: Publish Helm charts
41
- uses: stefanprodan/helm-gh-pages@master
42
- with:
43
- token: ${{ secrets.GITHUB_TOKEN }}
44
-
45
- publish_to_pypi:
46
- name: Publishes tag to pypi
47
- runs-on: ubuntu-latest
48
- steps:
49
- - uses: actions/checkout@v3
50
-
51
- - uses: jdx/mise-action@v2
52
- with:
53
- install: true
54
- cache: true
55
- experimental: true
56
-
57
- - run: |
58
- mise run build
59
- shell: bash
60
-
61
- - name: Publish Pypi Package
62
- uses: pypa/gh-action-pypi-publish@release/v1
@@ -1,5 +0,0 @@
1
- {
2
- ".": "0.13.2",
3
- "gitops_server": "0.14.0",
4
- "charts/gitops": "0.13.0"
5
- }
File without changes
File without changes
@@ -1,7 +0,0 @@
1
- import os
2
-
3
- CLUSTER_NAME = os.getenv("CLUSTER_NAME", "")
4
- # Namespace to search/deploy into
5
- CLUSTER_NAMESPACE = os.getenv("CLUSTER_NAMESPACE", "")
6
- ACCOUNT_ID = os.getenv("ACCOUNT_ID", "")
7
- GITHUB_WEBHOOK_KEY = os.getenv("GITHUB_WEBHOOK_KEY", "")
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -1,7 +1,7 @@
1
1
  import asyncio
2
2
  import logging
3
- from pathlib import Path
4
3
  from collections.abc import Callable
4
+ from pathlib import Path
5
5
 
6
6
  from ..types import RunOutput
7
7
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes