| 1 | import * as ds from "@distilled.cloud/gcp/datastream_v1"; |
| 2 | import * as Effect from "effect/Effect"; |
| 3 | import * as Schedule from "effect/Schedule"; |
| 4 | import { Unowned } from "../../AdoptPolicy.ts"; |
| 5 | import { isResolved } from "../../Diff.ts"; |
| 6 | import * as Provider from "../../Provider.ts"; |
| 7 | import { Resource } from "../../Resource.ts"; |
| 8 | import { tagRecord } from "../../Tags.ts"; |
| 9 | import { GcpEnvironment } from "../Environment.ts"; |
| 10 | import { |
| 11 | createInternalLabels, |
| 12 | diffLabels, |
| 13 | hasAlchemyLabels, |
| 14 | toLabels, |
| 15 | } from "../Labels.ts"; |
| 16 | import type { Providers } from "../Providers.ts"; |
| 17 | import { |
| 18 | collectPages, |
| 19 | emptyMessage, |
| 20 | fieldMask, |
| 21 | fingerprint, |
| 22 | hasAlchemyLabelMap, |
| 23 | listAtLocation, |
| 24 | locationParent, |
| 25 | normalizeLocation, |
| 26 | parseName, |
| 27 | privateConnectionOf, |
| 28 | replaceOnIdentity, |
| 29 | ResourceNotResolved, |
| 30 | toPhysicalId, |
| 31 | userLabels, |
| 32 | settleOperation, |
| 33 | waitUntilExists, |
| 34 | waitUntilGone, |
| 35 | } from "./internal.ts"; |
| 36 | |
| 37 | export type MysqlProfile = ds.MysqlProfile; |
| 38 | export type PostgresqlProfile = ds.PostgresqlProfile; |
| 39 | export type OracleProfile = ds.OracleProfile; |
| 40 | export type SqlServerProfile = ds.SqlServerProfile; |
| 41 | export type MongodbProfile = ds.MongodbProfile; |
| 42 | export type SpannerProfile = ds.SpannerProfile; |
| 43 | export type GcsProfile = ds.GcsProfile; |
| 44 | export type BigQueryProfile = ds.BigQueryProfile; |
| 45 | export type SalesforceProfile = ds.SalesforceProfile; |
| 46 | export type SalesforceMarketingCloudProfile = |
| 47 | ds.SalesforceMarketingCloudProfile; |
| 48 | export type ServiceNowProfile = ds.ServiceNowProfile; |
| 49 | export type DataverseProfile = ds.DataverseProfile; |
| 50 | export type ForwardSshTunnelConnectivity = ds.ForwardSshTunnelConnectivity; |
| 51 | export type PrivateConnectivity = ds.PrivateConnectivity; |
| 52 | export type MysqlSslConfig = ds.MysqlSslConfig; |
| 53 | export type PostgresqlSslConfig = ds.PostgresqlSslConfig; |
| 54 | export type OracleSslConfig = ds.OracleSslConfig; |
| 55 | export type SqlServerSslConfig = ds.SqlServerSslConfig; |
| 56 | export type MongodbSslConfig = ds.MongodbSslConfig; |
| 57 | export type OracleAsmConfig = ds.OracleAsmConfig; |
| 58 | export type Secret = ds.Secret; |
| 59 | |
| 60 | export type ConnectionProfileProps = { |
| 61 | /** |
| 62 | * Connection profile id (the `{connectionProfile}` segment of |
| 63 | * `projects/{project}/locations/{location}/connectionProfiles/{connectionProfile}`). |
| 64 | * If omitted, a unique RFC1035 name is generated. Immutable — changing |
| 65 | * it replaces the profile. |
| 66 | */ |
| 67 | connectionProfileId?: string; |
| 68 | /** |
| 69 | * Region (`us-central1`, …). Immutable — changing it replaces the |
| 70 | * profile. `US-CENTRAL1` is accepted and normalized to `us-central1`. |
| 71 | * @default the stack's GCP region (`GCP.Region`, else the profile region, else `us-central1`) |
| 72 | */ |
| 73 | location?: string; |
| 74 | /** |
| 75 | * User-friendly display name. |
| 76 | */ |
| 77 | displayName?: string; |
| 78 | /** |
| 79 | * User labels. Alchemy ownership labels are merged in automatically. |
| 80 | */ |
| 81 | labels?: Record<string, string>; |
| 82 | /** |
| 83 | * MySQL source profile. Mutually exclusive with the other engine |
| 84 | * blocks. Switching engines replaces the profile. |
| 85 | */ |
| 86 | mysqlProfile?: MysqlProfile; |
| 87 | /** |
| 88 | * PostgreSQL source profile. Mutually exclusive with the other engine |
| 89 | * blocks. Switching engines replaces the profile. |
| 90 | */ |
| 91 | postgresqlProfile?: PostgresqlProfile; |
| 92 | /** |
| 93 | * Oracle source profile. Mutually exclusive with the other engine |
| 94 | * blocks. Switching engines replaces the profile. |
| 95 | */ |
| 96 | oracleProfile?: OracleProfile; |
| 97 | /** |
| 98 | * SQL Server source profile. Mutually exclusive with the other engine |
| 99 | * blocks. Switching engines replaces the profile. |
| 100 | */ |
| 101 | sqlServerProfile?: SqlServerProfile; |
| 102 | /** |
| 103 | * MongoDB source profile. Mutually exclusive with the other engine |
| 104 | * blocks. Switching engines replaces the profile. |
| 105 | */ |
| 106 | mongodbProfile?: MongodbProfile; |
| 107 | /** |
| 108 | * Spanner source profile. `database` is immutable — changing it |
| 109 | * replaces the profile. |
| 110 | */ |
| 111 | spannerProfile?: SpannerProfile; |
| 112 | /** |
| 113 | * Cloud Storage destination profile. Mutually exclusive with the other |
| 114 | * engine blocks. Switching engines replaces the profile. |
| 115 | */ |
| 116 | gcsProfile?: GcsProfile; |
| 117 | /** |
| 118 | * BigQuery destination profile (empty object). Mutually exclusive with |
| 119 | * the other engine blocks. Switching engines replaces the profile. |
| 120 | */ |
| 121 | bigqueryProfile?: BigQueryProfile; |
| 122 | /** |
| 123 | * Salesforce source profile. Mutually exclusive with the other engine |
| 124 | * blocks. Switching engines replaces the profile. |
| 125 | */ |
| 126 | salesforceProfile?: SalesforceProfile; |
| 127 | /** |
| 128 | * Salesforce Marketing Cloud source profile. Mutually exclusive with |
| 129 | * the other engine blocks. Switching engines replaces the profile. |
| 130 | */ |
| 131 | salesforceMarketingCloudProfile?: SalesforceMarketingCloudProfile; |
| 132 | /** |
| 133 | * ServiceNow source profile. Mutually exclusive with the other engine |
| 134 | * blocks. Switching engines replaces the profile. |
| 135 | */ |
| 136 | serviceNowProfile?: ServiceNowProfile; |
| 137 | /** |
| 138 | * Dataverse source profile. Mutually exclusive with the other engine |
| 139 | * blocks. Switching engines replaces the profile. |
| 140 | */ |
| 141 | dataverseProfile?: DataverseProfile; |
| 142 | /** |
| 143 | * Static service IP connectivity (Datastream public IPs). Mutually |
| 144 | * exclusive with SSH tunnel and private connectivity. |
| 145 | */ |
| 146 | staticServiceIpConnectivity?: Record<string, never>; |
| 147 | /** |
| 148 | * Forward SSH tunnel connectivity. Mutually exclusive with static IP |
| 149 | * and private connectivity. Passwords and private keys are input-only. |
| 150 | */ |
| 151 | forwardSshConnectivity?: ForwardSshTunnelConnectivity; |
| 152 | /** |
| 153 | * Private connectivity via a Datastream private connection. Full name |
| 154 | * or id (combined with `location`). Mutually exclusive with static IP |
| 155 | * and SSH tunnel. |
| 156 | */ |
| 157 | privateConnectivity?: { |
| 158 | privateConnection: string; |
| 159 | }; |
| 160 | /** |
| 161 | * Create or update without validating connectivity. |
| 162 | */ |
| 163 | force?: boolean; |
| 164 | /** |
| 165 | * Validate without creating or updating resources. |
| 166 | */ |
| 167 | validateOnly?: boolean; |
| 168 | }; |
| 169 | |
| 170 | export type ConnectionProfile = Resource< |
| 171 | "GCP.Datastream.ConnectionProfile", |
| 172 | ConnectionProfileProps, |
| 173 | { |
| 174 | /** Full resource name. */ |
| 175 | name: string; |
| 176 | /** Connection profile id (last path segment). */ |
| 177 | connectionProfileId: string; |
| 178 | /** Project id. */ |
| 179 | project: string; |
| 180 | /** Location id. */ |
| 181 | location: string; |
| 182 | /** User-friendly display name. */ |
| 183 | displayName: string | undefined; |
| 184 | /** User labels (Alchemy ownership labels stripped). */ |
| 185 | labels: Record<string, string>; |
| 186 | /** MySQL profile (password omitted). */ |
| 187 | mysqlProfile: MysqlProfile | undefined; |
| 188 | /** PostgreSQL profile (password omitted). */ |
| 189 | postgresqlProfile: PostgresqlProfile | undefined; |
| 190 | /** Oracle profile (password omitted). */ |
| 191 | oracleProfile: OracleProfile | undefined; |
| 192 | /** SQL Server profile (password omitted). */ |
| 193 | sqlServerProfile: SqlServerProfile | undefined; |
| 194 | /** MongoDB profile (password omitted). */ |
| 195 | mongodbProfile: MongodbProfile | undefined; |
| 196 | /** Spanner profile. */ |
| 197 | spannerProfile: SpannerProfile | undefined; |
| 198 | /** Cloud Storage destination profile. */ |
| 199 | gcsProfile: GcsProfile | undefined; |
| 200 | /** BigQuery destination profile. */ |
| 201 | bigqueryProfile: BigQueryProfile | undefined; |
| 202 | /** Salesforce profile (secrets omitted). */ |
| 203 | salesforceProfile: SalesforceProfile | undefined; |
| 204 | /** Salesforce Marketing Cloud profile (secrets omitted). */ |
| 205 | salesforceMarketingCloudProfile: |
| 206 | | SalesforceMarketingCloudProfile |
| 207 | | undefined; |
| 208 | /** ServiceNow profile (secrets omitted). */ |
| 209 | serviceNowProfile: ServiceNowProfile | undefined; |
| 210 | /** Dataverse profile (secrets omitted). */ |
| 211 | dataverseProfile: DataverseProfile | undefined; |
| 212 | /** Static service IP connectivity. */ |
| 213 | staticServiceIpConnectivity: Record<string, never> | undefined; |
| 214 | /** Forward SSH connectivity (password and key omitted). */ |
| 215 | forwardSshConnectivity: ForwardSshTunnelConnectivity | undefined; |
| 216 | /** Private connectivity. */ |
| 217 | privateConnectivity: PrivateConnectivity | undefined; |
| 218 | /** Whether the profile satisfies physical zone isolation. */ |
| 219 | satisfiesPzi: boolean | undefined; |
| 220 | /** Whether the profile satisfies physical zone separation. */ |
| 221 | satisfiesPzs: boolean | undefined; |
| 222 | /** RFC3339 creation timestamp. */ |
| 223 | createTime: string | undefined; |
| 224 | /** RFC3339 last-update timestamp. */ |
| 225 | updateTime: string | undefined; |
| 226 | }, |
| 227 | never, |
| 228 | Providers |
| 229 | >; |
| 230 | |
| 231 | /** |
| 232 | * A Datastream connection profile: credentials and connectivity for a |
| 233 | * source database or a BigQuery / Cloud Storage destination. |
| 234 | * |
| 235 | * `connectionProfileId`, `location`, and the engine kind |
| 236 | * (`mysqlProfile` / `postgresqlProfile` / `oracleProfile` / |
| 237 | * `sqlServerProfile` / `mongodbProfile` / `spannerProfile` / |
| 238 | * `gcsProfile` / `bigqueryProfile` / `salesforceProfile` / |
| 239 | * `salesforceMarketingCloudProfile` / `serviceNowProfile` / |
| 240 | * `dataverseProfile`) plus Spanner `database` are replacement triggers. |
| 241 | * Display name, labels, credentials, and connectivity update in place. |
| 242 | * Passwords, SSL keys, and OAuth secrets are input-only and never |
| 243 | * returned. |
| 244 | * |
| 245 | * ### Creating a Connection Profile |
| 246 | * **Example:** BigQuery destination |
| 247 | * ```typescript |
| 248 | * const dest = yield* GCP.Datastream.ConnectionProfile("BqDest", { |
| 249 | * bigqueryProfile: {}, |
| 250 | * labels: { env: "test" }, |
| 251 | * }); |
| 252 | * ``` |
| 253 | * |
| 254 | * **Example:** MySQL source |
| 255 | * ```typescript |
| 256 | * const source = yield* GCP.Datastream.ConnectionProfile("MysqlSrc", { |
| 257 | * mysqlProfile: { |
| 258 | * hostname: "10.0.0.8", |
| 259 | * port: 3306, |
| 260 | * username: "datastream", |
| 261 | * password: process.env.MYSQL_PASSWORD, |
| 262 | * }, |
| 263 | * staticServiceIpConnectivity: {}, |
| 264 | * force: true, |
| 265 | * }); |
| 266 | * ``` |
| 267 | * |
| 268 | * **Example:** Cloud Storage destination |
| 269 | * ```typescript |
| 270 | * const gcs = yield* GCP.Datastream.ConnectionProfile("GcsDest", { |
| 271 | * gcsProfile: { bucket: bucket.bucketName, rootPath: "/data" }, |
| 272 | * }); |
| 273 | * ``` |
| 274 | * |
| 275 | * @resource |
| 276 | * @category Datastream |
| 277 | */ |
| 278 | export const ConnectionProfile = Resource<ConnectionProfile>( |
| 279 | "GCP.Datastream.ConnectionProfile", |
| 280 | ); |
| 281 | |
| 282 | const resourceName = ( |
| 283 | project: string, |
| 284 | location: string, |
| 285 | connectionProfileId: string, |
| 286 | ) => |
| 287 | `${locationParent(project, location)}/connectionProfiles/${connectionProfileId}`; |
| 288 | |
| 289 | const kindOf = (value: { |
| 290 | mysqlProfile?: unknown; |
| 291 | postgresqlProfile?: unknown; |
| 292 | oracleProfile?: unknown; |
| 293 | sqlServerProfile?: unknown; |
| 294 | mongodbProfile?: unknown; |
| 295 | spannerProfile?: unknown; |
| 296 | gcsProfile?: unknown; |
| 297 | bigqueryProfile?: unknown; |
| 298 | salesforceProfile?: unknown; |
| 299 | salesforceMarketingCloudProfile?: unknown; |
| 300 | serviceNowProfile?: unknown; |
| 301 | dataverseProfile?: unknown; |
| 302 | }) => |
| 303 | value.mysqlProfile |
| 304 | ? "mysql" |
| 305 | : value.postgresqlProfile |
| 306 | ? "postgresql" |
| 307 | : value.oracleProfile |
| 308 | ? "oracle" |
| 309 | : value.sqlServerProfile |
| 310 | ? "sqlserver" |
| 311 | : value.mongodbProfile |
| 312 | ? "mongodb" |
| 313 | : value.spannerProfile |
| 314 | ? "spanner" |
| 315 | : value.gcsProfile |
| 316 | ? "gcs" |
| 317 | : value.bigqueryProfile |
| 318 | ? "bigquery" |
| 319 | : value.salesforceProfile |
| 320 | ? "salesforce" |
| 321 | : value.salesforceMarketingCloudProfile |
| 322 | ? "sfmc" |
| 323 | : value.serviceNowProfile |
| 324 | ? "servicenow" |
| 325 | : value.dataverseProfile |
| 326 | ? "dataverse" |
| 327 | : ""; |
| 328 | |
| 329 | const publicSecret = (secret: Secret | undefined) => |
| 330 | secret === undefined |
| 331 | ? undefined |
| 332 | : { |
| 333 | secretVersion: secret.secretVersion, |
| 334 | }; |
| 335 | |
| 336 | const publicMysqlSsl = (ssl: MysqlSslConfig | undefined) => |
| 337 | ssl === undefined |
| 338 | ? undefined |
| 339 | : { |
| 340 | clientCertificateSet: ssl.clientCertificateSet, |
| 341 | caCertificateSet: ssl.caCertificateSet, |
| 342 | clientKeySet: ssl.clientKeySet, |
| 343 | }; |
| 344 | |
| 345 | const publicPostgresSsl = (ssl: PostgresqlSslConfig | undefined) => |
| 346 | ssl === undefined |
| 347 | ? undefined |
| 348 | : { |
| 349 | serverVerification: ssl.serverVerification |
| 350 | ? { |
| 351 | serverCertificateHostname: |
| 352 | ssl.serverVerification.serverCertificateHostname, |
| 353 | } |
| 354 | : undefined, |
| 355 | serverAndClientVerification: ssl.serverAndClientVerification |
| 356 | ? { |
| 357 | serverCertificateHostname: |
| 358 | ssl.serverAndClientVerification.serverCertificateHostname, |
| 359 | } |
| 360 | : undefined, |
| 361 | }; |
| 362 | |
| 363 | const publicOracleSsl = (ssl: OracleSslConfig | undefined) => |
| 364 | ssl === undefined |
| 365 | ? undefined |
| 366 | : { |
| 367 | caCertificateSet: ssl.caCertificateSet, |
| 368 | serverCertificateDistinguishedName: |
| 369 | ssl.serverCertificateDistinguishedName, |
| 370 | }; |
| 371 | |
| 372 | const publicSqlServerSsl = (ssl: SqlServerSslConfig | undefined) => |
| 373 | ssl === undefined |
| 374 | ? undefined |
| 375 | : { |
| 376 | encryptionNotEnforced: emptyMessage(ssl.encryptionNotEnforced), |
| 377 | basicEncryption: emptyMessage(ssl.basicEncryption), |
| 378 | encryptionAndServerValidation: ssl.encryptionAndServerValidation |
| 379 | ? { |
| 380 | serverCertificateHostname: |
| 381 | ssl.encryptionAndServerValidation.serverCertificateHostname, |
| 382 | } |
| 383 | : undefined, |
| 384 | }; |
| 385 | |
| 386 | const publicMongodbSsl = (ssl: MongodbSslConfig | undefined) => |
| 387 | ssl === undefined |
| 388 | ? undefined |
| 389 | : { |
| 390 | clientCertificateSet: ssl.clientCertificateSet, |
| 391 | clientKeySet: ssl.clientKeySet, |
| 392 | caCertificateSet: ssl.caCertificateSet, |
| 393 | secretManagerStoredClientKey: ssl.secretManagerStoredClientKey, |
| 394 | }; |
| 395 | |
| 396 | const publicMysql = (mysql: MysqlProfile | undefined) => |
| 397 | mysql === undefined |
| 398 | ? undefined |
| 399 | : { |
| 400 | username: mysql.username, |
| 401 | port: mysql.port, |
| 402 | hostname: mysql.hostname, |
| 403 | secretManagerStoredPassword: mysql.secretManagerStoredPassword, |
| 404 | sslConfig: publicMysqlSsl(mysql.sslConfig), |
| 405 | }; |
| 406 | |
| 407 | const publicPostgresql = (postgresql: PostgresqlProfile | undefined) => |
| 408 | postgresql === undefined |
| 409 | ? undefined |
| 410 | : { |
| 411 | hostname: postgresql.hostname, |
| 412 | database: postgresql.database, |
| 413 | username: postgresql.username, |
| 414 | port: postgresql.port, |
| 415 | secretManagerStoredPassword: postgresql.secretManagerStoredPassword, |
| 416 | sslConfig: publicPostgresSsl(postgresql.sslConfig), |
| 417 | }; |
| 418 | |
| 419 | const publicOracleAsm = (asm: OracleAsmConfig | undefined) => |
| 420 | asm === undefined |
| 421 | ? undefined |
| 422 | : { |
| 423 | port: asm.port, |
| 424 | connectionAttributes: asm.connectionAttributes, |
| 425 | username: asm.username, |
| 426 | secretManagerStoredPassword: asm.secretManagerStoredPassword, |
| 427 | oracleSslConfig: publicOracleSsl(asm.oracleSslConfig), |
| 428 | hostname: asm.hostname, |
| 429 | asmService: asm.asmService, |
| 430 | }; |
| 431 | |
| 432 | const publicOracle = (oracle: OracleProfile | undefined) => |
| 433 | oracle === undefined |
| 434 | ? undefined |
| 435 | : { |
| 436 | connectionAttributes: oracle.connectionAttributes, |
| 437 | databaseService: oracle.databaseService, |
| 438 | hostname: oracle.hostname, |
| 439 | oracleSslConfig: publicOracleSsl(oracle.oracleSslConfig), |
| 440 | oracleAsmConfig: publicOracleAsm(oracle.oracleAsmConfig), |
| 441 | username: oracle.username, |
| 442 | secretManagerStoredPassword: oracle.secretManagerStoredPassword, |
| 443 | port: oracle.port, |
| 444 | }; |
| 445 | |
| 446 | const publicSqlServer = (sqlserver: SqlServerProfile | undefined) => |
| 447 | sqlserver === undefined |
| 448 | ? undefined |
| 449 | : { |
| 450 | hostname: sqlserver.hostname, |
| 451 | sslConfig: publicSqlServerSsl(sqlserver.sslConfig), |
| 452 | database: sqlserver.database, |
| 453 | username: sqlserver.username, |
| 454 | secretManagerStoredPassword: sqlserver.secretManagerStoredPassword, |
| 455 | port: sqlserver.port, |
| 456 | }; |
| 457 | |
| 458 | const publicMongodb = (mongodb: MongodbProfile | undefined) => |
| 459 | mongodb === undefined |
| 460 | ? undefined |
| 461 | : { |
| 462 | hostAddresses: mongodb.hostAddresses, |
| 463 | username: mongodb.username, |
| 464 | secretManagerStoredPassword: mongodb.secretManagerStoredPassword, |
| 465 | additionalOptions: mongodb.additionalOptions, |
| 466 | replicaSet: mongodb.replicaSet, |
| 467 | srvConnectionFormat: emptyMessage(mongodb.srvConnectionFormat), |
| 468 | standardConnectionFormat: mongodb.standardConnectionFormat, |
| 469 | sslConfig: publicMongodbSsl(mongodb.sslConfig), |
| 470 | }; |
| 471 | |
| 472 | const publicSalesforce = (salesforce: SalesforceProfile | undefined) => |
| 473 | salesforce === undefined |
| 474 | ? undefined |
| 475 | : { |
| 476 | domain: salesforce.domain, |
| 477 | userCredentials: salesforce.userCredentials |
| 478 | ? { |
| 479 | username: salesforce.userCredentials.username, |
| 480 | secretManagerStoredPassword: |
| 481 | salesforce.userCredentials.secretManagerStoredPassword, |
| 482 | secretManagerStoredSecurityToken: |
| 483 | salesforce.userCredentials.secretManagerStoredSecurityToken, |
| 484 | } |
| 485 | : undefined, |
| 486 | oauth2ClientCredentials: salesforce.oauth2ClientCredentials |
| 487 | ? { |
| 488 | clientId: salesforce.oauth2ClientCredentials.clientId, |
| 489 | secretManagerStoredClientSecret: |
| 490 | salesforce.oauth2ClientCredentials |
| 491 | .secretManagerStoredClientSecret, |
| 492 | } |
| 493 | : undefined, |
| 494 | }; |
| 495 | |
| 496 | const publicSfmc = (profile: SalesforceMarketingCloudProfile | undefined) => |
| 497 | profile === undefined |
| 498 | ? undefined |
| 499 | : { |
| 500 | subdomain: profile.subdomain, |
| 501 | oauthClientCredentials: profile.oauthClientCredentials |
| 502 | ? { |
| 503 | clientId: profile.oauthClientCredentials.clientId, |
| 504 | clientSecret: publicSecret( |
| 505 | profile.oauthClientCredentials.clientSecret, |
| 506 | ), |
| 507 | } |
| 508 | : undefined, |
| 509 | }; |
| 510 | |
| 511 | const publicServiceNow = (profile: ServiceNowProfile | undefined) => |
| 512 | profile === undefined |
| 513 | ? undefined |
| 514 | : { |
| 515 | instance: profile.instance, |
| 516 | userPasswordCredentials: profile.userPasswordCredentials |
| 517 | ? { |
| 518 | username: profile.userPasswordCredentials.username, |
| 519 | password: publicSecret(profile.userPasswordCredentials.password), |
| 520 | } |
| 521 | : undefined, |
| 522 | oauthClientCredentials: profile.oauthClientCredentials |
| 523 | ? { |
| 524 | clientId: profile.oauthClientCredentials.clientId, |
| 525 | clientSecret: publicSecret( |
| 526 | profile.oauthClientCredentials.clientSecret, |
| 527 | ), |
| 528 | } |
| 529 | : undefined, |
| 530 | }; |
| 531 | |
| 532 | const publicDataverse = (profile: DataverseProfile | undefined) => |
| 533 | profile === undefined |
| 534 | ? undefined |
| 535 | : { |
| 536 | tenantId: profile.tenantId, |
| 537 | environmentUrl: profile.environmentUrl, |
| 538 | oauthClientCredentials: profile.oauthClientCredentials |
| 539 | ? { |
| 540 | clientId: profile.oauthClientCredentials.clientId, |
| 541 | clientSecret: publicSecret( |
| 542 | profile.oauthClientCredentials.clientSecret, |
| 543 | ), |
| 544 | } |
| 545 | : undefined, |
| 546 | }; |
| 547 | |
| 548 | const publicForwardSsh = (ssh: ForwardSshTunnelConnectivity | undefined) => |
| 549 | ssh === undefined |
| 550 | ? undefined |
| 551 | : { |
| 552 | hostname: ssh.hostname, |
| 553 | username: ssh.username, |
| 554 | port: ssh.port, |
| 555 | }; |
| 556 | |
| 557 | const publicPrivateConnectivity = ( |
| 558 | value: PrivateConnectivity | ConnectionProfileProps["privateConnectivity"], |
| 559 | project: string, |
| 560 | location: string, |
| 561 | ) => |
| 562 | value?.privateConnection === undefined |
| 563 | ? undefined |
| 564 | : { |
| 565 | privateConnection: privateConnectionOf( |
| 566 | value.privateConnection, |
| 567 | project, |
| 568 | location, |
| 569 | ), |
| 570 | }; |
| 571 | |
| 572 | const secretChanged = ( |
| 573 | newsHasSecret: boolean, |
| 574 | publicPrevious: unknown, |
| 575 | publicNext: unknown, |
| 576 | ) => newsHasSecret || fingerprint(publicPrevious) !== fingerprint(publicNext); |
| 577 | |
| 578 | const mysqlHasSecret = (mysql: MysqlProfile | undefined) => |
| 579 | mysql?.password !== undefined || |
| 580 | mysql?.sslConfig?.clientCertificate !== undefined || |
| 581 | mysql?.sslConfig?.clientKey !== undefined || |
| 582 | mysql?.sslConfig?.caCertificate !== undefined; |
| 583 | |
| 584 | const postgresqlHasSecret = (postgresql: PostgresqlProfile | undefined) => |
| 585 | postgresql?.password !== undefined || |
| 586 | postgresql?.sslConfig?.serverVerification?.caCertificate !== undefined || |
| 587 | postgresql?.sslConfig?.serverAndClientVerification?.caCertificate !== |
| 588 | undefined || |
| 589 | postgresql?.sslConfig?.serverAndClientVerification?.clientCertificate !== |
| 590 | undefined || |
| 591 | postgresql?.sslConfig?.serverAndClientVerification?.clientKey !== undefined; |
| 592 | |
| 593 | const oracleHasSecret = (oracle: OracleProfile | undefined) => |
| 594 | oracle?.password !== undefined || |
| 595 | oracle?.oracleSslConfig?.caCertificate !== undefined || |
| 596 | oracle?.oracleAsmConfig?.password !== undefined || |
| 597 | oracle?.oracleAsmConfig?.oracleSslConfig?.caCertificate !== undefined; |
| 598 | |
| 599 | const sqlServerHasSecret = (sqlserver: SqlServerProfile | undefined) => |
| 600 | sqlserver?.password !== undefined || |
| 601 | sqlserver?.sslConfig?.encryptionAndServerValidation?.caCertificate !== |
| 602 | undefined; |
| 603 | |
| 604 | const mongodbHasSecret = (mongodb: MongodbProfile | undefined) => |
| 605 | mongodb?.password !== undefined || |
| 606 | mongodb?.sslConfig?.clientCertificate !== undefined || |
| 607 | mongodb?.sslConfig?.clientKey !== undefined || |
| 608 | mongodb?.sslConfig?.caCertificate !== undefined; |
| 609 | |
| 610 | const salesforceHasSecret = (salesforce: SalesforceProfile | undefined) => |
| 611 | salesforce?.userCredentials?.password !== undefined || |
| 612 | salesforce?.userCredentials?.securityToken !== undefined || |
| 613 | salesforce?.oauth2ClientCredentials?.clientSecret !== undefined; |
| 614 | |
| 615 | const sfmcHasSecret = (profile: SalesforceMarketingCloudProfile | undefined) => |
| 616 | profile?.oauthClientCredentials?.clientSecret?.rawValue !== undefined; |
| 617 | |
| 618 | const serviceNowHasSecret = (profile: ServiceNowProfile | undefined) => |
| 619 | profile?.userPasswordCredentials?.password?.rawValue !== undefined || |
| 620 | profile?.oauthClientCredentials?.clientSecret?.rawValue !== undefined; |
| 621 | |
| 622 | const dataverseHasSecret = (profile: DataverseProfile | undefined) => |
| 623 | profile?.oauthClientCredentials?.clientSecret?.rawValue !== undefined; |
| 624 | |
| 625 | const sshHasSecret = (ssh: ForwardSshTunnelConnectivity | undefined) => |
| 626 | ssh?.password !== undefined || ssh?.privateKey !== undefined; |
| 627 | |
| 628 | const toAttrs = ( |
| 629 | profile: ds.ConnectionProfile, |
| 630 | project: string, |
| 631 | locationHint: string, |
| 632 | ) => { |
| 633 | const name = profile.name ?? ""; |
| 634 | const parsed = parseName(name, "connectionProfiles", locationHint); |
| 635 | return { |
| 636 | name, |
| 637 | connectionProfileId: parsed.id, |
| 638 | project: parsed.project || project, |
| 639 | location: parsed.location || locationHint, |
| 640 | displayName: profile.displayName, |
| 641 | labels: userLabels(profile.labels), |
| 642 | mysqlProfile: publicMysql(profile.mysqlProfile), |
| 643 | postgresqlProfile: publicPostgresql(profile.postgresqlProfile), |
| 644 | oracleProfile: publicOracle(profile.oracleProfile), |
| 645 | sqlServerProfile: publicSqlServer(profile.sqlServerProfile), |
| 646 | mongodbProfile: publicMongodb(profile.mongodbProfile), |
| 647 | spannerProfile: profile.spannerProfile, |
| 648 | gcsProfile: profile.gcsProfile, |
| 649 | bigqueryProfile: emptyMessage(profile.bigqueryProfile), |
| 650 | salesforceProfile: publicSalesforce(profile.salesforceProfile), |
| 651 | salesforceMarketingCloudProfile: publicSfmc( |
| 652 | profile.salesforceMarketingCloudProfile, |
| 653 | ), |
| 654 | serviceNowProfile: publicServiceNow(profile.serviceNowProfile), |
| 655 | dataverseProfile: publicDataverse(profile.dataverseProfile), |
| 656 | staticServiceIpConnectivity: emptyMessage( |
| 657 | profile.staticServiceIpConnectivity, |
| 658 | ), |
| 659 | forwardSshConnectivity: publicForwardSsh(profile.forwardSshConnectivity), |
| 660 | privateConnectivity: profile.privateConnectivity, |
| 661 | satisfiesPzi: profile.satisfiesPzi, |
| 662 | satisfiesPzs: profile.satisfiesPzs, |
| 663 | createTime: profile.createTime, |
| 664 | updateTime: profile.updateTime, |
| 665 | }; |
| 666 | }; |
| 667 | |
| 668 | const getByName = (name: string) => |
| 669 | name.length === 0 |
| 670 | ? Effect.succeed(undefined) |
| 671 | : ds |
| 672 | .getProjectsLocationsConnectionProfiles({ name }) |
| 673 | .pipe(Effect.catchTag("NotFound", () => Effect.succeed(undefined))); |
| 674 | |
| 675 | const listOwned = (project: string, region: string) => |
| 676 | listAtLocation(project, region, (parent) => |
| 677 | collectPages( |
| 678 | ds.listProjectsLocationsConnectionProfiles.pages({ |
| 679 | parent, |
| 680 | pageSize: 1000, |
| 681 | }), |
| 682 | (page) => page.connectionProfiles, |
| 683 | ).pipe( |
| 684 | Effect.map((items) => |
| 685 | items.filter((item) => hasAlchemyLabelMap(item.labels)), |
| 686 | ), |
| 687 | ), |
| 688 | ); |
| 689 | |
| 690 | const connectivityOf = ( |
| 691 | news: ConnectionProfileProps, |
| 692 | project: string, |
| 693 | location: string, |
| 694 | ) => ({ |
| 695 | staticServiceIpConnectivity: emptyMessage(news.staticServiceIpConnectivity), |
| 696 | forwardSshConnectivity: news.forwardSshConnectivity, |
| 697 | privateConnectivity: publicPrivateConnectivity( |
| 698 | news.privateConnectivity, |
| 699 | project, |
| 700 | location, |
| 701 | ), |
| 702 | }); |
| 703 | |
| 704 | export const ConnectionProfileProvider = () => |
| 705 | Provider.succeed(ConnectionProfile, { |
| 706 | stables: [ |
| 707 | "name", |
| 708 | "connectionProfileId", |
| 709 | "project", |
| 710 | "location", |
| 711 | "createTime", |
| 712 | ], |
| 713 | |
| 714 | diff: Effect.fn(function* ({ news, olds, output }) { |
| 715 | if (!isResolved(news)) return undefined; |
| 716 | const env = yield* GcpEnvironment.current; |
| 717 | const previousKind = kindOf({ |
| 718 | mysqlProfile: olds?.mysqlProfile ?? output?.mysqlProfile, |
| 719 | postgresqlProfile: olds?.postgresqlProfile ?? output?.postgresqlProfile, |
| 720 | oracleProfile: olds?.oracleProfile ?? output?.oracleProfile, |
| 721 | sqlServerProfile: olds?.sqlServerProfile ?? output?.sqlServerProfile, |
| 722 | mongodbProfile: olds?.mongodbProfile ?? output?.mongodbProfile, |
| 723 | spannerProfile: olds?.spannerProfile ?? output?.spannerProfile, |
| 724 | gcsProfile: olds?.gcsProfile ?? output?.gcsProfile, |
| 725 | bigqueryProfile: olds?.bigqueryProfile ?? output?.bigqueryProfile, |
| 726 | salesforceProfile: olds?.salesforceProfile ?? output?.salesforceProfile, |
| 727 | salesforceMarketingCloudProfile: |
| 728 | olds?.salesforceMarketingCloudProfile ?? |
| 729 | output?.salesforceMarketingCloudProfile, |
| 730 | serviceNowProfile: olds?.serviceNowProfile ?? output?.serviceNowProfile, |
| 731 | dataverseProfile: olds?.dataverseProfile ?? output?.dataverseProfile, |
| 732 | }); |
| 733 | const nextKind = kindOf(news) || previousKind; |
| 734 | const previousSpanner = |
| 735 | olds?.spannerProfile?.database ?? output?.spannerProfile?.database; |
| 736 | const nextSpanner = news.spannerProfile?.database ?? previousSpanner; |
| 737 | return replaceOnIdentity({ |
| 738 | previousId: olds?.connectionProfileId ?? output?.connectionProfileId, |
| 739 | nextId: |
| 740 | news.connectionProfileId ?? |
| 741 | olds?.connectionProfileId ?? |
| 742 | output?.connectionProfileId, |
| 743 | previousLocation: normalizeLocation( |
| 744 | olds?.location ?? output?.location, |
| 745 | env.region, |
| 746 | ), |
| 747 | nextLocation: normalizeLocation( |
| 748 | news.location ?? olds?.location ?? output?.location, |
| 749 | env.region, |
| 750 | ), |
| 751 | extra: |
| 752 | previousKind !== nextKind || |
| 753 | (nextKind === "spanner" && |
| 754 | previousSpanner !== undefined && |
| 755 | nextSpanner !== undefined && |
| 756 | previousSpanner !== nextSpanner), |
| 757 | }); |
| 758 | }), |
| 759 | |
| 760 | read: Effect.fn(function* ({ id, olds, output }) { |
| 761 | const env = yield* GcpEnvironment.current; |
| 762 | const connectionProfileId = yield* toPhysicalId( |
| 763 | id, |
| 764 | olds?.connectionProfileId, |
| 765 | output?.connectionProfileId, |
| 766 | "profile", |
| 767 | ); |
| 768 | const location = normalizeLocation( |
| 769 | olds?.location ?? output?.location, |
| 770 | env.region, |
| 771 | ); |
| 772 | const name = |
| 773 | output?.name ?? |
| 774 | resourceName(env.project, location, connectionProfileId); |
| 775 | const existing = yield* getByName(name); |
| 776 | if (existing === undefined) return undefined; |
| 777 | const attrs = toAttrs(existing, env.project, location); |
| 778 | return (yield* hasAlchemyLabels(id, tagRecord(existing.labels))) |
| 779 | ? attrs |
| 780 | : Unowned(attrs); |
| 781 | }), |
| 782 | |
| 783 | list: () => |
| 784 | Effect.gen(function* () { |
| 785 | const env = yield* GcpEnvironment.current; |
| 786 | const items = yield* listOwned(env.project, env.region); |
| 787 | return items.map((item) => toAttrs(item, env.project, env.region)); |
| 788 | }), |
| 789 | |
| 790 | reconcile: Effect.fn(function* ({ id, news, output }) { |
| 791 | const env = yield* GcpEnvironment.current; |
| 792 | const connectionProfileId = yield* toPhysicalId( |
| 793 | id, |
| 794 | news.connectionProfileId, |
| 795 | output?.connectionProfileId, |
| 796 | "profile", |
| 797 | ); |
| 798 | const location = normalizeLocation( |
| 799 | news.location ?? output?.location, |
| 800 | env.region, |
| 801 | ); |
| 802 | const name = resourceName(env.project, location, connectionProfileId); |
| 803 | const desiredLabels = { |
| 804 | ...toLabels(news.labels), |
| 805 | ...(yield* createInternalLabels(id)), |
| 806 | }; |
| 807 | const displayName = news.displayName ?? connectionProfileId; |
| 808 | const connectivity = connectivityOf(news, env.project, location); |
| 809 | const body: ds.ConnectionProfile = { |
| 810 | displayName, |
| 811 | labels: desiredLabels, |
| 812 | mysqlProfile: news.mysqlProfile, |
| 813 | postgresqlProfile: news.postgresqlProfile, |
| 814 | oracleProfile: news.oracleProfile, |
| 815 | sqlServerProfile: news.sqlServerProfile, |
| 816 | mongodbProfile: news.mongodbProfile, |
| 817 | spannerProfile: news.spannerProfile, |
| 818 | gcsProfile: news.gcsProfile, |
| 819 | bigqueryProfile: emptyMessage(news.bigqueryProfile), |
| 820 | salesforceProfile: news.salesforceProfile, |
| 821 | salesforceMarketingCloudProfile: news.salesforceMarketingCloudProfile, |
| 822 | serviceNowProfile: news.serviceNowProfile, |
| 823 | dataverseProfile: news.dataverseProfile, |
| 824 | ...connectivity, |
| 825 | }; |
| 826 | |
| 827 | let current = yield* getByName(output?.name ?? name); |
| 828 | |
| 829 | if (current === undefined) { |
| 830 | yield* ds |
| 831 | .createProjectsLocationsConnectionProfiles({ |
| 832 | parent: locationParent(env.project, location), |
| 833 | connectionProfileId, |
| 834 | force: news.force, |
| 835 | validateOnly: news.validateOnly, |
| 836 | body, |
| 837 | }) |
| 838 | .pipe( |
| 839 | Effect.catchTag("Conflict", () => Effect.succeed(undefined)), |
| 840 | Effect.flatMap((created) => settleOperation(created)), |
| 841 | // Datastream intermittently fails the create operation with |
| 842 | // INTERNAL ("an internal error has occurred") under load. |
| 843 | Effect.retry({ |
| 844 | while: (error) => |
| 845 | error._tag === "GCP.OperationFailed" && error.code === 13, |
| 846 | times: 4, |
| 847 | schedule: Schedule.exponential("5 seconds"), |
| 848 | }), |
| 849 | ); |
| 850 | current = yield* waitUntilExists(getByName(name), name); |
| 851 | } |
| 852 | |
| 853 | if (current === undefined) { |
| 854 | return yield* new ResourceNotResolved({ name }); |
| 855 | } |
| 856 | |
| 857 | const observedLabels = tagRecord(current.labels); |
| 858 | const { upsert, removed } = diffLabels(observedLabels, desiredLabels); |
| 859 | const labelsChanged = upsert.length > 0 || removed.length > 0; |
| 860 | const displayNameChanged = (current.displayName ?? "") !== displayName; |
| 861 | const mysqlChanged = |
| 862 | news.mysqlProfile !== undefined && |
| 863 | secretChanged( |
| 864 | mysqlHasSecret(news.mysqlProfile), |
| 865 | publicMysql(current.mysqlProfile), |
| 866 | publicMysql(news.mysqlProfile), |
| 867 | ); |
| 868 | const postgresqlChanged = |
| 869 | news.postgresqlProfile !== undefined && |
| 870 | secretChanged( |
| 871 | postgresqlHasSecret(news.postgresqlProfile), |
| 872 | publicPostgresql(current.postgresqlProfile), |
| 873 | publicPostgresql(news.postgresqlProfile), |
| 874 | ); |
| 875 | const oracleChanged = |
| 876 | news.oracleProfile !== undefined && |
| 877 | secretChanged( |
| 878 | oracleHasSecret(news.oracleProfile), |
| 879 | publicOracle(current.oracleProfile), |
| 880 | publicOracle(news.oracleProfile), |
| 881 | ); |
| 882 | const sqlServerChanged = |
| 883 | news.sqlServerProfile !== undefined && |
| 884 | secretChanged( |
| 885 | sqlServerHasSecret(news.sqlServerProfile), |
| 886 | publicSqlServer(current.sqlServerProfile), |
| 887 | publicSqlServer(news.sqlServerProfile), |
| 888 | ); |
| 889 | const mongodbChanged = |
| 890 | news.mongodbProfile !== undefined && |
| 891 | secretChanged( |
| 892 | mongodbHasSecret(news.mongodbProfile), |
| 893 | publicMongodb(current.mongodbProfile), |
| 894 | publicMongodb(news.mongodbProfile), |
| 895 | ); |
| 896 | const gcsChanged = |
| 897 | news.gcsProfile !== undefined && |
| 898 | fingerprint(current.gcsProfile) !== fingerprint(news.gcsProfile); |
| 899 | const salesforceChanged = |
| 900 | news.salesforceProfile !== undefined && |
| 901 | secretChanged( |
| 902 | salesforceHasSecret(news.salesforceProfile), |
| 903 | publicSalesforce(current.salesforceProfile), |
| 904 | publicSalesforce(news.salesforceProfile), |
| 905 | ); |
| 906 | const sfmcChanged = |
| 907 | news.salesforceMarketingCloudProfile !== undefined && |
| 908 | secretChanged( |
| 909 | sfmcHasSecret(news.salesforceMarketingCloudProfile), |
| 910 | publicSfmc(current.salesforceMarketingCloudProfile), |
| 911 | publicSfmc(news.salesforceMarketingCloudProfile), |
| 912 | ); |
| 913 | const serviceNowChanged = |
| 914 | news.serviceNowProfile !== undefined && |
| 915 | secretChanged( |
| 916 | serviceNowHasSecret(news.serviceNowProfile), |
| 917 | publicServiceNow(current.serviceNowProfile), |
| 918 | publicServiceNow(news.serviceNowProfile), |
| 919 | ); |
| 920 | const dataverseChanged = |
| 921 | news.dataverseProfile !== undefined && |
| 922 | secretChanged( |
| 923 | dataverseHasSecret(news.dataverseProfile), |
| 924 | publicDataverse(current.dataverseProfile), |
| 925 | publicDataverse(news.dataverseProfile), |
| 926 | ); |
| 927 | const desiredPrivate = publicPrivateConnectivity( |
| 928 | news.privateConnectivity, |
| 929 | env.project, |
| 930 | location, |
| 931 | ); |
| 932 | const staticChanged = |
| 933 | news.staticServiceIpConnectivity !== undefined && |
| 934 | fingerprint(emptyMessage(current.staticServiceIpConnectivity)) !== |
| 935 | fingerprint(emptyMessage(news.staticServiceIpConnectivity)); |
| 936 | const sshChanged = |
| 937 | news.forwardSshConnectivity !== undefined && |
| 938 | secretChanged( |
| 939 | sshHasSecret(news.forwardSshConnectivity), |
| 940 | publicForwardSsh(current.forwardSshConnectivity), |
| 941 | publicForwardSsh(news.forwardSshConnectivity), |
| 942 | ); |
| 943 | const privateChanged = |
| 944 | news.privateConnectivity !== undefined && |
| 945 | fingerprint(current.privateConnectivity) !== |
| 946 | fingerprint(desiredPrivate); |
| 947 | const mask = fieldMask([ |
| 948 | labelsChanged && "labels", |
| 949 | displayNameChanged && "displayName", |
| 950 | mysqlChanged && "mysqlProfile", |
| 951 | postgresqlChanged && "postgresqlProfile", |
| 952 | oracleChanged && "oracleProfile", |
| 953 | sqlServerChanged && "sqlServerProfile", |
| 954 | mongodbChanged && "mongodbProfile", |
| 955 | gcsChanged && "gcsProfile", |
| 956 | salesforceChanged && "salesforceProfile", |
| 957 | sfmcChanged && "salesforceMarketingCloudProfile", |
| 958 | serviceNowChanged && "serviceNowProfile", |
| 959 | dataverseChanged && "dataverseProfile", |
| 960 | staticChanged && "staticServiceIpConnectivity", |
| 961 | sshChanged && "forwardSshConnectivity", |
| 962 | privateChanged && "privateConnectivity", |
| 963 | ]); |
| 964 | |
| 965 | if (mask.length > 0) { |
| 966 | const patch: ds.ConnectionProfile = { name: current.name ?? name }; |
| 967 | if (labelsChanged) patch.labels = desiredLabels; |
| 968 | if (displayNameChanged) patch.displayName = displayName; |
| 969 | if (mysqlChanged) patch.mysqlProfile = news.mysqlProfile; |
| 970 | if (postgresqlChanged) patch.postgresqlProfile = news.postgresqlProfile; |
| 971 | if (oracleChanged) patch.oracleProfile = news.oracleProfile; |
| 972 | if (sqlServerChanged) patch.sqlServerProfile = news.sqlServerProfile; |
| 973 | if (mongodbChanged) patch.mongodbProfile = news.mongodbProfile; |
| 974 | if (gcsChanged) patch.gcsProfile = news.gcsProfile; |
| 975 | if (salesforceChanged) patch.salesforceProfile = news.salesforceProfile; |
| 976 | if (sfmcChanged) { |
| 977 | patch.salesforceMarketingCloudProfile = |
| 978 | news.salesforceMarketingCloudProfile; |
| 979 | } |
| 980 | if (serviceNowChanged) patch.serviceNowProfile = news.serviceNowProfile; |
| 981 | if (dataverseChanged) patch.dataverseProfile = news.dataverseProfile; |
| 982 | if (staticChanged) { |
| 983 | patch.staticServiceIpConnectivity = |
| 984 | connectivity.staticServiceIpConnectivity; |
| 985 | } |
| 986 | if (sshChanged) { |
| 987 | patch.forwardSshConnectivity = connectivity.forwardSshConnectivity; |
| 988 | } |
| 989 | if (privateChanged) { |
| 990 | patch.privateConnectivity = connectivity.privateConnectivity; |
| 991 | } |
| 992 | const operation = yield* ds.patchProjectsLocationsConnectionProfiles({ |
| 993 | name: current.name ?? name, |
| 994 | updateMask: mask, |
| 995 | force: news.force, |
| 996 | validateOnly: news.validateOnly, |
| 997 | body: patch, |
| 998 | }); |
| 999 | yield* settleOperation(operation); |
| 1000 | current = yield* waitUntilExists( |
| 1001 | getByName(current.name ?? name), |
| 1002 | current.name ?? name, |
| 1003 | ); |
| 1004 | } |
| 1005 | |
| 1006 | return toAttrs(current, env.project, location); |
| 1007 | }), |
| 1008 | |
| 1009 | delete: Effect.fn(function* ({ output }) { |
| 1010 | const operation = yield* ds |
| 1011 | .deleteProjectsLocationsConnectionProfiles({ |
| 1012 | name: output.name, |
| 1013 | }) |
| 1014 | .pipe( |
| 1015 | Effect.retry({ |
| 1016 | while: (error) => error._tag === "Conflict", |
| 1017 | times: 8, |
| 1018 | schedule: Schedule.spaced("2 seconds"), |
| 1019 | }), |
| 1020 | Effect.catchTag(["NotFound", "Conflict"], () => |
| 1021 | Effect.succeed(undefined), |
| 1022 | ), |
| 1023 | ); |
| 1024 | yield* settleOperation(operation, { notFoundOk: true }); |
| 1025 | yield* waitUntilGone(getByName(output.name), output.name, { |
| 1026 | times: 15, |
| 1027 | interval: "2 seconds", |
| 1028 | }).pipe( |
| 1029 | Effect.catchTag( |
| 1030 | "GCP.Datastream.ResourceStillExists", |
| 1031 | () => Effect.void, |
| 1032 | ), |
| 1033 | ); |
| 1034 | }), |
| 1035 | }); |