20
20
21
21
from typing import Sequence
22
22
23
+ from google .api_core .exceptions import AlreadyExists
23
24
from google .api_core .gapic_v1 .method import DEFAULT , _MethodDefault
24
25
from google .api_core .operation import Operation
25
26
from google .api_core .retry import Retry
26
- from google .cloud .devtools .cloudbuild import CloudBuildClient
27
+ from google .cloud .devtools .cloudbuild_v1 import CloudBuildAsyncClient , CloudBuildClient , GetBuildRequest
27
28
from google .cloud .devtools .cloudbuild_v1 .types import Build , BuildTrigger , RepoSource
28
29
29
30
from airflow .exceptions import AirflowException
@@ -77,6 +78,14 @@ def _get_build_id_from_operation(self, operation: Operation) -> str:
77
78
except Exception :
78
79
raise AirflowException ("Could not retrieve Build ID from Operation." )
79
80
81
+ def wait_for_operation (self , operation : Operation , timeout : float | None = None ):
82
+ """Waits for long-lasting operation to complete."""
83
+ try :
84
+ return operation .result (timeout = timeout )
85
+ except Exception :
86
+ error = operation .exception (timeout = timeout )
87
+ raise AirflowException (error )
88
+
80
89
def get_conn (self ) -> CloudBuildClient :
81
90
"""
82
91
Retrieves the connection to Google Cloud Build.
@@ -123,6 +132,41 @@ def cancel_build(
123
132
124
133
return build
125
134
135
+ @GoogleBaseHook .fallback_to_default_project_id
136
+ def create_build_without_waiting_for_result (
137
+ self ,
138
+ build : dict | Build ,
139
+ project_id : str = PROVIDE_PROJECT_ID ,
140
+ retry : Retry | _MethodDefault = DEFAULT ,
141
+ timeout : float | None = None ,
142
+ metadata : Sequence [tuple [str , str ]] = (),
143
+ ) -> tuple [Operation , str ]:
144
+ """
145
+ Starts a build with the specified configuration without waiting for it to finish.
146
+
147
+ :param build: The build resource to create. If a dict is provided, it must be of the same form
148
+ as the protobuf message `google.cloud.devtools.cloudbuild_v1.types.Build`
149
+ :param project_id: Optional, Google Cloud Project project_id where the function belongs.
150
+ If set to None or missing, the default project_id from the GCP connection is used.
151
+ :param retry: Optional, a retry object used to retry requests. If `None` is specified, requests
152
+ will not be retried.
153
+ :param timeout: Optional, the amount of time, in seconds, to wait for the request to complete.
154
+ Note that if `retry` is specified, the timeout applies to each individual attempt.
155
+ :param metadata: Optional, additional metadata that is provided to the method.
156
+ """
157
+ client = self .get_conn ()
158
+
159
+ self .log .info ("Start creating build..." )
160
+
161
+ operation = client .create_build (
162
+ request = {"project_id" : project_id , "build" : build },
163
+ retry = retry ,
164
+ timeout = timeout ,
165
+ metadata = metadata ,
166
+ )
167
+ id_ = self ._get_build_id_from_operation (operation )
168
+ return operation , id_
169
+
126
170
@GoogleBaseHook .fallback_to_default_project_id
127
171
def create_build (
128
172
self ,
@@ -150,7 +194,7 @@ def create_build(
150
194
"""
151
195
client = self .get_conn ()
152
196
153
- self .log .info ("Start creating build." )
197
+ self .log .info ("Start creating build... " )
154
198
155
199
operation = client .create_build (
156
200
request = {"project_id" : project_id , "build" : build },
@@ -195,14 +239,17 @@ def create_build_trigger(
195
239
"""
196
240
client = self .get_conn ()
197
241
198
- self .log .info ("Start creating build trigger." )
242
+ self .log .info ("Start creating build trigger... " )
199
243
200
- trigger = client .create_build_trigger (
201
- request = {"project_id" : project_id , "trigger" : trigger },
202
- retry = retry ,
203
- timeout = timeout ,
204
- metadata = metadata ,
205
- )
244
+ try :
245
+ trigger = client .create_build_trigger (
246
+ request = {"project_id" : project_id , "trigger" : trigger },
247
+ retry = retry ,
248
+ timeout = timeout ,
249
+ metadata = metadata ,
250
+ )
251
+ except AlreadyExists :
252
+ raise AirflowException ("Cloud Build Trigger with such parameters already exists." )
206
253
207
254
self .log .info ("Build trigger has been created." )
208
255
@@ -492,7 +539,6 @@ def run_build_trigger(
492
539
client = self .get_conn ()
493
540
494
541
self .log .info ("Start running build trigger: %s." , trigger_id )
495
-
496
542
operation = client .run_build_trigger (
497
543
request = {"project_id" : project_id , "trigger_id" : trigger_id , "source" : source },
498
544
retry = retry ,
@@ -504,7 +550,6 @@ def run_build_trigger(
504
550
505
551
if not wait :
506
552
return self .get_build (id_ = id_ , project_id = project_id )
507
-
508
553
operation .result ()
509
554
510
555
self .log .info ("Build trigger has been run: %s." , trigger_id )
@@ -550,3 +595,34 @@ def update_build_trigger(
550
595
self .log .info ("Build trigger has been updated: %s." , trigger_id )
551
596
552
597
return trigger
598
+
599
+
600
+ class CloudBuildAsyncHook (GoogleBaseHook ):
601
+ """Asynchronous Hook for the Google Cloud Build Service."""
602
+
603
+ @GoogleBaseHook .fallback_to_default_project_id
604
+ async def get_cloud_build (
605
+ self ,
606
+ id_ : str ,
607
+ project_id : str = PROVIDE_PROJECT_ID ,
608
+ retry : Retry | _MethodDefault = DEFAULT ,
609
+ timeout : float | None = None ,
610
+ metadata : Sequence [tuple [str , str ]] = (),
611
+ ) -> Build :
612
+ """Retrieves a Cloud Build with a specified id."""
613
+ if not id_ :
614
+ raise AirflowException ("Google Cloud Build id is required." )
615
+
616
+ client = CloudBuildAsyncClient ()
617
+
618
+ request = GetBuildRequest (
619
+ project_id = project_id ,
620
+ id = id_ ,
621
+ )
622
+ build_instance = await client .get_build (
623
+ request = request ,
624
+ retry = retry ,
625
+ timeout = timeout ,
626
+ metadata = metadata ,
627
+ )
628
+ return build_instance
0 commit comments