|
47 | 47 |
|
48 | 48 | from cabotage.server.models.projects import ( |
49 | 49 | activity_plugin, |
| 50 | + Environment, |
50 | 51 | Image, |
51 | 52 | Release, |
52 | 53 | Deployment, |
|
75 | 76 | log = logging.getLogger(__name__) |
76 | 77 |
|
77 | 78 |
|
| 79 | +def _branch_deploy_environment_backing_services_ready(environment): |
| 80 | + resources = environment.active_resources |
| 81 | + if not resources: |
| 82 | + return True |
| 83 | + return all(resource.provisioning_status == "ready" for resource in resources) |
| 84 | + |
| 85 | + |
| 86 | +def _branch_deploy_backing_services_ready(app_env): |
| 87 | + environment = app_env.environment if app_env else None |
| 88 | + if not environment or environment.forked_from_environment_id is None: |
| 89 | + return True |
| 90 | + return _branch_deploy_environment_backing_services_ready(environment) |
| 91 | + |
| 92 | + |
| 93 | +def _mark_image_waiting_for_backing_services(image): |
| 94 | + image.image_metadata = { |
| 95 | + **(image.image_metadata or {}), |
| 96 | + "waiting_for_backing_services": True, |
| 97 | + } |
| 98 | + db.session.add(image) |
| 99 | + db.session.commit() |
| 100 | + |
| 101 | + |
| 102 | +def _clear_image_waiting_for_backing_services(image): |
| 103 | + metadata = dict(image.image_metadata or {}) |
| 104 | + if "waiting_for_backing_services" in metadata: |
| 105 | + metadata.pop("waiting_for_backing_services", None) |
| 106 | + image.image_metadata = metadata |
| 107 | + db.session.add(image) |
| 108 | + db.session.commit() |
| 109 | + |
| 110 | + |
| 111 | +def _latest_release_for_image(image): |
| 112 | + app_env = image.application_environment |
| 113 | + if not app_env: |
| 114 | + return None |
| 115 | + release = app_env.latest_release |
| 116 | + if not release or not release.release_metadata: |
| 117 | + return None |
| 118 | + if release.release_metadata.get("source_image_id") == str(image.id): |
| 119 | + return release |
| 120 | + return None |
| 121 | + |
| 122 | + |
| 123 | +def _queue_autodeploy_release_for_image(image): |
| 124 | + app_env = image.application_environment |
| 125 | + if not app_env or not image.built: |
| 126 | + return None |
| 127 | + |
| 128 | + existing_release = _latest_release_for_image(image) |
| 129 | + if existing_release is not None: |
| 130 | + return existing_release |
| 131 | + |
| 132 | + _clear_image_waiting_for_backing_services(image) |
| 133 | + |
| 134 | + application = image.application |
| 135 | + release = image.application.create_release(app_env=app_env) |
| 136 | + release.release_metadata = { |
| 137 | + **(image.image_metadata or {}), |
| 138 | + "source_image_id": str(image.id), |
| 139 | + } |
| 140 | + db.session.add(release) |
| 141 | + db.session.flush() |
| 142 | + activity = Activity( |
| 143 | + verb="create", |
| 144 | + object=release, |
| 145 | + data={ |
| 146 | + "user_id": "automation", |
| 147 | + "deployment_id": image.image_metadata.get("id", None), |
| 148 | + "description": image.image_metadata.get("description", None), |
| 149 | + "timestamp": datetime.datetime.now(datetime.timezone.utc).isoformat(), |
| 150 | + }, |
| 151 | + ) |
| 152 | + db.session.add(activity) |
| 153 | + db.session.commit() |
| 154 | + |
| 155 | + CheckRun.from_metadata(image.image_metadata, app_env).progress( |
| 156 | + "Building release...", |
| 157 | + detail="Image built, release build starting.", |
| 158 | + details_url=cabotage_url(application, f"releases/{release.id}"), |
| 159 | + Image=f"images/{image.id}", |
| 160 | + Release=f"releases/{release.id}", |
| 161 | + ) |
| 162 | + run_release_build.delay(release_id=release.id) |
| 163 | + try: |
| 164 | + dispatch_autodeploy_notification( |
| 165 | + "release_building", |
| 166 | + image.id, |
| 167 | + application, |
| 168 | + app_env, |
| 169 | + image_url=cabotage_url(application, f"images/{image.id}"), |
| 170 | + release_url=cabotage_url(application, f"releases/{release.id}"), |
| 171 | + image_metadata=image.image_metadata, |
| 172 | + ) |
| 173 | + except Exception: |
| 174 | + log.warning( |
| 175 | + "Failed to dispatch autodeploy release_building notification", |
| 176 | + exc_info=True, |
| 177 | + ) |
| 178 | + return release |
| 179 | + |
| 180 | + |
| 181 | +def resume_branch_deploy_releases_for_environment(environment_id): |
| 182 | + environment = Environment.query.filter_by(id=environment_id).first() |
| 183 | + if ( |
| 184 | + environment is None |
| 185 | + or environment.forked_from_environment_id is None |
| 186 | + or not _branch_deploy_environment_backing_services_ready(environment) |
| 187 | + ): |
| 188 | + return |
| 189 | + |
| 190 | + for app_env in environment.application_environments: |
| 191 | + image = app_env.latest_image |
| 192 | + if not image or not image.image_metadata: |
| 193 | + continue |
| 194 | + if not image.image_metadata.get("auto_deploy"): |
| 195 | + continue |
| 196 | + if not image.image_metadata.get("branch_deploy"): |
| 197 | + continue |
| 198 | + |
| 199 | + if current_app.config.get("CABOTAGE_OMNIBUS_BUILDS"): |
| 200 | + if not image.built and image.image_metadata.get( |
| 201 | + "waiting_for_backing_services" |
| 202 | + ): |
| 203 | + _clear_image_waiting_for_backing_services(image) |
| 204 | + run_omnibus_build.delay(image_id=image.id) |
| 205 | + elif image.built: |
| 206 | + _queue_autodeploy_release_for_image(image) |
| 207 | + |
| 208 | + |
78 | 209 | def _dispatch_image_failure(image, error_detail): |
79 | 210 | try: |
80 | 211 | app = image.application |
@@ -1873,48 +2004,21 @@ def run_image_build(image_id: str, buildkit: bool = False): |
1873 | 2004 | and image.image_metadata.get("auto_deploy", False) |
1874 | 2005 | ): |
1875 | 2006 | app_env = image.application_environment |
1876 | | - release = image.application.create_release(app_env=app_env) |
1877 | | - release.release_metadata = { |
1878 | | - **(image.image_metadata or {}), |
1879 | | - "source_image_id": str(image.id), |
1880 | | - } |
1881 | | - db.session.add(release) |
1882 | | - db.session.flush() |
1883 | | - activity = Activity( |
1884 | | - verb="create", |
1885 | | - object=release, |
1886 | | - data={ |
1887 | | - "user_id": "automation", |
1888 | | - "deployment_id": image.image_metadata.get("id", None), |
1889 | | - "description": image.image_metadata.get("description", None), |
1890 | | - "timestamp": datetime.datetime.now(datetime.timezone.utc).isoformat(), |
1891 | | - }, |
1892 | | - ) |
1893 | | - db.session.add(activity) |
1894 | | - db.session.commit() |
1895 | | - check.progress( |
1896 | | - "Building release...", |
1897 | | - detail="Image built, release build starting.", |
1898 | | - details_url=cabotage_url(application, f"releases/{release.id}"), |
1899 | | - Image=f"images/{image.id}", |
1900 | | - Release=f"releases/{release.id}", |
1901 | | - ) |
1902 | | - run_release_build.delay(release_id=release.id) |
1903 | | - try: |
1904 | | - dispatch_autodeploy_notification( |
1905 | | - "release_building", |
1906 | | - image.id, |
1907 | | - application, |
1908 | | - app_env, |
1909 | | - image_url=cabotage_url(application, f"images/{image.id}"), |
1910 | | - release_url=cabotage_url(application, f"releases/{release.id}"), |
1911 | | - image_metadata=image.image_metadata, |
1912 | | - ) |
1913 | | - except Exception: |
1914 | | - log.warning( |
1915 | | - "Failed to dispatch autodeploy release_building notification", |
1916 | | - exc_info=True, |
| 2007 | + if image.image_metadata.get( |
| 2008 | + "branch_deploy" |
| 2009 | + ) and not _branch_deploy_backing_services_ready(app_env): |
| 2010 | + _mark_image_waiting_for_backing_services(image) |
| 2011 | + check.progress( |
| 2012 | + "Waiting for backing services...", |
| 2013 | + detail=( |
| 2014 | + "Image built successfully. Waiting for backing services " |
| 2015 | + "to become ready before release build." |
| 2016 | + ), |
| 2017 | + details_url=cabotage_url(application, f"images/{image.id}"), |
| 2018 | + Image=f"images/{image.id}", |
1917 | 2019 | ) |
| 2020 | + else: |
| 2021 | + _queue_autodeploy_release_for_image(image) |
1918 | 2022 |
|
1919 | 2023 |
|
1920 | 2024 | @shared_task() |
@@ -2245,6 +2349,24 @@ def run_omnibus_build(image_id: str): |
2245 | 2349 |
|
2246 | 2350 | release = None |
2247 | 2351 | app_env = image.application_environment |
| 2352 | + if ( |
| 2353 | + image.image_metadata |
| 2354 | + and image.image_metadata.get("auto_deploy", False) |
| 2355 | + and image.image_metadata.get("branch_deploy") |
| 2356 | + and not _branch_deploy_backing_services_ready(app_env) |
| 2357 | + ): |
| 2358 | + _mark_image_waiting_for_backing_services(image) |
| 2359 | + check.progress( |
| 2360 | + "Waiting for backing services...", |
| 2361 | + detail=( |
| 2362 | + "Waiting for backing services to become ready before starting " |
| 2363 | + "the branch deploy build." |
| 2364 | + ), |
| 2365 | + details_url=cabotage_url(application, f"images/{image.id}"), |
| 2366 | + Image=f"images/{image.id}", |
| 2367 | + ) |
| 2368 | + return |
| 2369 | + |
2248 | 2370 | try: |
2249 | 2371 | try: |
2250 | 2372 | # Create the release record upfront so build_omnibus_buildkit |
|
0 commit comments