Bài 23: Tạo API gateway
API gateway là một thành phần trong ứng dụng của chúng ta cung cấp khả năng xử lý tập trung giao tiếp API giữa client và nhiều dịch vụ cần thiết.
API gateway có thể được triển khai thông qua GraphQL Persisted Queries được lưu trữ trên máy chủ và được client gọi đến, tương tác với một hoặc nhiều dịch vụ backend, thu thập kết quả và trả về cho client trong một phản hồi duy nhất.
Dưới đây là một số lợi ích khi sử dụng GraphQL Persisted Queries để cung cấp API gateway:
- Client không cần xử lý các kết nối đến dịch vụ backend, do đó đơn giản hóa logic của chúng
- Truy cập đến các dịch vụ backend được tập trung hóa
- Không có thông tin xác thực nào bị lộ trên client
- Phản hồi từ dịch vụ có thể được chuyển đổi sang định dạng mà client mong đợi hoặc có thể xử lý tốt hơn
- Nếu một dịch vụ backend được nâng cấp, Persisted Query có thể được điều chỉnh mà không tạo ra các thay đổi gây hỏng trên client
- Máy chủ có thể lưu trữ nhật ký truy cập vào các dịch vụ backend và trích xuất các số liệu để cải thiện phân tích
Bài học hướng dẫn này minh họa một API gateway lấy các artifact mới nhất từ GitHub Actions API và trích xuất URL để tải xuống, tránh yêu cầu client phải đăng nhập vào GitHub.
API gateway sử dụng GraphQL để truy cập các artifact GitHub Actions
Query GraphQL dưới đây phải được lưu trữ dưới dạng Persisted Query (ví dụ: sử dụng slug retrieve-public-urls-for-github-actions-artifacts).
Nó lấy các URL tải xuống có thể truy cập công khai cho các artifact GitHub Actions:
- Đầu tiên, nó lấy X artifact mới nhất từ GitHub Actions và trích xuất URL proxy để truy cập từng artifact. (Vì chỉ những người dùng đã xác thực mới có thể truy cập artifact, những URL này chưa trỏ đến artifact thực tế.)
- Sau đó, nó truy cập từng URL proxy này (có artifact được tải lên một vị trí công khai trong một khoảng thời gian ngắn) và trích xuất URL thực tế từ header
Locationcủa phản hồi HTTP - Cuối cùng, nó in tất cả các URL có thể truy cập công khai, cho phép người dùng chưa xác thực tải xuống artifact GitHub trong khoảng thời gian đó
(Bài học hướng dẫn kết thúc tại đây, nhưng như một phần tiếp theo, query GraphQL có thể thực hiện điều gì đó với các URL này: gửi chúng qua email, tải tệp lên qua FTP, cài đặt chúng trong một trang InstaWP, v.v.)
query RetrieveGitHubAccessToken {
githubAccessToken: _env(name: "GITHUB_ACCESS_TOKEN")
@export(as: "githubAccessToken")
@remove
}
query RetrieveProxyArtifactDownloadURLs($numberArtifacts: Int! = 3)
@depends(on: "RetrieveGitHubAccessToken")
{
githubArtifactsEndpoint: _sprintf(
string: "https://api.github.com/repos/GatoGraphQL/GatoGraphQL/actions/artifacts?per_page=%s",
values: [$numberArtifacts]
)
@remove
# Retrieve Artifact data from GitHub Actions API
gitHubArtifactData: _sendJSONObjectItemHTTPRequest(
input: {
url: $__githubArtifactsEndpoint,
options: {
auth: {
password: $githubAccessToken
},
headers: [
{
name: "Accept",
value: "application/vnd.github+json"
}
]
}
}
)
@remove
# Extract the URL from within each "artifacts" item
gitHubProxyArtifactDownloadURLs: _objectProperty(
object: $__gitHubArtifactData,
by: {
key: "artifacts"
}
)
@underEachArrayItem(passValueOnwardsAs: "artifactItem")
@applyField(
name: "_objectProperty",
arguments: {
object: $artifactItem,
by: {
key: "archive_download_url"
}
},
setResultInResponse: true
)
@export(as: "gitHubProxyArtifactDownloadURLs")
}
query CreateHTTPRequestInputs
@depends(on: "RetrieveProxyArtifactDownloadURLs")
{
httpRequestInputs: _echo(value: $gitHubProxyArtifactDownloadURLs)
@underEachArrayItem(
passValueOnwardsAs: "url"
)
@applyField(
name: "_objectAddEntry",
arguments: {
object: {
options: {
auth: {
password: $githubAccessToken
},
headers: {
name: "Accept",
value: "application/vnd.github+json"
},
allowRedirects: null
}
},
key: "url",
value: $url
},
setResultInResponse: true
)
@export(as: "httpRequestInputs")
@remove
}
query RetrieveActualArtifactDownloadURLs
@depends(on: "CreateHTTPRequestInputs")
{
_sendHTTPRequests(
inputs: $httpRequestInputs
) {
artifactDownloadURL: header(name: "Location")
@export(as: "artifactDownloadURLs", type: LIST)
}
}
query PrintArtifactDownloadURLsAsList
@depends(on: "RetrieveActualArtifactDownloadURLs")
{
artifactDownloadURLs: _echo(value: $artifactDownloadURLs)
}Phản hồi là:
{
"data": {
"gitHubProxyArtifactDownloadURLs": [
"https://api.github.com/repos/GatoGraphQL/GatoGraphQL/actions/artifacts/803444209/zip",
"https://api.github.com/repos/GatoGraphQL/GatoGraphQL/actions/artifacts/803444208/zip",
"https://api.github.com/repos/GatoGraphQL/GatoGraphQL/actions/artifacts/803444207/zip"
],
"_sendHTTPRequests": [
{
"artifactDownloadURL": "https://pipelines.actions.githubusercontent.com/serviceHosts/a6be3ecc-6518-4aaa-b5ec-232be0438a37/_apis/pipelines/1/runs/53473/signedartifactscontent?artifactName=gato-graphql-testing-schema-1.0.0-dev&urlExpires=2023-07-14T03%3A31%3A00.9351393Z&urlSigningMethod=HMACV2&urlSignature=8v8cDVZKAnkXoN8z1GdjXLz4SCGkpv%2Fl0qjlDArac5M%3D"
},
{
"artifactDownloadURL": "https://pipelines.actions.githubusercontent.com/serviceHosts/a6be3ecc-6518-4aaa-b5ec-232be0438a37/_apis/pipelines/1/runs/53473/signedartifactscontent?artifactName=gato-graphql-testing-1.0.0-dev&urlExpires=2023-07-14T03%3A31%3A00.9333471Z&urlSigningMethod=HMACV2&urlSignature=ffsyy0p97oeQByMD3X6WKbFyIEbh6nbU%2BFsXKHQHYSM%3D"
},
{
"artifactDownloadURL": "https://pipelines.actions.githubusercontent.com/serviceHosts/a6be3ecc-6518-4aaa-b5ec-232be0438a37/_apis/pipelines/1/runs/53473/signedartifactscontent?artifactName=gato-graphql-1.0.0-dev&urlExpires=2023-07-14T03%3A31%3A00.9699160Z&urlSigningMethod=HMACV2&urlSignature=gUi%2F39RS7X5YgVZbEu977ufFt1girQKeNI7LP61gxfY%3D"
}
],
"artifactDownloadURLs": [
"https://pipelines.actions.githubusercontent.com/serviceHosts/a6be3ecc-6518-4aaa-b5ec-232be0438a37/_apis/pipelines/1/runs/53473/signedartifactscontent?artifactName=gato-graphql-testing-schema-1.0.0-dev&urlExpires=2023-07-14T03%3A31%3A00.9351393Z&urlSigningMethod=HMACV2&urlSignature=8v8cDVZKAnkXoN8z1GdjXLz4SCGkpv%2Fl0qjlDArac5M%3D",
"https://pipelines.actions.githubusercontent.com/serviceHosts/a6be3ecc-6518-4aaa-b5ec-232be0438a37/_apis/pipelines/1/runs/53473/signedartifactscontent?artifactName=gato-graphql-testing-1.0.0-dev&urlExpires=2023-07-14T03%3A31%3A00.9333471Z&urlSigningMethod=HMACV2&urlSignature=ffsyy0p97oeQByMD3X6WKbFyIEbh6nbU%2BFsXKHQHYSM%3D",
"https://pipelines.actions.githubusercontent.com/serviceHosts/a6be3ecc-6518-4aaa-b5ec-232be0438a37/_apis/pipelines/1/runs/53473/signedartifactscontent?artifactName=gato-graphql-1.0.0-dev&urlExpires=2023-07-14T03%3A31%3A00.9699160Z&urlSigningMethod=HMACV2&urlSignature=gUi%2F39RS7X5YgVZbEu977ufFt1girQKeNI7LP61gxfY%3D"
]
}
}Phương án thay thế: Lấy thông tin xác thực GitHub từ yêu cầu HTTP
Chúng ta cũng có thể cho phép người dùng cung cấp thông tin xác thực GitHub của riêng họ qua header.
Query GraphQL này là một sự điều chỉnh của query trước đó, với các điểm khác biệt sau:
- Thao tác
RetrieveGitHubAccessTokenđọc và xuất giá trị từ headerX-Github-Access-Tokencủa yêu cầu HTTP hiện tại, và cho biết nếu header này chưa được cung cấp FailIfGitHubAccessTokenIsMissingkích hoạt lỗi khi header bị thiếu- Tất cả các thao tác khác đã được thêm directive
@skip(if: $isGithubAccessTokenMissing), để chúng sẽ không được thực thi khi token bị thiếu
query RetrieveGitHubAccessToken {
githubAccessToken: _httpRequestHeader(name: "X-Github-Access-Token")
@export(as: "githubAccessToken")
@remove
isGithubAccessTokenMissing: _isEmpty(value: $__githubAccessToken)
@export(as: "isGithubAccessTokenMissing")
}
query FailIfGitHubAccessTokenIsMissing
@depends(on: "RetrieveGitHubAccessToken")
@include(if: $isGithubAccessTokenMissing)
{
_fail(
message: "Header 'X-Github-Access-Token' has not been provided"
) @remove
}
query RetrieveProxyArtifactDownloadURLs($numberArtifacts: Int! = 3)
@depends(on: "RetrieveGitHubAccessToken")
@skip(if: $isGithubAccessTokenMissing)
{
# Do same as before
# ...
}
query CreateHTTPRequestInputs
@depends(on: "RetrieveProxyArtifactDownloadURLs")
@skip(if: $isGithubAccessTokenMissing)
{
# Do same as before
# ...
}
query RetrieveActualArtifactDownloadURLs
@depends(on: "CreateHTTPRequestInputs")
@skip(if: $isGithubAccessTokenMissing)
{
# Do same as before
# ...
}
query PrintArtifactDownloadURLsAsList
@depends(on: [
"RetrieveActualArtifactDownloadURLs",
"FailIfGitHubAccessTokenIsMissing"
])
@skip(if: $isGithubAccessTokenMissing)
{
# Do same as before
# ...
}Khi header X-Github-Access-Token được cung cấp, phản hồi sẽ giống như trên.
Khi nó không được cung cấp, phản hồi sẽ là:
{
"errors": [
{
"message": "Header 'X-Github-Access-Token' has not been provided",
"locations": [
{
"line": 18,
"column": 3
}
],
"extensions": {
"path": [
"_fail(message: \"Header 'X-Github-Access-Token' has not been provided\") @remove",
"query FailIfGitHubAccessTokenIsMissing @depends(on: \"ValidateHasGitHubAccessToken\") @skip(if: $isGithubAccessTokenMissing) { ... }"
],
"type": "QueryRoot",
"field": "_fail(message: \"Header 'X-Github-Access-Token' has not been provided\") @remove",
"id": "root",
"code": "PoPSchema/FailFieldAndDirective@e1"
}
}
],
"data": {
"isGithubAccessTokenMissing": false
}
}Chúng ta có thể lấy từ các header thông tin xác thực cho nhiều dịch vụ được sử dụng trong API gateway, đồng thời xác nhận rằng tất cả chúng đã được cung cấp:
query RetrieveServiceTokens {
githubAccessToken: _httpRequestHeader(name: "X-Github-Access-Token")
@export(as: "githubAccessToken")
slackAccessToken: _httpRequestHeader(name: "X-Slack-Access-Token")
@export(as: "slackAccessToken")
isGithubAccessTokenMissing: _isEmpty(value: $__githubAccessToken)
isSlackAccessTokenMissing: _isEmpty(value: $__slackAccessToken)
isAnyAccessTokenMissing: _or(values: [
$__isGithubAccessTokenMissing,
$__isSlackAccessTokenMissing
])
@export(as: "isAnyAccessTokenMissing")
}
query FailIfAnyAccessTokenMissing
@depends(on: "RetrieveServiceTokens")
@include(if: $isAnyAccessTokenMissing)
{
_fail(
message: "Access tokens for GitHub and Slack must be provided"
) @remove
}
query RetrieveProxyArtifactDownloadURLs
@depends(on: "RetrieveServiceTokens")
@skip(if: $isAnyAccessTokenMissing)
{
# Do something
# ...
}
# Do something
# ...Từng bước: tạo query GraphQL
Dưới đây là phân tích chi tiết về cách thức hoạt động của query.
Endpoint để kết nối có thể được tạo động, trong trường hợp này sử dụng _sprintf:
query RetrieveProxyArtifactDownloadURLs($numberArtifacts: Int! = 3)
@depends(on: "RetrieveGitHubAccessToken")
{
githubArtifactsEndpoint: _sprintf(
string: "https://api.github.com/repos/GatoGraphQL/GatoGraphQL/actions/artifacts?per_page=%s",
values: [$numberArtifacts]
)
@remove
# ...
}Phản hồi từ GitHub Actions API rất cồng kềnh và không có ích với chúng ta, vì vậy chúng ta @remove nó khỏi phản hồi. Tuy nhiên, trong quá trình phát triển, chúng ta tắt directive này để trực quan hóa và hiểu cấu trúc của đối tượng JSON được trả về, và xác định các mục dữ liệu chúng ta cần trích xuất:
query RetrieveProxyArtifactDownloadURLs($numberArtifacts: Int! = 3)
@depends(on: "RetrieveGitHubAccessToken")
{
# ...
# Retrieve Artifact data from GitHub Actions API
gitHubArtifactData: _sendJSONObjectItemHTTPRequest(
input: {
url: $__githubArtifactsEndpoint,
options: {
auth: {
password: $githubAccessToken
},
headers: [
{
name: "Accept",
value: "application/vnd.github+json"
}
]
}
}
)
# @remove <= Disabled to visualize output
}Phản hồi là:
{
"data": {
"gitHubArtifactData": {
"total_count": 8344,
"artifacts": [
{
"id": 803739808,
"node_id": "MDg6QXJ0aWZhY3Q4MDM3Mzk4MDg=",
"name": "gato-graphql-testing-schema-1.0.0-dev",
"size_in_bytes": 62952,
"url": "https://api.github.com/repos/GatoGraphQL/GatoGraphQL/actions/artifacts/803739808",
"archive_download_url": "https://api.github.com/repos/GatoGraphQL/GatoGraphQL/actions/artifacts/803739808/zip",
"expired": false,
"created_at": "2023-07-14T06:25:57Z",
"updated_at": "2023-07-14T06:25:59Z",
"expires_at": "2023-08-13T06:17:15Z",
"workflow_run": {
"id": 5551097653,
"repository_id": 66721227,
"head_repository_id": 66721227,
"head_branch": "Enable-headers-in-GraphiQL",
"head_sha": "31e69ccab2a8d1fdea942e71f7a93ec484bdd9c8"
}
},
{
"id": 803739806,
"node_id": "MDg6QXJ0aWZhY3Q4MDM3Mzk4MDY=",
"name": "gato-graphql-testing-1.0.0-dev",
"size_in_bytes": 123914,
"url": "https://api.github.com/repos/GatoGraphQL/GatoGraphQL/actions/artifacts/803739806",
"archive_download_url": "https://api.github.com/repos/GatoGraphQL/GatoGraphQL/actions/artifacts/803739806/zip",
"expired": false,
"created_at": "2023-07-14T06:25:57Z",
"updated_at": "2023-07-14T06:25:59Z",
"expires_at": "2023-08-13T06:17:11Z",
"workflow_run": {
"id": 5551097653,
"repository_id": 66721227,
"head_repository_id": 66721227,
"head_branch": "Enable-headers-in-GraphiQL",
"head_sha": "31e69ccab2a8d1fdea942e71f7a93ec484bdd9c8"
}
},
{
"id": 803739803,
"node_id": "MDg6QXJ0aWZhY3Q4MDM3Mzk4MDM=",
"name": "gato-graphql-1.0.0-dev",
"size_in_bytes": 33394234,
"url": "https://api.github.com/repos/GatoGraphQL/GatoGraphQL/actions/artifacts/803739803",
"archive_download_url": "https://api.github.com/repos/GatoGraphQL/GatoGraphQL/actions/artifacts/803739803/zip",
"expired": false,
"created_at": "2023-07-14T06:25:57Z",
"updated_at": "2023-07-14T06:25:59Z",
"expires_at": "2023-08-13T06:21:42Z",
"workflow_run": {
"id": 5551097653,
"repository_id": 66721227,
"head_repository_id": 66721227,
"head_branch": "Enable-headers-in-GraphiQL",
"head_sha": "31e69ccab2a8d1fdea942e71f7a93ec484bdd9c8"
}
}
]
}
}
}Mục dữ liệu chúng ta quan tâm là thuộc tính "archive_download_url". Chúng ta điều hướng đến từng mục dữ liệu này trong cấu trúc đối tượng JSON, trích xuất giá trị đó bằng field _objectProperty (được áp dụng qua directive @applyField), và ghi đè phần tử đang được lặp qua bằng cách truyền đối số setResultInResponse: true:
query RetrieveProxyArtifactDownloadURLs($numberArtifacts: Int! = 3)
@depends(on: "RetrieveGitHubAccessToken")
{
# ...
# Extract the URL from within each "artifacts" item
gitHubProxyArtifactDownloadURLs: _objectProperty(
object: $__gitHubArtifactData,
by: {
key: "artifacts"
}
)
@underEachArrayItem(passValueOnwardsAs: "artifactItem")
@applyField(
name: "_objectProperty",
arguments: {
object: $artifactItem,
by: {
key: "archive_download_url"
}
},
setResultInResponse: true
)
@export(as: "gitHubProxyArtifactDownloadURLs")
}Chúng ta kết nối đồng thời đến tất cả các URL artifact đã trích xuất thông qua field _sendHTTPRequests (gửi nhiều yêu cầu HTTP không đồng bộ), và chúng ta truy vấn header Location từ mỗi phản hồi.
Vì field _sendHTTPRequests nhận đối số input (kiểu [HTTPRequestInput]), chúng ta tạo động input này bằng cách:
- Lặp qua từng URL artifact (được lưu trong biến động
$gitHubProxyArtifactDownloadURLs) - Tạo động một đối tượng JSON cho từng artifact (sử dụng field
_objectAddEntry) chứa tất cả các tham số cần thiết (headers, xác thực và các tham số khác) - Thêm URL vào đối tượng JSON này (có sẵn trong biến động
$url)
Danh sách các đối tượng JSON được tạo động này sẽ được ép kiểu thành [HTTPRequestInput] khi được truyền làm đối số cho _sendHTTPRequests(input:). Nếu quy trình của chúng ta không đúng và bất kỳ mục nào không thể được ép kiểu thành HTTPRequestInput (ví dụ: vì chúng ta không cung cấp thuộc tính bắt buộc, hoặc cung cấp thuộc tính không tồn tại), thì máy chủ GraphQL sẽ tạo ra lỗi ép kiểu.
Lưu ý rằng chúng ta phải @remove field httpRequestInputs, vì nó chứa token GitHub (trong password: $githubAccessToken), mà chúng ta không muốn in ra trong phản hồi. Tuy nhiên, trong quá trình phát triển, chúng ta có thể tắt directive này.
query CreateHTTPRequestInputs
@depends(on: "RetrieveProxyArtifactDownloadURLs")
{
httpRequestInputs: _echo(value: $gitHubProxyArtifactDownloadURLs)
@underEachArrayItem(
passValueOnwardsAs: "url"
)
@applyField(
name: "_objectAddEntry",
arguments: {
object: {
options: {
auth: {
password: $githubAccessToken
},
headers: {
name: "Accept",
value: "application/vnd.github+json"
},
allowRedirects: null
}
},
key: "url",
value: $url
},
setResultInResponse: true
)
@export(as: "httpRequestInputs")
# @remove <= Disabled to visualize output
}
query RetrieveActualArtifactDownloadURLs
@depends(on: "CreateHTTPRequestInputs")
{
_sendHTTPRequests(
inputs: $httpRequestInputs
) {
artifactDownloadURL: header(name: "Location")
@export(as: "artifactDownloadURLs", type: LIST)
}
}Vì @remove hiện đã được comment out, chúng ta có thể trực quan hóa các input đối tượng JSON được tạo trong phản hồi (dưới mục httpRequestInputs), và sau đó là header Location kết quả từ mỗi phản hồi HTTP (dưới alias artifactDownloadURL):
{
"data": {
"gitHubProxyArtifactDownloadURLs": [
// ...
],
"httpRequestInputs": [
{
"options": {
"auth": {
"password": "ghp_{some_github_access_token}"
},
"headers": {
"name": "Accept",
"value": "application/vnd.github+json"
},
"allowRedirects": null
},
"url": "https://api.github.com/repos/GatoGraphQL/GatoGraphQL/actions/artifacts/803739808/zip"
},
{
"options": {
"auth": {
"password": "ghp_{some_github_access_token}"
},
"headers": {
"name": "Accept",
"value": "application/vnd.github+json"
},
"allowRedirects": null
},
"url": "https://api.github.com/repos/GatoGraphQL/GatoGraphQL/actions/artifacts/803739806/zip"
},
{
"options": {
"auth": {
"password": "ghp_{some_github_access_token}"
},
"headers": {
"name": "Accept",
"value": "application/vnd.github+json"
},
"allowRedirects": null
},
"url": "https://api.github.com/repos/GatoGraphQL/GatoGraphQL/actions/artifacts/803739803/zip"
}
],
"_sendHTTPRequests": [
{
"artifactDownloadURL": "https://pipelines.actions.githubusercontent.com/serviceHosts/a6be3ecc-6518-4aaa-b5ec-232be0438a37/_apis/pipelines/1/runs/53479/signedartifactscontent?artifactName=gato-graphql-testing-schema-1.0.0-dev&urlExpires=2023-07-14T07%3A26%3A47.2766840Z&urlSigningMethod=HMACV2&urlSignature=Ype82npdlUlLk4gcGZcBiz80e0ZuvcvnC2rdaSDg9p8%3D"
},
{
"artifactDownloadURL": "https://pipelines.actions.githubusercontent.com/serviceHosts/a6be3ecc-6518-4aaa-b5ec-232be0438a37/_apis/pipelines/1/runs/53479/signedartifactscontent?artifactName=gato-graphql-testing-1.0.0-dev&urlExpires=2023-07-14T07%3A26%3A47.2961965Z&urlSigningMethod=HMACV2&urlSignature=FdWAh8JXNPJsVIPNuiYN8R7i0vRnN8eCGc57VZDNUEc%3D"
},
{
"artifactDownloadURL": "https://pipelines.actions.githubusercontent.com/serviceHosts/a6be3ecc-6518-4aaa-b5ec-232be0438a37/_apis/pipelines/1/runs/53479/signedartifactscontent?artifactName=gato-graphql-1.0.0-dev&urlExpires=2023-07-14T07%3A26%3A47.2861087Z&urlSigningMethod=HMACV2&urlSignature=0Go8QnkZqIbn0urTQqfbMW4rQtjMfDAR9fSm6fCePjw%3D"
}
]
}
}Cuối cùng, chúng ta in tất cả các mục artifactDownloadURL cùng nhau dưới dạng danh sách (có sẵn trong biến động $artifactDownloadURLs), sử dụng _echo:
query PrintArtifactDownloadURLsAsList
@depends(on: "RetrieveActualArtifactDownloadURLs")
{
artifactDownloadURLs: _echo(value: $artifactDownloadURLs)
}Điều này sẽ in ra:
{
"data": {
// ...
"artifactDownloadURLs": [
"https://pipelines.actions.githubusercontent.com/serviceHosts/a6be3ecc-6518-4aaa-b5ec-232be0438a37/_apis/pipelines/1/runs/53479/signedartifactscontent?artifactName=gato-graphql-testing-schema-1.0.0-dev&urlExpires=2023-07-14T07%3A37%3A42.4998268Z&urlSigningMethod=HMACV2&urlSignature=1c1qNRfD9KFwSuzMjw9tsumq9B5I1c9H4LWgSbR0Kwg%3D",
"https://pipelines.actions.githubusercontent.com/serviceHosts/a6be3ecc-6518-4aaa-b5ec-232be0438a37/_apis/pipelines/1/runs/53479/signedartifactscontent?artifactName=gato-graphql-testing-1.0.0-dev&urlExpires=2023-07-14T07%3A37%3A42.4878741Z&urlSigningMethod=HMACV2&urlSignature=htjc1HrmZpbecECpBQnEHhlP7lkqkdyjzATb0vFnzDE%3D",
"https://pipelines.actions.githubusercontent.com/serviceHosts/a6be3ecc-6518-4aaa-b5ec-232be0438a37/_apis/pipelines/1/runs/53479/signedartifactscontent?artifactName=gato-graphql-1.0.0-dev&urlExpires=2023-07-14T07%3A37%3A42.5240496Z&urlSigningMethod=HMACV2&urlSignature=YDuHFqweL9m6LIycLsVy0bJJ4zePc4pWkHz8RfjfzCg%3D"
]
}
}