alien_bindings/providers/artifact_registry/
grpc.rs1use crate::{
2 error::{Error, ErrorData},
3 grpc::artifact_registry_service::alien_bindings::artifact_registry::{
4 artifact_registry_service_client::ArtifactRegistryServiceClient,
5 AddCrossAccountAccessRequest, ArtifactRegistryPermissions as ProtoRegistryPermissions,
6 AwsCrossAccountAccess as ProtoAwsCrossAccountAccess,
7 ComputeServiceType as ProtoComputeServiceType, CreateRepositoryRequest,
8 CrossAccountAccess as ProtoCrossAccountAccess, DeleteRepositoryRequest,
9 GcpCrossAccountAccess as ProtoGcpCrossAccountAccess, GenerateCredentialsRequest,
10 GetCrossAccountAccessRequest, GetRepositoryRequest, RemoveCrossAccountAccessRequest,
11 },
12 grpc::status_conversion::status_to_alien_error,
13 traits::{
14 ArtifactRegistry, ArtifactRegistryCredentials, ArtifactRegistryPermissions,
15 AwsCrossAccountAccess, ComputeServiceType, CrossAccountAccess, CrossAccountPermissions,
16 GcpCrossAccountAccess, RepositoryResponse,
17 },
18 Binding,
19};
20
21use alien_error::{AlienError, Context, IntoAlienError};
22use async_trait::async_trait;
23use tonic::{transport::Channel, Request, Status};
24
25#[derive(Debug)]
30pub struct GrpcArtifactRegistry {
31 client: ArtifactRegistryServiceClient<Channel>,
32 binding_name: String,
33}
34
35impl GrpcArtifactRegistry {
36 pub async fn new(binding_name: String, grpc_address: String) -> Result<Self, Error> {
38 let channel = crate::providers::grpc_provider::create_grpc_channel(grpc_address).await?;
39 Self::new_from_channel(channel, binding_name).await
40 }
41
42 pub async fn new_from_channel(channel: Channel, binding_name: String) -> Result<Self, Error> {
44 let client = ArtifactRegistryServiceClient::new(channel);
45
46 Ok(Self {
47 client,
48 binding_name,
49 })
50 }
51
52 fn client(&self) -> ArtifactRegistryServiceClient<Channel> {
53 self.client.clone()
54 }
55
56 fn convert_cross_account_access_to_proto(
58 access: &CrossAccountAccess,
59 ) -> ProtoCrossAccountAccess {
60 match access {
61 CrossAccountAccess::Aws(aws) => {
62 let proto_service_types: Vec<i32> = aws
63 .allowed_service_types
64 .iter()
65 .map(|st| match st {
66 ComputeServiceType::Function => ProtoComputeServiceType::Function as i32,
67 })
68 .collect();
69
70 ProtoCrossAccountAccess {
71 access: Some(crate::grpc::artifact_registry_service::alien_bindings::artifact_registry::cross_account_access::Access::Aws(
72 ProtoAwsCrossAccountAccess {
73 account_ids: aws.account_ids.clone(),
74 allowed_service_types: proto_service_types,
75 role_arns: aws.role_arns.clone(),
76 regions: aws.regions.clone(),
77 }
78 )),
79 }
80 }
81 CrossAccountAccess::Gcp(gcp) => {
82 let proto_service_types: Vec<i32> = gcp
83 .allowed_service_types
84 .iter()
85 .map(|st| match st {
86 ComputeServiceType::Function => ProtoComputeServiceType::Function as i32,
87 })
88 .collect();
89
90 ProtoCrossAccountAccess {
91 access: Some(crate::grpc::artifact_registry_service::alien_bindings::artifact_registry::cross_account_access::Access::Gcp(
92 ProtoGcpCrossAccountAccess {
93 project_numbers: gcp.project_numbers.clone(),
94 allowed_service_types: proto_service_types,
95 service_account_emails: gcp.service_account_emails.clone(),
96 }
97 )),
98 }
99 }
100 }
101 }
102
103 fn convert_proto_to_cross_account_permissions(
105 proto: crate::grpc::artifact_registry_service::alien_bindings::artifact_registry::CrossAccountPermissions,
106 ) -> Result<CrossAccountPermissions, Error> {
107 let access_proto = proto.access.ok_or_else(|| {
108 AlienError::new(ErrorData::UnexpectedResponseFormat {
109 provider: "grpc".to_string(),
110 binding_name: "artifact_registry".to_string(),
111 field: "access".to_string(),
112 response_json: "missing access field".to_string(),
113 })
114 })?;
115
116 let access = match access_proto.access {
117 Some(crate::grpc::artifact_registry_service::alien_bindings::artifact_registry::cross_account_access::Access::Aws(aws)) => {
118 let service_types: Vec<ComputeServiceType> = aws.allowed_service_types.iter()
119 .filter_map(|&st| match ProtoComputeServiceType::try_from(st) {
120 Ok(ProtoComputeServiceType::Function) => Some(ComputeServiceType::Function),
121 _ => None,
122 })
123 .collect();
124
125 CrossAccountAccess::Aws(AwsCrossAccountAccess {
126 account_ids: aws.account_ids,
127 allowed_service_types: service_types,
128 role_arns: aws.role_arns,
129 regions: vec![],
130 })
131 }
132 Some(crate::grpc::artifact_registry_service::alien_bindings::artifact_registry::cross_account_access::Access::Gcp(gcp)) => {
133 let service_types: Vec<ComputeServiceType> = gcp.allowed_service_types.iter()
134 .filter_map(|&st| match ProtoComputeServiceType::try_from(st) {
135 Ok(ProtoComputeServiceType::Function) => Some(ComputeServiceType::Function),
136 _ => None,
137 })
138 .collect();
139
140 CrossAccountAccess::Gcp(GcpCrossAccountAccess {
141 project_numbers: gcp.project_numbers,
142 allowed_service_types: service_types,
143 service_account_emails: gcp.service_account_emails,
144 })
145 }
146 None => {
147 return Err(AlienError::new(ErrorData::UnexpectedResponseFormat {
148 provider: "grpc".to_string(),
149 binding_name: "artifact_registry".to_string(),
150 field: "access".to_string(),
151 response_json: "missing access variant".to_string(),
152 }));
153 }
154 };
155
156 Ok(CrossAccountPermissions {
157 access,
158 last_updated: proto.last_updated,
159 })
160 }
161}
162
163impl Binding for GrpcArtifactRegistry {}
164
165#[async_trait]
166impl ArtifactRegistry for GrpcArtifactRegistry {
167 async fn create_repository(&self, repo_name: &str) -> Result<RepositoryResponse, Error> {
168 let mut client = self.client();
169
170 let request = CreateRepositoryRequest {
171 binding_name: self.binding_name.clone(),
172 repo_name: repo_name.to_string(),
173 };
174
175 let response = client
176 .create_repository(Request::new(request))
177 .await
178 .map_err(|e| status_to_alien_error(e, "create_repository"))?
179 .into_inner();
180
181 let result = response.result.ok_or_else(|| {
182 AlienError::new(ErrorData::UnexpectedResponseFormat {
183 provider: "grpc".to_string(),
184 binding_name: self.binding_name.clone(),
185 field: "result".to_string(),
186 response_json: "missing result field".to_string(),
187 })
188 })?;
189
190 Ok(RepositoryResponse {
191 name: result.name,
192 uri: result.uri,
193 created_at: result.created_at,
194 })
195 }
196
197 async fn get_repository(&self, repo_id: &str) -> Result<RepositoryResponse, Error> {
198 let mut client = self.client();
199
200 let request = GetRepositoryRequest {
201 binding_name: self.binding_name.clone(),
202 repo_id: repo_id.to_string(),
203 };
204
205 let response = client
206 .get_repository(Request::new(request))
207 .await
208 .map_err(|e| status_to_alien_error(e, "get_repository"))?
209 .into_inner();
210
211 let result = response.result.ok_or_else(|| {
212 AlienError::new(ErrorData::UnexpectedResponseFormat {
213 provider: "grpc".to_string(),
214 binding_name: self.binding_name.clone(),
215 field: "result".to_string(),
216 response_json: "missing result field".to_string(),
217 })
218 })?;
219
220 Ok(RepositoryResponse {
221 name: result.name,
222 uri: result.uri,
223 created_at: result.created_at,
224 })
225 }
226
227 async fn add_cross_account_access(
228 &self,
229 repo_id: &str,
230 access: CrossAccountAccess,
231 ) -> Result<(), Error> {
232 let mut client = self.client();
233
234 let proto_access = Self::convert_cross_account_access_to_proto(&access);
235
236 let request = AddCrossAccountAccessRequest {
237 binding_name: self.binding_name.clone(),
238 repo_id: repo_id.to_string(),
239 access: Some(proto_access),
240 };
241
242 client
243 .add_cross_account_access(Request::new(request))
244 .await
245 .map_err(|e| status_to_alien_error(e, "add_cross_account_access"))?;
246
247 Ok(())
248 }
249
250 async fn remove_cross_account_access(
251 &self,
252 repo_id: &str,
253 access: CrossAccountAccess,
254 ) -> Result<(), Error> {
255 let mut client = self.client();
256
257 let proto_access = Self::convert_cross_account_access_to_proto(&access);
258
259 let request = RemoveCrossAccountAccessRequest {
260 binding_name: self.binding_name.clone(),
261 repo_id: repo_id.to_string(),
262 access: Some(proto_access),
263 };
264
265 client
266 .remove_cross_account_access(Request::new(request))
267 .await
268 .map_err(|e| status_to_alien_error(e, "remove_cross_account_access"))?;
269
270 Ok(())
271 }
272
273 async fn get_cross_account_access(
274 &self,
275 repo_id: &str,
276 ) -> Result<CrossAccountPermissions, Error> {
277 let mut client = self.client();
278
279 let request = GetCrossAccountAccessRequest {
280 binding_name: self.binding_name.clone(),
281 repo_id: repo_id.to_string(),
282 };
283
284 let response = client
285 .get_cross_account_access(Request::new(request))
286 .await
287 .map_err(|e| status_to_alien_error(e, "get_cross_account_access"))?
288 .into_inner();
289
290 let permissions = response.permissions.ok_or_else(|| {
291 AlienError::new(ErrorData::UnexpectedResponseFormat {
292 provider: "grpc".to_string(),
293 binding_name: self.binding_name.clone(),
294 field: "permissions".to_string(),
295 response_json: "missing permissions field".to_string(),
296 })
297 })?;
298
299 Self::convert_proto_to_cross_account_permissions(permissions)
300 }
301
302 async fn generate_credentials(
303 &self,
304 repo_id: &str,
305 permissions: ArtifactRegistryPermissions,
306 ttl_seconds: Option<u32>,
307 ) -> Result<ArtifactRegistryCredentials, Error> {
308 let mut client = self.client();
309
310 let proto_permissions = match permissions {
311 ArtifactRegistryPermissions::Pull => ProtoRegistryPermissions::Pull,
312 ArtifactRegistryPermissions::PushPull => ProtoRegistryPermissions::PushPull,
313 };
314
315 let request = GenerateCredentialsRequest {
316 binding_name: self.binding_name.clone(),
317 repo_id: repo_id.to_string(),
318 permissions: proto_permissions.into(),
319 ttl_seconds,
320 };
321
322 let response = client
323 .generate_credentials(Request::new(request))
324 .await
325 .map_err(|e| status_to_alien_error(e, "generate_credentials"))?
326 .into_inner();
327
328 let credentials = response.credentials.ok_or_else(|| {
329 AlienError::new(ErrorData::UnexpectedResponseFormat {
330 provider: "grpc".to_string(),
331 binding_name: self.binding_name.clone(),
332 field: "credentials".to_string(),
333 response_json: "missing credentials field".to_string(),
334 })
335 })?;
336
337 Ok(ArtifactRegistryCredentials {
338 username: credentials.username,
339 password: credentials.password,
340 expires_at: credentials.expires_at,
341 })
342 }
343
344 async fn delete_repository(&self, repo_id: &str) -> Result<(), Error> {
345 let mut client = self.client();
346
347 let request = DeleteRepositoryRequest {
348 binding_name: self.binding_name.clone(),
349 repo_id: repo_id.to_string(),
350 };
351
352 client
353 .delete_repository(Request::new(request))
354 .await
355 .map_err(|e| status_to_alien_error(e, "delete_repository"))?;
356
357 Ok(())
358 }
359}