gitops 1.1.2__tar.gz → 1.3.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 (84) hide show
  1. {gitops-1.1.2 → gitops-1.3.0}/.release-please-manifest.json +1 -1
  2. {gitops-1.1.2 → gitops-1.3.0}/CHANGELOG.md +21 -0
  3. {gitops-1.1.2 → gitops-1.3.0}/PKG-INFO +1 -1
  4. {gitops-1.1.2 → gitops-1.3.0}/gitops/__init__.py +1 -1
  5. {gitops-1.1.2 → gitops-1.3.0}/gitops/core.py +4 -0
  6. {gitops-1.1.2 → gitops-1.3.0}/gitops/utils/apps.py +15 -1
  7. {gitops-1.1.2 → gitops-1.3.0}/gitops_server/main.py +1 -4
  8. gitops-1.3.0/gitops_server/version.py +1 -0
  9. gitops-1.3.0/gitops_server/workers/__init__.py +1 -0
  10. {gitops-1.1.2 → gitops-1.3.0}/gitops_server/workers/deployer/deploy.py +5 -0
  11. {gitops-1.1.2 → gitops-1.3.0}/gitops_server/workers/deployer/hooks.py +4 -1
  12. {gitops-1.1.2 → gitops-1.3.0}/pyproject.toml +1 -1
  13. {gitops-1.1.2 → gitops-1.3.0}/tests/sample_data.py +189 -0
  14. {gitops-1.1.2 → gitops-1.3.0}/tests/test_deploy.py +17 -1
  15. {gitops-1.1.2 → gitops-1.3.0}/tests/test_gitops_server.py +0 -11
  16. gitops-1.1.2/gitops_server/version.py +0 -1
  17. gitops-1.1.2/gitops_server/workers/__init__.py +0 -2
  18. gitops-1.1.2/gitops_server/workers/status_updater/__init__.py +0 -1
  19. gitops-1.1.2/gitops_server/workers/status_updater/worker.py +0 -126
  20. {gitops-1.1.2 → gitops-1.3.0}/.dockerignore +0 -0
  21. {gitops-1.1.2 → gitops-1.3.0}/.github/workflows/release-please.yaml +0 -0
  22. {gitops-1.1.2 → gitops-1.3.0}/.github/workflows/test.yml +0 -0
  23. {gitops-1.1.2 → gitops-1.3.0}/.gitignore +0 -0
  24. {gitops-1.1.2 → gitops-1.3.0}/.mise.toml +0 -0
  25. {gitops-1.1.2 → gitops-1.3.0}/.pre-commit-config.yaml +0 -0
  26. {gitops-1.1.2 → gitops-1.3.0}/Dockerfile +0 -0
  27. {gitops-1.1.2 → gitops-1.3.0}/LICENSE +0 -0
  28. {gitops-1.1.2 → gitops-1.3.0}/README.md +0 -0
  29. {gitops-1.1.2 → gitops-1.3.0}/charts/gitops/.helmignore +0 -0
  30. {gitops-1.1.2 → gitops-1.3.0}/charts/gitops/CHANGELOG.md +0 -0
  31. {gitops-1.1.2 → gitops-1.3.0}/charts/gitops/Chart.yaml +0 -0
  32. {gitops-1.1.2 → gitops-1.3.0}/charts/gitops/README.md +0 -0
  33. {gitops-1.1.2 → gitops-1.3.0}/charts/gitops/templates/_helpers.tpl +0 -0
  34. {gitops-1.1.2 → gitops-1.3.0}/charts/gitops/templates/certificate.yml +0 -0
  35. {gitops-1.1.2 → gitops-1.3.0}/charts/gitops/templates/deployment.yml +0 -0
  36. {gitops-1.1.2 → gitops-1.3.0}/charts/gitops/templates/environment.yml +0 -0
  37. {gitops-1.1.2 → gitops-1.3.0}/charts/gitops/templates/ingress.yml +0 -0
  38. {gitops-1.1.2 → gitops-1.3.0}/charts/gitops/templates/issuer.yml +0 -0
  39. {gitops-1.1.2 → gitops-1.3.0}/charts/gitops/templates/secrets.yml +0 -0
  40. {gitops-1.1.2 → gitops-1.3.0}/charts/gitops/templates/service.yml +0 -0
  41. {gitops-1.1.2 → gitops-1.3.0}/charts/gitops/templates/serviceaccount.yaml +0 -0
  42. {gitops-1.1.2 → gitops-1.3.0}/charts/gitops/values.yaml +0 -0
  43. {gitops-1.1.2 → gitops-1.3.0}/docker-compose.yml +0 -0
  44. {gitops-1.1.2 → gitops-1.3.0}/gitops/common/__init__.py +0 -0
  45. {gitops-1.1.2 → gitops-1.3.0}/gitops/common/app.py +0 -0
  46. {gitops-1.1.2 → gitops-1.3.0}/gitops/common/utils.py +0 -0
  47. {gitops-1.1.2 → gitops-1.3.0}/gitops/main.py +0 -0
  48. {gitops-1.1.2 → gitops-1.3.0}/gitops/py.typed +0 -0
  49. {gitops-1.1.2 → gitops-1.3.0}/gitops/settings.py +0 -0
  50. {gitops-1.1.2 → gitops-1.3.0}/gitops/shorthands.py +0 -0
  51. {gitops-1.1.2 → gitops-1.3.0}/gitops/utils/__init__.py +0 -0
  52. {gitops-1.1.2 → gitops-1.3.0}/gitops/utils/async_runner.py +0 -0
  53. {gitops-1.1.2 → gitops-1.3.0}/gitops/utils/cli.py +0 -0
  54. {gitops-1.1.2 → gitops-1.3.0}/gitops/utils/exceptions.py +0 -0
  55. {gitops-1.1.2 → gitops-1.3.0}/gitops/utils/images.py +0 -0
  56. {gitops-1.1.2 → gitops-1.3.0}/gitops/utils/kube.py +0 -0
  57. {gitops-1.1.2 → gitops-1.3.0}/gitops/utils/tags.py +0 -0
  58. {gitops-1.1.2 → gitops-1.3.0}/gitops/utils/yaml.py +0 -0
  59. {gitops-1.1.2 → gitops-1.3.0}/gitops_server/CHANGELOG.md +0 -0
  60. {gitops-1.1.2 → gitops-1.3.0}/gitops_server/README.md +0 -0
  61. {gitops-1.1.2 → gitops-1.3.0}/gitops_server/__init__.py +0 -0
  62. {gitops-1.1.2 → gitops-1.3.0}/gitops_server/app.py +0 -0
  63. {gitops-1.1.2 → gitops-1.3.0}/gitops_server/settings.py +0 -0
  64. {gitops-1.1.2 → gitops-1.3.0}/gitops_server/types.py +0 -0
  65. {gitops-1.1.2 → gitops-1.3.0}/gitops_server/utils/__init__.py +0 -0
  66. {gitops-1.1.2 → gitops-1.3.0}/gitops_server/utils/git.py +0 -0
  67. {gitops-1.1.2 → gitops-1.3.0}/gitops_server/utils/github.py +0 -0
  68. {gitops-1.1.2 → gitops-1.3.0}/gitops_server/utils/slack.py +0 -0
  69. {gitops-1.1.2 → gitops-1.3.0}/gitops_server/workers/deployer/__init__.py +0 -0
  70. {gitops-1.1.2 → gitops-1.3.0}/gitops_server/workers/deployer/worker.py +0 -0
  71. {gitops-1.1.2 → gitops-1.3.0}/release-please-config.json +0 -0
  72. {gitops-1.1.2 → gitops-1.3.0}/release.py +0 -0
  73. {gitops-1.1.2 → gitops-1.3.0}/ruff.toml +0 -0
  74. {gitops-1.1.2 → gitops-1.3.0}/secrets.example.env +0 -0
  75. {gitops-1.1.2 → gitops-1.3.0}/setup.cfg +0 -0
  76. {gitops-1.1.2 → gitops-1.3.0}/tests/__init__.py +0 -0
  77. {gitops-1.1.2 → gitops-1.3.0}/tests/conftest.py +0 -0
  78. {gitops-1.1.2 → gitops-1.3.0}/tests/test_app.py +0 -0
  79. {gitops-1.1.2 → gitops-1.3.0}/tests/test_core.py +0 -0
  80. {gitops-1.1.2 → gitops-1.3.0}/tests/test_git.py +0 -0
  81. {gitops-1.1.2 → gitops-1.3.0}/tests/test_utils.py +0 -0
  82. {gitops-1.1.2 → gitops-1.3.0}/tests/utils.py +0 -0
  83. {gitops-1.1.2 → gitops-1.3.0}/tests/webhook_sample_data.py +0 -0
  84. {gitops-1.1.2 → gitops-1.3.0}/uv.lock +0 -0
@@ -1,5 +1,5 @@
1
1
  {
2
- ".": "1.1.2",
2
+ ".": "1.3.0",
3
3
  "gitops_server": "0.15.1",
4
4
  "charts/gitops": "1.1.2"
5
5
  }
@@ -1,5 +1,26 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.3.0](https://github.com/uptick/gitops/compare/gitops-v1.2.0...gitops-v1.3.0) (2025-03-25)
4
+
5
+
6
+ ### Features
7
+
8
+ * **cli:** add skip deploy to gitops bump ([554b0a5](https://github.com/uptick/gitops/commit/554b0a5adf5dbea0f0ac3d020ac65aa1e3c831bd))
9
+ * **server:** skip deployment if --skip-deploy is present ([2cd9c16](https://github.com/uptick/gitops/commit/2cd9c16ab618d13660b5ef17079e39bbcd02d956))
10
+
11
+ ## [1.2.0](https://github.com/uptick/gitops/compare/gitops-v1.1.2...gitops-v1.2.0) (2025-03-17)
12
+
13
+
14
+ ### Features
15
+
16
+ * **cli:** allow filtering apps via a csv file ([f465d9a](https://github.com/uptick/gitops/commit/f465d9a8ad565afde1f9bf7f9d1232281352882c))
17
+ * **cli:** allow filtering apps via a csv file ([7fa06cd](https://github.com/uptick/gitops/commit/7fa06cd6d8ca42a2d1e50304ad7cba7e688650e9))
18
+
19
+
20
+ ### Bug Fixes
21
+
22
+ * remove status updater and replace with a post deploy hook ([e72c2ad](https://github.com/uptick/gitops/commit/e72c2ad2b46304f2258d7b3d1bb04e4200ce1602))
23
+
3
24
  ## [1.1.2](https://github.com/uptick/gitops/compare/gitops-v1.1.1...gitops-v1.1.2) (2025-02-14)
4
25
 
5
26
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gitops
3
- Version: 1.1.2
3
+ Version: 1.3.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
@@ -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__ = "1.1.2"
9
+ __version__ = "1.3.0"
10
10
 
11
11
 
12
12
  # Checking gitops version matches cluster repo version.
@@ -42,6 +42,7 @@ def bump( # noqa: C901
42
42
  push=False,
43
43
  redeploy=False,
44
44
  skip_migrations=False,
45
+ skip_deploy=False,
45
46
  ):
46
47
  """Bump image tag on selected app(s).
47
48
  Provide `image_tag` to set to a specific image tag, or provide `prefix` to use latest image
@@ -50,6 +51,7 @@ def bump( # noqa: C901
50
51
  Provide `push` to automatically push the commit (and retry on conflict.)
51
52
  Provide `redeploy` to redeploy servers even if nothing has changed.
52
53
  Provide `skip_migrations` to disable running migrations via helm hooks.
54
+ Provide `skip_deploy` to skip deploying for non functional changes.
53
55
  """
54
56
  prompt_message = "The following apps will have their image bumped"
55
57
  if image_tag:
@@ -119,6 +121,8 @@ def bump( # noqa: C901
119
121
  commit_message += f" to use prefix {prefix}"
120
122
  if skip_migrations:
121
123
  commit_message += " --skip-migrations"
124
+ if skip_deploy:
125
+ commit_message += " --skip-deploy"
122
126
 
123
127
  run(f'cd {get_apps_directory()}; git commit -am "{commit_message}."')
124
128
 
@@ -1,3 +1,4 @@
1
+ import pathlib
1
2
  import sys
2
3
  from pathlib import Path
3
4
  from typing import Literal
@@ -77,6 +78,9 @@ def get_apps( # noqa: C901
77
78
  `exclude`. The incoming filter and exclude params may come in as a list or commastring.
78
79
  For the purpose of this filtering, app names and image tag prefixes are also considered as
79
80
  tags. For instance, you can do get_apps(tags=[emeriss, production], exclude=[arafire]).
81
+
82
+ If filter is a file like name; read a list of app names from the file.
83
+
80
84
  Calling this method without any args returns all apps.
81
85
  There are three modes for communicating selected apps to the user:
82
86
  - PROMPT: Prints selected apps and asks for confirmation to proceed.
@@ -84,8 +88,13 @@ def get_apps( # noqa: C901
84
88
  - SILENT: Proceeds without printing.
85
89
  Apps with the `inactive` tag are excluded by default, unless requested otherwise.
86
90
  """
91
+ app_names: list[str] = []
92
+
87
93
  if filter == "all":
88
94
  filter = set()
95
+ elif isinstance(filter, str) and pathlib.Path(filter).is_file():
96
+ app_names = [f.strip() for f in pathlib.Path(filter).read_text().splitlines() if f.strip()]
97
+ filter = set()
89
98
  else:
90
99
  filter = set(filter.split(",") if filter and isinstance(filter, str) else filter)
91
100
 
@@ -114,7 +123,12 @@ def get_apps( # noqa: C901
114
123
 
115
124
  tags = set(app.tags + pseudotags)
116
125
  existing_tags |= tags
117
- if filter <= tags and not exclude & tags:
126
+
127
+ # Decide whether to include the app based on the filter and exclude tags.
128
+ if app_names:
129
+ if app.name in app_names:
130
+ apps.append(app)
131
+ elif filter <= tags and not exclude & tags:
118
132
  apps.append(app)
119
133
 
120
134
  validate_tags(filter | exclude, existing_tags)
@@ -13,7 +13,7 @@ from uptick_observability.logging import ( # type: ignore[import-untyped]
13
13
 
14
14
  from gitops_server import settings
15
15
  from gitops_server.app import app
16
- from gitops_server.workers import DeploymentStatusWorker, DeployQueueWorker
16
+ from gitops_server.workers import DeployQueueWorker
17
17
 
18
18
  manually_instrument_logging()
19
19
  manually_instrument_fastapi()
@@ -64,9 +64,6 @@ async def startup_event():
64
64
  deploy_queue_worker = DeployQueueWorker.get_worker()
65
65
  deploy_queue_worker.task = asyncio.ensure_future(deploy_queue_worker.run(), loop=loop)
66
66
 
67
- deployment_status_worker = DeploymentStatusWorker.get_worker()
68
- deployment_status_worker.task = asyncio.ensure_future(deployment_status_worker.run(), loop=loop)
69
-
70
67
 
71
68
  def get_digest(data: bytes) -> str:
72
69
  """Calculate the digest of a webhook body.
@@ -0,0 +1 @@
1
+ __version__ = "1.3.0"
@@ -0,0 +1 @@
1
+ from .deployer import DeployQueueWorker # NOQA
@@ -70,6 +70,7 @@ class Deployer:
70
70
  self.failed_apps: set[str] = set()
71
71
 
72
72
  self.post_ts: str | None = None
73
+ self.skip_deploy = "--skip-deploy" in commit_message
73
74
 
74
75
  @classmethod
75
76
  async def from_push_event(cls, push_event):
@@ -94,6 +95,10 @@ class Deployer:
94
95
  )
95
96
 
96
97
  async def deploy(self):
98
+ if self.skip_deploy:
99
+ logger.info("Skipping deploy due to `--skip-deploy` flag.")
100
+ return
101
+
97
102
  self.added_apps, self.updated_apps, self.removed_apps = self.calculate_app_deltas()
98
103
  current_span = trace.get_current_span()
99
104
  if current_span:
@@ -50,10 +50,13 @@ async def update_issue_from_deployment_url(app: App, deployment_url: str, **kwar
50
50
 
51
51
  async def handle_successful_deploy(app: App, result, deployer, **kwargs) -> UpdateAppResult:
52
52
  github_deployment_url = str(app.values.get("github/deployment_url", ""))
53
+ # I know this shouldn't be uptick specific but for now it is.
54
+ environment_url = app.values.get("github/environment_url", "") or f"https://{app.name}.onuptick.com"
53
55
  await github.update_deployment(
54
56
  github_deployment_url,
55
- status=github.STATUSES.in_progress,
57
+ status=github.STATUSES.success,
56
58
  description="Helm installed app into cluster. Waiting for pods to deploy.",
59
+ environment_url=environment_url,
57
60
  )
58
61
  return result
59
62
 
@@ -1,7 +1,7 @@
1
1
 
2
2
  [project]
3
3
  name = "gitops"
4
- version = "1.1.2"
4
+ version = "1.3.0"
5
5
  description = "Manage multiple apps across one or more k8s clusters."
6
6
  requires-python = ">=3.12"
7
7
  readme = "README.md"
@@ -375,3 +375,192 @@ SAMPLE_GITHUB_PAYLOAD_SKIP_MIGRATIONS = {
375
375
  "site_admin": False,
376
376
  },
377
377
  }
378
+
379
+ SAMPLE_GITHUB_PAYLOAD_SKIP_DEPLOY = {
380
+ "ref": "refs/heads/master",
381
+ "before": "bef04e58a0001234567890123456789012345678",
382
+ "after": "af7e458a00012345678901234567890123456789",
383
+ "created": False,
384
+ "deleted": False,
385
+ "forced": False,
386
+ "base_ref": None,
387
+ "compare": "https://github.com/user/mock-repo/compare/bef04e58a000...af7e458a0001",
388
+ "commits": [
389
+ {
390
+ "id": "c099171d00c099171d00c099171d00c099171d00",
391
+ "tree_id": "74ee1d000074ee1d000074ee1d000074ee1d0000",
392
+ "distinct": True,
393
+ "message": "bump test-app-1 --skip-deploy",
394
+ "timestamp": "2019-07-18T15:07:37+10:00",
395
+ "url": ("https://github.com/user/mock-repo/commit/c099171d00c099171d00c099171d00c099171d00"),
396
+ "author": {
397
+ "name": "Author Fullname",
398
+ "email": "author@example.com",
399
+ "username": "authorusername",
400
+ },
401
+ "committer": {
402
+ "name": "Author Fullname",
403
+ "email": "author@example.com",
404
+ "username": "authorusername",
405
+ },
406
+ "added": [],
407
+ "removed": [],
408
+ "modified": ["apps/test-app-1/deployment.yml"],
409
+ }
410
+ ],
411
+ "head_commit": {
412
+ "id": "c099171d00c099171d00c099171d00c099171d00",
413
+ "tree_id": "74ee1d000074ee1d000074ee1d000074ee1d0000",
414
+ "distinct": True,
415
+ "message": "bump test-app-1 --skip-deploy",
416
+ "timestamp": "2019-07-18T15:07:37+10:00",
417
+ "url": "https://github.com/user/mock-repo/commit/c099171d00c099171d00c099171d00c099171d00",
418
+ "author": {
419
+ "name": "Author Fullname",
420
+ "email": "author@example.com",
421
+ "username": "authorusername",
422
+ },
423
+ "committer": {
424
+ "name": "Author Fullname",
425
+ "email": "author@example.com",
426
+ "username": "authorusername",
427
+ },
428
+ "added": [],
429
+ "removed": [],
430
+ "modified": ["apps/test-app-1/deployment.yml"],
431
+ },
432
+ "repository": {
433
+ "id": 123123123,
434
+ "node_id": "NODEIDNODEIDNODEIDNODEIDNODEIDN=",
435
+ "name": "mock-repo",
436
+ "full_name": "user/mock-repo",
437
+ "private": True,
438
+ "owner": {
439
+ "name": "user",
440
+ "email": "admin@example.com",
441
+ "login": "user",
442
+ "id": 123123123,
443
+ "node_id": "NODEIDNODEIDNODEIDNODEIDNODEIDNO",
444
+ "avatar_url": "",
445
+ "gravatar_id": "",
446
+ "url": "https://api.github.com/users/user",
447
+ "html_url": "https://github.com/user",
448
+ "followers_url": "https://api.github.com/users/user/followers",
449
+ "following_url": "https://api.github.com/users/user/following{/other_user}",
450
+ "gists_url": "https://api.github.com/users/user/gists{/gist_id}",
451
+ "starred_url": "https://api.github.com/users/user/starred{/owner}{/repo}",
452
+ "subscriptions_url": "https://api.github.com/users/user/subscriptions",
453
+ "organizations_url": "https://api.github.com/users/user/orgs",
454
+ "repos_url": "https://api.github.com/users/user/repos",
455
+ "events_url": "https://api.github.com/users/user/events{/privacy}",
456
+ "received_events_url": "https://api.github.com/users/user/received_events",
457
+ "type": "Organization",
458
+ "site_admin": False,
459
+ },
460
+ "html_url": "https://github.com/user/mock-repo",
461
+ "description": "Kubernetes cluster configuration.",
462
+ "fork": False,
463
+ "url": "https://github.com/user/mock-repo",
464
+ "forks_url": "https://api.github.com/repos/user/mock-repo/forks",
465
+ "keys_url": "https://api.github.com/repos/user/mock-repo/keys{/key_id}",
466
+ "collaborators_url": ("https://api.github.com/repos/user/mock-repo/collaborators{/collaborator}"),
467
+ "teams_url": "https://api.github.com/repos/user/mock-repo/teams",
468
+ "hooks_url": "https://api.github.com/repos/user/mock-repo/hooks",
469
+ "issue_events_url": "https://api.github.com/repos/user/mock-repo/issues/events{/number}",
470
+ "events_url": "https://api.github.com/repos/user/mock-repo/events",
471
+ "assignees_url": "https://api.github.com/repos/user/mock-repo/assignees{/user}",
472
+ "branches_url": "https://api.github.com/repos/user/mock-repo/branches{/branch}",
473
+ "tags_url": "https://api.github.com/repos/user/mock-repo/tags",
474
+ "blobs_url": "https://api.github.com/repos/user/mock-repo/git/blobs{/sha}",
475
+ "git_tags_url": "https://api.github.com/repos/user/mock-repo/git/tags{/sha}",
476
+ "git_refs_url": "https://api.github.com/repos/user/mock-repo/git/refs{/sha}",
477
+ "trees_url": "https://api.github.com/repos/user/mock-repo/git/trees{/sha}",
478
+ "statuses_url": "https://api.github.com/repos/user/mock-repo/statuses/{sha}",
479
+ "languages_url": "https://api.github.com/repos/user/mock-repo/languages",
480
+ "stargazers_url": "https://api.github.com/repos/user/mock-repo/stargazers",
481
+ "contributors_url": "https://api.github.com/repos/user/mock-repo/contributors",
482
+ "subscribers_url": "https://api.github.com/repos/user/mock-repo/subscribers",
483
+ "subscription_url": "https://api.github.com/repos/user/mock-repo/subscription",
484
+ "commits_url": "https://api.github.com/repos/user/mock-repo/commits{/sha}",
485
+ "git_commits_url": "https://api.github.com/repos/user/mock-repo/git/commits{/sha}",
486
+ "comments_url": "https://api.github.com/repos/user/mock-repo/comments{/number}",
487
+ "issue_comment_url": "https://api.github.com/repos/user/mock-repo/issues/comments{/number}",
488
+ "contents_url": "https://api.github.com/repos/user/mock-repo/contents/{+path}",
489
+ "compare_url": "https://api.github.com/repos/user/mock-repo/compare/{base}...{head}",
490
+ "merges_url": "https://api.github.com/repos/user/mock-repo/merges",
491
+ "archive_url": "https://api.github.com/repos/user/mock-repo/{archive_format}{/ref}",
492
+ "downloads_url": "https://api.github.com/repos/user/mock-repo/downloads",
493
+ "issues_url": "https://api.github.com/repos/user/mock-repo/issues{/number}",
494
+ "pulls_url": "https://api.github.com/repos/user/mock-repo/pulls{/number}",
495
+ "milestones_url": "https://api.github.com/repos/user/mock-repo/milestones{/number}",
496
+ "notifications_url": ("https://api.github.com/repos/user/mock-repo/notifications{?since,all,participating}"),
497
+ "labels_url": "https://api.github.com/repos/user/mock-repo/labels{/name}",
498
+ "releases_url": "https://api.github.com/repos/user/mock-repo/releases{/id}",
499
+ "deployments_url": "https://api.github.com/repos/user/mock-repo/deployments",
500
+ "created_at": 123123,
501
+ "updated_at": "2019-07-18T05:05:03Z",
502
+ "pushed_at": 123123,
503
+ "git_url": "git://github.com/user/mock-repo.git",
504
+ "ssh_url": "git@github.com:user/mock-repo.git",
505
+ "clone_url": "https://github.com/user/mock-repo.git",
506
+ "svn_url": "https://github.com/user/mock-repo",
507
+ "homepage": "",
508
+ "size": 2917,
509
+ "stargazers_count": 0,
510
+ "watchers_count": 0,
511
+ "language": "Python",
512
+ "has_issues": True,
513
+ "has_projects": True,
514
+ "has_downloads": True,
515
+ "has_wiki": True,
516
+ "has_pages": False,
517
+ "forks_count": 0,
518
+ "mirror_url": None,
519
+ "archived": False,
520
+ "disabled": False,
521
+ "open_issues_count": 5,
522
+ "license": None,
523
+ "forks": 0,
524
+ "open_issues": 5,
525
+ "watchers": 0,
526
+ "default_branch": "master",
527
+ "stargazers": 0,
528
+ "master_branch": "master",
529
+ "organization": "user",
530
+ },
531
+ "pusher": {"name": "authorusername", "email": "author@example.com"},
532
+ "organization": {
533
+ "login": "user",
534
+ "id": 123123,
535
+ "node_id": "NODEID00NODEID00NODEID00NODEID00",
536
+ "url": "https://api.github.com/orgs/user",
537
+ "repos_url": "https://api.github.com/orgs/user/repos",
538
+ "events_url": "https://api.github.com/orgs/user/events",
539
+ "hooks_url": "https://api.github.com/orgs/user/hooks",
540
+ "issues_url": "https://api.github.com/orgs/user/issues",
541
+ "members_url": "https://api.github.com/orgs/user/members{/member}",
542
+ "public_members_url": "https://api.github.com/orgs/user/public_members{/member}",
543
+ "avatar_url": "",
544
+ "description": "",
545
+ },
546
+ "sender": {
547
+ "login": "authorusername",
548
+ "id": 123123,
549
+ "node_id": "NODEID00NODEID00NOD=",
550
+ "avatar_url": "https://avatars0.githubusercontent.com/u/5036488?v=4",
551
+ "gravatar_id": "",
552
+ "url": "https://api.github.com/users/authorusername",
553
+ "html_url": "https://github.com/authorusername",
554
+ "followers_url": "https://api.github.com/users/authorusername/followers",
555
+ "following_url": "https://api.github.com/users/authorusername/following{/other_user}",
556
+ "gists_url": "https://api.github.com/users/authorusername/gists{/gist_id}",
557
+ "starred_url": "https://api.github.com/users/authorusername/starred{/owner}{/repo}",
558
+ "subscriptions_url": "https://api.github.com/users/authorusername/subscriptions",
559
+ "organizations_url": "https://api.github.com/users/authorusername/orgs",
560
+ "repos_url": "https://api.github.com/users/authorusername/repos",
561
+ "events_url": "https://api.github.com/users/authorusername/events{/privacy}",
562
+ "received_events_url": "https://api.github.com/users/authorusername/received_events",
563
+ "type": "User",
564
+ "site_admin": False,
565
+ },
566
+ }
@@ -7,7 +7,7 @@ from gitops.common.app import App
7
7
  from gitops_server.types import AppDefinitions
8
8
  from gitops_server.workers.deployer import Deployer
9
9
 
10
- from .sample_data import SAMPLE_GITHUB_PAYLOAD, SAMPLE_GITHUB_PAYLOAD_SKIP_MIGRATIONS
10
+ from .sample_data import SAMPLE_GITHUB_PAYLOAD, SAMPLE_GITHUB_PAYLOAD_SKIP_DEPLOY, SAMPLE_GITHUB_PAYLOAD_SKIP_MIGRATIONS
11
11
  from .utils import create_test_yaml, mock_load_app_definitions
12
12
 
13
13
  # Patch gitops_server.git.run & check correct commands + order
@@ -102,6 +102,22 @@ class TestDeploy:
102
102
  )
103
103
  assert post_mock.call_count == 2
104
104
 
105
+ @patch("gitops_server.workers.deployer.deploy.run")
106
+ @patch("gitops_server.utils.slack.post")
107
+ @patch("gitops_server.workers.deployer.deploy.load_app_definitions", mock_load_app_definitions)
108
+ @patch("gitops_server.workers.deployer.deploy.temp_repo")
109
+ async def test_skip_deploy_in_commit_message_should_not_run_deployment(self, temp_repo_mock, post_mock, run_mock):
110
+ """Fake a deploy to two servers, bumping fg from 2 to 4."""
111
+ run_mock.return_value = {"exit_code": 0, "output": ""}
112
+ temp_repo_mock.return_value.__aenter__.return_value = "mock-repo"
113
+ deployer = await Deployer.from_push_event(SAMPLE_GITHUB_PAYLOAD_SKIP_DEPLOY)
114
+
115
+ assert deployer.skip_deploy is True
116
+ await deployer.deploy()
117
+
118
+ assert run_mock.call_count == 0
119
+ assert post_mock.call_count == 0
120
+
105
121
 
106
122
  class TestLoadAppDefinitions:
107
123
  def test_load_app_definitions_ignores_suspended_apps(self):
@@ -31,14 +31,3 @@ def test_webhook_returns_200_if_hmac_is_correct():
31
31
 
32
32
  assert response.status_code == 200
33
33
  get_worker_mock.assert_called()
34
-
35
-
36
- @patch("gitops_server.main.settings.GITHUB_WEBHOOK_KEY", "test_key")
37
- def test_webhook_returns_400_if_hmac_is_invalid():
38
- sha_encoding = "INVALID HMAC ENCODING"
39
- headers["X-Hub-Signature"] = f"sha1={sha_encoding}"
40
-
41
- with patch.object(gitops_server.workers.DeploymentStatusWorker, "get_worker", AsyncMock):
42
- response = client.post("/webhook", headers=headers, json=payload)
43
-
44
- assert response.status_code == 400
@@ -1 +0,0 @@
1
- __version__ = "1.1.2"
@@ -1,2 +0,0 @@
1
- from .deployer import DeployQueueWorker # NOQA
2
- from .status_updater import DeploymentStatusWorker # NOQA
@@ -1 +0,0 @@
1
- from .worker import DeploymentStatusWorker # NOQA
@@ -1,126 +0,0 @@
1
- """
2
- This async worker keeps tracks of ongoing deployments by polling k8s space (CLUSTER_NAMESPACE).
3
-
4
- The worker polls and checks for deployments with the following labels: gitops/deploy_id and gitops/status=in_progress
5
-
6
- Any failing/successfully deployed deployment is notified to github.
7
- This mechanism will only work if the annotation: github/deployment_url is applied to the deployment.
8
-
9
- TODO
10
- - Update the slack summary message from deploy.py
11
- - @notify the user if the deployment failed
12
- """
13
-
14
- import asyncio
15
- import logging
16
-
17
- import kubernetes_asyncio # type:ignore[import-untyped]
18
- import kubernetes_asyncio.client # type:ignore[import-untyped]
19
- import kubernetes_asyncio.config # type:ignore[import-untyped]
20
-
21
- from gitops_server.settings import CLUSTER_NAMESPACE
22
- from gitops_server.utils import github
23
-
24
- logger = logging.getLogger("deployment_status")
25
-
26
-
27
- async def get_ingress_url(api, namespace: str, app: str):
28
- """Attempts to get domain for the ingress associated with the app"""
29
- ingresses = await kubernetes_asyncio.client.NetworkingV1Api(api).list_namespaced_ingress(
30
- namespace=namespace, label_selector=f"app={app}"
31
- )
32
- environment_url = ""
33
- if ingresses.items:
34
- try:
35
- environment_url = "https://" + ingresses.items[0].spec.rules[0].host
36
- except Exception:
37
- logger.warning(f"Could not find ingress for {app=}")
38
- return environment_url
39
-
40
-
41
- class DeploymentStatusWorker:
42
- """Watches for deployments and updates the github deployment status"""
43
-
44
- _worker = None
45
-
46
- @classmethod
47
- def get_worker(cls):
48
- if not cls._worker:
49
- loop = asyncio.get_running_loop()
50
- cls._worker = cls(loop)
51
- return cls._worker
52
-
53
- def __init__(self, loop):
54
- self.loop = loop
55
-
56
- async def load_config(self):
57
- try:
58
- kubernetes_asyncio.config.load_incluster_config()
59
- except kubernetes_asyncio.config.config_exception.ConfigException:
60
- await kubernetes_asyncio.config.load_kube_config()
61
-
62
- async def process_work(self):
63
- await asyncio.sleep(5)
64
- async with kubernetes_asyncio.client.ApiClient() as api:
65
- apps_api = kubernetes_asyncio.client.AppsV1Api(api)
66
- deployments = await apps_api.list_namespaced_deployment(
67
- # Only things that have gitops/deploy_id aka was deployed
68
- namespace=CLUSTER_NAMESPACE,
69
- label_selector="gitops/deploy_id,gitops/status=in_progress",
70
- )
71
- await asyncio.sleep(5)
72
-
73
- for deployment in deployments.items:
74
- # Deployment status may not exist yet
75
- if not deployment.status:
76
- continue
77
- app = deployment.metadata.labels["app"]
78
- namespace = deployment.metadata.namespace
79
- github_deployment_url = deployment.metadata.annotations.get("github/deployment_url")
80
- conds = {}
81
- for x in deployment.status.conditions:
82
- conds[x.type] = x
83
- status = None
84
- if (
85
- len(conds) == 2
86
- and conds["Available"].status == "True"
87
- and conds["Progressing"].status == "True"
88
- and conds["Progressing"].reason == "NewReplicaSetAvailable"
89
- ):
90
- status = github.STATUSES.success
91
- await github.update_deployment(
92
- github_deployment_url,
93
- status=status,
94
- description="Deployed successfully",
95
- environment_url=await get_ingress_url(api, namespace, app),
96
- )
97
- elif (
98
- "Progressing" in conds
99
- and conds["Progressing"].status == "False"
100
- and conds["Progressing"].reason == "ProgressDeadlineExceeded"
101
- ):
102
- status = github.STATUSES.failure
103
- await github.update_deployment(
104
- github_deployment_url,
105
- status=status,
106
- description="Failed to deploy. Check the pod or migrations.",
107
- )
108
- if status:
109
- logger.info(f"Patching {deployment.metadata.name}.label.gitops/status to {status}")
110
- patch = {"metadata": {"labels": {"gitops/status": status}}}
111
- try:
112
- await apps_api.patch_namespaced_deployment(
113
- deployment.metadata.name, deployment.metadata.namespace, patch
114
- )
115
- except kubernetes_asyncio.client.exceptions.ApiException as e:
116
- logger.warning(e, exc_info=True)
117
-
118
- async def run(self):
119
- logger.info("Starting deployment status watching loop")
120
- logger.info("Loading kubernetes asyncio api")
121
- while True:
122
- try:
123
- await self.load_config()
124
- await self.process_work()
125
- except Exception as e:
126
- logger.error(str(e), exc_info=True)
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
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