diff --git a/octavia-cli/README.md b/octavia-cli/README.md index f5d2fcf2e3c4..06befdd42a82 100644 --- a/octavia-cli/README.md +++ b/octavia-cli/README.md @@ -138,11 +138,11 @@ docker-compose run octavia-cli ` ### `octavia` command flags | **Flag** | **Description** | **Env Variable** | **Default** | -| ---------------------------------------- | --------------------------------------------------------------------------------- | -------------------------- | ------------------------------------------------------ | ---- | +| ---------------------------------------- | --------------------------------------------------------------------------------- | -------------------------- | ------------------------------------------------------ | | `--airbyte-url` | Airbyte instance URL. | `AIRBYTE_URL` | `http://localhost:8000` | | `--workspace-id` | Airbyte workspace id. | `AIRBYTE_WORKSPACE_ID` | The first workspace id found on your Airbyte instance. | | `--enable-telemetry/--disable-telemetry` | Enable or disable the sending of telemetry data. | `OCTAVIA_ENABLE_TELEMETRY` | True | -| `--api-http-header` | HTTP Header value pairs passed while calling Airbyte's API | not supported. | None | None | +| `--api-http-header` | HTTP Header value pairs passed while calling Airbyte's API | None | None | | `--api-http-headers-file-path` | Path to the YAML file that contains custom HTTP Headers to send to Airbyte's API. | None | None | #### Using custom HTTP headers @@ -157,7 +157,7 @@ You can also use a custom YAML file (one is already created on init in `api_http ```yaml headers: - Authorization: Basic foobar== + Authorization: Bearer my-secret-token User-Agent: octavia-cli/0.0.0 ``` @@ -183,6 +183,9 @@ headers: | **`octavia get source`** | Get the JSON representation of an existing source in current the Airbyte workspace. | | **`octavia get destination`** | Get the JSON representation of an existing destination in the current Airbyte workspace. | | **`octavia get connection`** | Get the JSON representation of an existing connection in the current Airbyte workspace. | +| **`octavia import source`** | Import an existing source to manage it with octavia-cli. | +| **`octavia import destination`** | Import an existing destination to manage it with octavia-cli. | +| **`octavia import connection`** | Import an existing connection to manage it with octavia-cli. | | **`octavia generate source`** | Generate a local YAML configuration for a new source. | | **`octavia generate destination`** | Generate a local YAML configuration for a new destination. | | **`octavia generate connection`** | Generate a local YAML configuration for a new connection. | @@ -485,6 +488,70 @@ $ octavia get connection "Poke To PG" "sourceCatalogId": "18102e7c-5340-4000-85f3-204ab7715801" } ``` + +#### `octavia import source or ` + +Import an existing source to manage it with octavia-cli. You can use a source ID or name. + +| **Argument** | **Description** | +| --------------| -----------------| +| `SOURCE_ID` | The source id. | +| `SOURCE_NAME` | The source name. | + +**Examples**: + +```bash +$ octavia import source poke +🐙 - Octavia is targetting your Airbyte instance running at http://localhost:8000 on workspace 75658e4f-e5f0-4e35-be0c-bdad33226c94. +✅ - Imported source poke in sources/poke/configuration.yaml. State stored in sources/poke/state_75658e4f-e5f0-4e35-be0c-bdad33226c94.yaml +⚠️ - Please update any secrets stored in sources/poke/configuration.yaml +``` +You know have local configuration file for an Airbyte source that was already existing. +You need to edit any secret value that exist in this configuration as secrets are not imported. +You can edit the configuration and run `octavia apply` to continue managing it with octavia-cli. + +#### `octavia import destination or ` + +Import an existing destination to manage it with octavia-cli. You can use a destination ID or name. + +| **Argument** | **Description** | +| -------------------| ----------------------| +| `DESTINATION_ID` | The destination id. | +| `DESTINATION_NAME` | The destination name. | + +**Examples**: + +```bash +$ octavia import destination pg +🐙 - Octavia is targetting your Airbyte instance running at http://localhost:8000 on workspace 75658e4f-e5f0-4e35-be0c-bdad33226c94. +✅ - Imported destination pg in destinations/pg/configuration.yaml. State stored in destinations/pg/state_75658e4f-e5f0-4e35-be0c-bdad33226c94.yaml +⚠️ - Please update any secrets stored in destinations/pg/configuration.yaml +``` +You know have local configuration file for an Airbyte destination that was already existing. +You need to edit any secret value that exist in this configuration as secrets are not imported. +You can edit the configuration and run `octavia apply` to continue managing it with octavia-cli. + +#### `octavia import connection or ` + +Import an existing connection to manage it with octavia-cli. You can use a connection ID or name. + +| **Argument** | **Description** | +| ------------------| ---------------------| +| `CONNECTION_ID` | The connection id. | +| `CONNECTION_NAME` | The connection name. | + +**Examples**: + +```bash +$ octavia import connection poke-to-pg +🐙 - Octavia is targetting your Airbyte instance running at http://localhost:8000 on workspace 75658e4f-e5f0-4e35-be0c-bdad33226c94. +✅ - Imported connection poke-to-pg in connections/poke-to-pg/configuration.yaml. State stored in connections/poke-to-pg/state_75658e4f-e5f0-4e35-be0c-bdad33226c94.yaml +⚠️ - Please update any secrets stored in connections/poke-to-pg/configuration.yaml +``` +You know have local configuration file for an Airbyte connection that was already existing. +**N.B.: You first need to import the source and destination used by the connection.** +You can edit the configuration and run `octavia apply` to continue managing it with octavia-cli. + #### `octavia generate source ` Generate a YAML configuration for a source. @@ -607,13 +674,14 @@ You can disable telemetry by setting the `OCTAVIA_ENABLE_TELEMETRY` environment ## Changelog -| Version | Date | Description | PR | -| ------- | ---------- | ------------------------------------------------------------ | ----------------------------------------------------------- | -| 0.39.27 | 2022-06-24 | Create get command to retrieve resources JSON representation | [#13254](https://github.com/airbytehq/airbyte/pull/13254) | -| 0.39.19 | 2022-06-16 | Allow connection management on multiple workspaces | [#13070](https://github.com/airbytehq/airbyte/pull/12727) | -| 0.39.19 | 2022-06-15 | Allow users to set custom HTTP headers | [#12893](https://github.com/airbytehq/airbyte/pull/12893) | -| 0.39.14 | 2022-05-12 | Enable normalization on connection | [#12727](https://github.com/airbytehq/airbyte/pull/12727) | -| 0.37.0 | 2022-05-05 | Use snake case in connection fields | [#12133](https://github.com/airbytehq/airbyte/pull/12133) | -| 0.35.68 | 2022-04-15 | Improve telemetry | [#12072](https://github.com/airbytehq/airbyte/issues/11896) | -| 0.35.68 | 2022-04-12 | Add telemetry | [#11896](https://github.com/airbytehq/airbyte/issues/11896) | -| 0.35.61 | 2022-04-07 | Alpha release | [EPIC](https://github.com/airbytehq/airbyte/issues/10704) | +| Version | Date | Description | PR | +| ------- | ---------- | --------------------------------------------------------------------------------------| ----------------------------------------------------------- | +| 0.39.31 | 2022-06-30 | Create import command to import and manage existing Airbyte resource from octavia-cli | [#14137](https://github.com/airbytehq/airbyte/pull/14137) | +| 0.39.27 | 2022-06-24 | Create get command to retrieve resources JSON representation | [#13254](https://github.com/airbytehq/airbyte/pull/13254) | +| 0.39.19 | 2022-06-16 | Allow connection management on multiple workspaces | [#13070](https://github.com/airbytehq/airbyte/pull/12727) | +| 0.39.19 | 2022-06-15 | Allow users to set custom HTTP headers | [#12893](https://github.com/airbytehq/airbyte/pull/12893) | +| 0.39.14 | 2022-05-12 | Enable normalization on connection | [#12727](https://github.com/airbytehq/airbyte/pull/12727) | +| 0.37.0 | 2022-05-05 | Use snake case in connection fields | [#12133](https://github.com/airbytehq/airbyte/pull/12133) | +| 0.35.68 | 2022-04-15 | Improve telemetry | [#12072](https://github.com/airbytehq/airbyte/issues/11896) | +| 0.35.68 | 2022-04-12 | Add telemetry | [#11896](https://github.com/airbytehq/airbyte/issues/11896) | +| 0.35.61 | 2022-04-07 | Alpha release | [EPIC](https://github.com/airbytehq/airbyte/issues/10704) | diff --git a/octavia-cli/integration_tests/cassettes/test_api_http_headers/test_api_http_headers.yaml b/octavia-cli/integration_tests/cassettes/test_api_http_headers/test_api_http_headers.yaml index 50e6c7586582..24332db97353 100644 --- a/octavia-cli/integration_tests/cassettes/test_api_http_headers/test_api_http_headers.yaml +++ b/octavia-cli/integration_tests/cassettes/test_api_http_headers/test_api_http_headers.yaml @@ -9,7 +9,7 @@ interactions: Custom-Header: - Foo User-Agent: - - octavia-cli/0.39.20 + - octavia-cli/0.39.27 method: GET uri: http://localhost:8000/api/v1/health response: @@ -27,11 +27,11 @@ interactions: Content-Length: - "18" Content-Security-Policy: - - script-src * 'unsafe-inline'; + - script-src * 'unsafe-inline'; worker-src self blob:; Content-Type: - application/json Date: - - Fri, 17 Jun 2022 14:51:34 GMT + - Wed, 29 Jun 2022 10:53:05 GMT Server: - nginx/1.19.10 status: @@ -49,7 +49,7 @@ interactions: Custom-Header: - Foo User-Agent: - - octavia-cli/0.39.20 + - octavia-cli/0.39.27 method: POST uri: http://localhost:8000/api/v1/workspaces/list response: @@ -67,11 +67,11 @@ interactions: Content-Length: - "387" Content-Security-Policy: - - script-src * 'unsafe-inline'; + - script-src * 'unsafe-inline'; worker-src self blob:; Content-Type: - application/json Date: - - Fri, 17 Jun 2022 14:51:34 GMT + - Wed, 29 Jun 2022 10:53:05 GMT Server: - nginx/1.19.10 status: @@ -89,7 +89,7 @@ interactions: Custom-Header: - Foo User-Agent: - - octavia-cli/0.39.20 + - octavia-cli/0.39.27 method: POST uri: http://localhost:8000/api/v1/workspaces/get response: @@ -107,11 +107,11 @@ interactions: Content-Length: - "370" Content-Security-Policy: - - script-src * 'unsafe-inline'; + - script-src * 'unsafe-inline'; worker-src self blob:; Content-Type: - application/json Date: - - Fri, 17 Jun 2022 14:51:34 GMT + - Wed, 29 Jun 2022 10:53:05 GMT Server: - nginx/1.19.10 status: @@ -129,39 +129,26 @@ interactions: Custom-Header: - Foo User-Agent: - - octavia-cli/0.39.20 + - octavia-cli/0.39.27 method: POST uri: http://localhost:8000/api/v1/source_definitions/list response: body: string: - "{\"sourceDefinitions\":[{\"sourceDefinitionId\":\"789f8e7a-2d28-11ec-8d3d-0242ac130003\",\"name\":\"Lemlist\",\"dockerRepository\":\"airbyte/source-lemlist\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/lemlist\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"eff3616a-f9c3-11eb-9a03-0242ac130003\",\"name\":\"Google - Analytics\",\"dockerRepository\":\"airbyte/source-google-analytics-v4\",\"dockerImageTag\":\"0.1.21\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/google-analytics-v4\",\"icon\":\"\\n\\n\\n\\n\\t\\n\\t\\t\\n\\t\\n\\t\\n\\t\\t\\n\\t\\n\\t\\n\\t\\t\\n\\t\\n\\n\\n\",\"releaseStage\":\"beta\"},{\"sourceDefinitionId\":\"c2281cee-86f9-4a86-bb48-d23286b4c7bd\",\"name\":\"Slack\",\"dockerRepository\":\"airbyte/source-slack\",\"dockerImageTag\":\"0.1.15\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/slack\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"435bb9a5-7887-4809-aa58-28c27df0d7ad\",\"name\":\"MySQL\",\"dockerRepository\":\"airbyte/source-mysql\",\"dockerImageTag\":\"0.5.11\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/mysql\",\"icon\":\"\\n + \ \\n \\n \\n \\n \\n \\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"435bb9a5-7887-4809-aa58-28c27df0d7ad\",\"name\":\"MySQL\",\"dockerRepository\":\"airbyte/source-mysql\",\"dockerImageTag\":\"0.5.15\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/mysql\",\"icon\":\"\\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"ef69ef6e-aa7f-4af1-a01d-ef775033524e\",\"name\":\"GitHub\",\"dockerRepository\":\"airbyte/source-github\",\"dockerImageTag\":\"0.2.35\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/github\",\"icon\":\"\\n\\n\\n\",\"releaseStage\":\"beta\"},{\"sourceDefinitionId\":\"d917a47b-8537-4d0d-8c10-36a9928d4265\",\"name\":\"Kafka\",\"dockerRepository\":\"airbyte/source-kafka\",\"dockerImageTag\":\"0.1.6\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/kafka\",\"icon\":\"\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"6371b14b-bc68-4236-bfbd-468e8df8e968\",\"name\":\"PokeAPI\",\"dockerRepository\":\"airbyte/source-pokeapi\",\"dockerImageTag\":\"0.1.5\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/pokeapi\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"e7778cfc-e97c-4458-9ecb-b4f2bba8946c\",\"name\":\"Facebook - Marketing\",\"dockerRepository\":\"airbyte/source-facebook-marketing\",\"dockerImageTag\":\"0.2.53\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/facebook-marketing\",\"icon\":\"\\nimage/svg+xml\\n\",\"releaseStage\":\"generally_available\"},{\"sourceDefinitionId\":\"aea2fd0d-377d-465e-86c0-4fdc4f688e51\",\"name\":\"Zoom\",\"dockerRepository\":\"airbyte/source-zoom-singer\",\"dockerImageTag\":\"0.2.4\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/zoom\",\"icon\":\"\\n\\n \\n \\n - \ \\n image/svg+xml\\n - \ \\n \\n \\n \\n - \ \\n \\n \\n \\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"d1aa448b-7c54-498e-ad95-263cbebcd2db\",\"name\":\"Tempo\",\"dockerRepository\":\"airbyte/source-tempo\",\"dockerImageTag\":\"0.2.5\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/tempo\",\"icon\":\"\\n\\n\\n \\n \\n \\n \\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"010eb12f-837b-4685-892d-0a39f76a98f5\",\"name\":\"Facebook - Pages\",\"dockerRepository\":\"airbyte/source-facebook-pages\",\"dockerImageTag\":\"0.1.6\",\"documentationUrl\":\"https://docs.airbyte.com/integrations/sources/facebook-pages\",\"icon\":\"\\nimage/svg+xml\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"fbb5fbe2-16ad-4cf4-af7d-ff9d9c316c87\",\"name\":\"Sendgrid\",\"dockerRepository\":\"airbyte/source-sendgrid\",\"dockerImageTag\":\"0.2.6\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/sendgrid\",\"icon\":\"\\nimage/svg+xml\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"d6f73702-d7a0-4e95-9758-b0fb1af0bfba\",\"name\":\"Jenkins\",\"dockerRepository\":\"farosai/airbyte-jenkins-source\",\"dockerImageTag\":\"0.1.23\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/jenkins\",\"icon\":\"\\n\\n\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"2817b3f0-04e4-4c7a-9f32-7a5e8a83db95\",\"name\":\"PagerDuty\",\"dockerRepository\":\"farosai/airbyte-pagerduty-source\",\"dockerImageTag\":\"0.1.23\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/pagerduty\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"008b2e26-11a3-11ec-82a8-0242ac130003\",\"name\":\"Commercetools\",\"dockerRepository\":\"airbyte/source-commercetools\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/commercetools\",\"icon\":\"\\n\\n\\n\\n\\n\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\t\\n\\t\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\n\\n\\n\\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"200330b2-ea62-4d11-ac6d-cfe3e3f8ab2b\",\"name\":\"Snapchat - Marketing\",\"dockerRepository\":\"airbyte/source-snapchat-marketing\",\"dockerImageTag\":\"0.1.4\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/snapchat-marketing\",\"icon\":\"\\n\\n\\n\\n - \ \\n \\n \\n - \ \\n \\n image/svg+xml\\n - \ \\n \\n \\n \\n - \ \\n \\n - \ \\n - \ \\n - \ \\n \\n \\n \\n \\n \\n \\n - \ \\n\\t\\n\\t\\t\\n\\n\\t\\n\\n\\t\\n\\n\\n \\n \\n\\t.st0{fill:#FFFFFF;}\\n\\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"cf40a7f8-71f8-45ce-a7fa-fca053e4028c\",\"name\":\"Confluence\",\"dockerRepository\":\"airbyte/source-confluence\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/confluence\",\"icon\":\"\\n\\n - \ \\n \\n - \ \\n - \ \\n - \ \\n \\n - \ \\n - \ \\n - \ \\n \\n \\n\\t\\t\\t\\t\\n\\t\\t\\t\\t\\n\\t\\t\\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"447e0381-3780-4b46-bb62-00a4e3c8b8e2\",\"name\":\"IBM - Db2\",\"dockerRepository\":\"airbyte/source-db2\",\"dockerImageTag\":\"0.1.10\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/db2\",\"icon\":\"\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"2af123bf-0aaf-4e0d-9784-cb497f23741a\",\"name\":\"Appstore\",\"dockerRepository\":\"airbyte/source-appstore-singer\",\"dockerImageTag\":\"0.2.6\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/appstore\",\"icon\":\"\\n\\n - \ \\n \\n \\n \\n - \ \\n \\n\\t\\t\\n\\t\\t\\t\\t\\n\\t\\t\\t\\t\\n\\t\\t\\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"c8630570-086d-4a40-99ae-ea5b18673071\",\"name\":\"Zendesk - Talk\",\"dockerRepository\":\"airbyte/source-zendesk-talk\",\"dockerImageTag\":\"0.1.3\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/zendesk-talk\",\"icon\":\"\\nimage/svg+xml\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"bfd1ddf8-ae8a-4620-b1d7-55597d2ba08c\",\"name\":\"BigQuery\",\"dockerRepository\":\"airbyte/source-bigquery\",\"dockerImageTag\":\"0.1.7\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/bigquery\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"eca08d79-7b92-4065-b7f3-79c14836ebe7\",\"name\":\"Freshsales\",\"dockerRepository\":\"airbyte/source-freshsales\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/freshsales\",\"icon\":\"freshsales_logo_color\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"b39a7370-74c3-45a6-ac3a-380d48520a83\",\"name\":\"Oracle - DB\",\"dockerRepository\":\"airbyte/source-oracle\",\"dockerImageTag\":\"0.3.15\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/oracle\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"2fed2292-5586-480c-af92-9944e39fe12d\",\"name\":\"Short.io\",\"dockerRepository\":\"airbyte/source-shortio\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/shortio\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"79c1aa37-dae3-42ae-b333-d1c105477715\",\"name\":\"Zendesk - Support\",\"dockerRepository\":\"airbyte/source-zendesk-support\",\"dockerImageTag\":\"0.2.10\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/zendesk-support\",\"icon\":\"\\nimage/svg+xml\\n\",\"releaseStage\":\"generally_available\"},{\"sourceDefinitionId\":\"fa9f58c6-2d03-4237-aaa4-07d75e0c1396\",\"name\":\"Amplitude\",\"dockerRepository\":\"airbyte/source-amplitude\",\"dockerImageTag\":\"0.1.10\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/amplitude\",\"icon\":\"\\n\\t\\n\\t\\n\\t\\n\",\"releaseStage\":\"generally_available\"},{\"sourceDefinitionId\":\"8d7ef552-2c0f-11ec-8d3d-0242ac130003\",\"name\":\"SearchMetrics\",\"dockerRepository\":\"airbyte/source-search-metrics\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/search-metrics\",\"icon\":\"\\n\\n\\n\\nCreated by potrace 1.16, written by Peter Selinger - 2001-2019\\n\\n\\n\\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"bb6afd81-87d5-47e3-97c4-e2c2901b1cf8\",\"name\":\"OneSignal\",\"dockerRepository\":\"airbyte/source-onesignal\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/onesignal\",\"icon\":\"\\n\\n \\n \\n - \ \\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"dfffecb7-9a13-43e9-acdc-b92af7997ca9\",\"name\":\"Close.com\",\"dockerRepository\":\"airbyte/source-close-com\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/close-com\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"29b409d9-30a5-4cc8-ad50-886eb846fea3\",\"name\":\"QuickBooks\",\"dockerRepository\":\"airbyte/source-quickbooks-singer\",\"dockerImageTag\":\"0.1.5\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/quickbooks\",\"icon\":\" qb-logoCreated with Sketch. - \",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"cd42861b-01fc-4658-a8ab-5d11d0510f01\",\"name\":\"Recurly\",\"dockerRepository\":\"airbyte/source-recurly\",\"dockerImageTag\":\"0.4.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/recurly\",\"icon\":\"\\nimage/svg+xml\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"d8313939-3782-41b0-be29-b3ca20d8dd3a\",\"name\":\"Intercom\",\"dockerRepository\":\"airbyte/source-intercom\",\"dockerImageTag\":\"0.1.19\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/intercom\",\"icon\":\"\",\"releaseStage\":\"generally_available\"},{\"sourceDefinitionId\":\"7b86879e-26c5-4ef6-a5ce-2be5c7b46d1e\",\"name\":\"Linnworks\",\"dockerRepository\":\"airbyte/source-linnworks\",\"dockerImageTag\":\"0.1.5\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/linnworks\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"6e00b415-b02e-4160-bf02-58176a0ae687\",\"name\":\"Notion\",\"dockerRepository\":\"airbyte/source-notion\",\"dockerImageTag\":\"0.1.3\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/notion\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"14c6e7ea-97ed-4f5e-a7b5-25e9a80b8212\",\"name\":\"Airtable\",\"dockerRepository\":\"airbyte/source-airtable\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/airtable\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"80a54ea2-9959-4040-aac1-eee42423ec9b\",\"name\":\"Monday\",\"dockerRepository\":\"airbyte/source-monday\",\"dockerImageTag\":\"0.1.3\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/monday\",\"icon\":\"\\n\\n - \ \\n \\n \\n image/svg+xml\\n - \ \\n \\n \\n \\n \\n \\n Logo / monday.com\\n \\n - \ \\n \\n \\n \\n \\n \\n \\n \\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"6ff047c0-f5d5-4ce5-8c81-204a830fa7e1\",\"name\":\"AWS - CloudTrail\",\"dockerRepository\":\"airbyte/source-aws-cloudtrail\",\"dockerImageTag\":\"0.1.4\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/aws-cloudtrail\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"c47d6804-8b98-449f-970a-5ddb5cb5d7aa\",\"name\":\"Customer.io\",\"dockerRepository\":\"farosai/airbyte-customer-io-source\",\"dockerImageTag\":\"0.1.23\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/customer-io\",\"icon\":\"Logo-Color-NEW\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"686473f1-76d9-4994-9cc7-9b13da46147c\",\"name\":\"Chargebee\",\"dockerRepository\":\"airbyte/source-chargebee\",\"dockerImageTag\":\"0.1.11\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/chargebee\",\"icon\":\"\\n\\n - \ \\n \\n - \ \\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"778daa7c-feaf-4db6-96f3-70fd645acc77\",\"name\":\"File\",\"dockerRepository\":\"airbyte/source-file\",\"dockerImageTag\":\"0.2.10\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/file\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"b117307c-14b6-41aa-9422-947e34922962\",\"name\":\"Salesforce\",\"dockerRepository\":\"airbyte/source-salesforce\",\"dockerImageTag\":\"1.0.10\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/salesforce\",\"icon\":\"\\n\\n\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\n\",\"releaseStage\":\"generally_available\"},{\"sourceDefinitionId\":\"d913b0f2-cc51-4e55-a44c-8ba1697b9239\",\"name\":\"Paypal - Transaction\",\"dockerRepository\":\"airbyte/source-paypal-transaction\",\"dockerImageTag\":\"0.1.5\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/paypal-transaction\",\"icon\":\"\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"b2e713cd-cc36-4c0a-b5bd-b47cb8a0561e\",\"name\":\"MongoDb\",\"dockerRepository\":\"airbyte/source-mongodb-v2\",\"dockerImageTag\":\"0.1.14\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/mongodb-v2\",\"icon\":\"\\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"253487c0-2246-43ba-a21f-5116b20a2c50\",\"name\":\"Google - Ads\",\"dockerRepository\":\"airbyte/source-google-ads\",\"dockerImageTag\":\"0.1.42\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/google-ads\",\"icon\":\"\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\",\"releaseStage\":\"generally_available\"},{\"sourceDefinitionId\":\"95e8cffd-b8c4-4039-968e-d32fb4a69bde\",\"name\":\"Klaviyo\",\"dockerRepository\":\"airbyte/source-klaviyo\",\"dockerImageTag\":\"0.1.4\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/klaviyo\",\"icon\":\"\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"90916976-a132-4ce9-8bce-82a03dd58788\",\"name\":\"BambooHR\",\"dockerRepository\":\"airbyte/source-bamboo-hr\",\"dockerImageTag\":\"0.2.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/bamboo-hr\",\"icon\":\"\\n\\n \\n BambooHR\\n Created - with Sketch.\\n \\n \\n \\n - \ \\n \\n \\n \\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"eb4c9e00-db83-4d63-a386-39cfa91012a8\",\"name\":\"Google - Search Console\",\"dockerRepository\":\"airbyte/source-google-search-console\",\"dockerImageTag\":\"0.1.12\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/google-search-console\",\"icon\":\"\\n\\n \\n Artboard\\n - \ Created with Sketch.\\n \\n \\n - \ \\n \\n - \ \\n \\n - \ \\n \\n - \ \\n - \ \\n - \ \\n \\n \\n \\n \\n - \ \\n - \ \\n \\n \\n \\n \\n \\n - \ \\n \\n - \ \\n \\n \\n - \ \\n \\n \\n\",\"releaseStage\":\"beta\"},{\"sourceDefinitionId\":\"bad83517-5e54-4a3d-9b53-63e85fbd4d7c\",\"name\":\"ClickHouse\",\"dockerRepository\":\"airbyte/source-clickhouse\",\"dockerImageTag\":\"0.1.10\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/clickhouse\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"1d4fdb25-64fc-4569-92da-fcdca79a8372\",\"name\":\"Okta\",\"dockerRepository\":\"airbyte/source-okta\",\"dockerImageTag\":\"0.1.4\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/okta\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"374ebc65-6636-4ea0-925c-7d35999a8ffc\",\"name\":\"Smartsheets\",\"dockerRepository\":\"airbyte/source-smartsheets\",\"dockerImageTag\":\"0.1.12\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/smartsheets\",\"icon\":\"\",\"releaseStage\":\"beta\"},{\"sourceDefinitionId\":\"47f25999-dd5e-4636-8c39-e7cea2453331\",\"name\":\"Bing - Ads\",\"dockerRepository\":\"airbyte/source-bing-ads\",\"dockerImageTag\":\"0.1.7\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/bing-ads\",\"icon\":\"\\n\\n \\n \\n \\n\",\"releaseStage\":\"beta\"},{\"sourceDefinitionId\":\"d60a46d4-709f-4092-a6b7-2457f7d455f5\",\"name\":\"Prestashop\",\"dockerRepository\":\"airbyte/source-prestashop\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/presta-shop\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"71607ba1-c0ac-4799-8049-7f4b90dd50f7\",\"name\":\"Google - Sheets\",\"dockerRepository\":\"airbyte/source-google-sheets\",\"dockerImageTag\":\"0.2.15\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/google-sheets\",\"icon\":\"\\n\\n\\n\\n\\t\\n\\t\\n\\t\\n\\n\\n\",\"releaseStage\":\"generally_available\"},{\"sourceDefinitionId\":\"492b56d1-937c-462e-8076-21ad2031e784\",\"name\":\"Hellobaton\",\"dockerRepository\":\"airbyte/source-hellobaton\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/hellobaton\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"59f1e50a-331f-4f09-b3e8-2e8d4d355f44\",\"name\":\"Greenhouse\",\"dockerRepository\":\"airbyte/source-greenhouse\",\"dockerImageTag\":\"0.2.7\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/greenhouse\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"d8540a80-6120-485d-b7d6-272bca477d9b\",\"name\":\"OpenWeather\",\"dockerRepository\":\"airbyte/source-openweather\",\"dockerImageTag\":\"0.1.4\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/openweather\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"45d2e135-2ede-49e1-939f-3e3ec357a65e\",\"name\":\"Recharge\",\"dockerRepository\":\"airbyte/source-recharge\",\"dockerImageTag\":\"0.1.5\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/recharge\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"d53f9084-fa6b-4a5a-976c-5b8392f4ad8a\",\"name\":\"E2E - Testing\",\"dockerRepository\":\"airbyte/source-e2e-test\",\"dockerImageTag\":\"2.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/e2e-test\",\"icon\":\"\\n \\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"cd06e646-31bf-4dc8-af48-cbc6530fcad3\",\"name\":\"Kustomer\",\"dockerRepository\":\"airbyte/source-kustomer-singer\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/kustomer\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"b9dc6155-672e-42ea-b10d-9f1f1fb95ab1\",\"name\":\"Twilio\",\"dockerRepository\":\"airbyte/source-twilio\",\"dockerImageTag\":\"0.1.4\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/twilio\",\"icon\":\"\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"3052c77e-8b91-47e2-97a0-a29a22794b4b\",\"name\":\"PersistIq\",\"dockerRepository\":\"airbyte/source-persistiq\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/persistiq\",\"icon\":\"\\n - \ \\n \\n \\n \\n \\n \\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"193bdcb8-1dd9-48d1-aade-91cadfd74f9b\",\"name\":\"Paystack\",\"dockerRepository\":\"airbyte/source-paystack\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/paystack\",\"icon\":\"\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"63cea06f-1c75-458d-88fe-ad48c7cb27fd\",\"name\":\"Braintree\",\"dockerRepository\":\"airbyte/source-braintree\",\"dockerImageTag\":\"0.1.3\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/braintree\",\"icon\":\"\\n\\n - \ \\n \\n - \ \\n \\n - \ \\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"b6604cbd-1b12-4c08-8767-e140d0fb0877\",\"name\":\"Chartmogul\",\"dockerRepository\":\"airbyte/source-chartmogul\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/chartmogul\",\"icon\":\"\\n\\n\\n\\n\\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"d8286229-c680-4063-8c59-23b9b391c700\",\"name\":\"Pipedrive\",\"dockerRepository\":\"airbyte/source-pipedrive\",\"dockerImageTag\":\"0.1.12\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/pipedrive\",\"icon\":\"\\n\\n\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"3dc3037c-5ce8-4661-adc2-f7a9e3c5ece5\",\"name\":\"Zuora\",\"dockerRepository\":\"airbyte/source-zuora\",\"dockerImageTag\":\"0.1.3\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/zuora\",\"icon\":\"\\n\\n\\nimage/svg+xml\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"f1e4c7f6-db5c-4035-981f-d35ab4998794\",\"name\":\"Zenloop\",\"dockerRepository\":\"airbyte/source-zenloop\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/zenloop\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"9e0556f4-69df-4522-a3fb-03264d36b348\",\"name\":\"Marketo\",\"dockerRepository\":\"airbyte/source-marketo\",\"dockerImageTag\":\"0.1.3\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/marketo\",\"icon\":\"\\nimage/svg+xml\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"7f0455fb-4518-4ec0-b7a3-d808bf8081cc\",\"name\":\"Orb\",\"dockerRepository\":\"airbyte/source-orb\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/orb\",\"icon\":\"\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"4942d392-c7b5-4271-91f9-3b4f4e51eb3e\",\"name\":\"ZohoCRM\",\"dockerRepository\":\"airbyte/source-zoho-crm\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.com/integrations/sources/zoho-crm\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"9b2d3607-7222-4709-9fa2-c2abdebbdd88\",\"name\":\"Chargify\",\"dockerRepository\":\"airbyte/source-chargify\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/chargify\",\"icon\":\"\\n\\n\\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"b5ea17b1-f170-46dc-bc31-cc744ca984c1\",\"name\":\"Microsoft - SQL Server (MSSQL)\",\"dockerRepository\":\"airbyte/source-mssql\",\"dockerImageTag\":\"0.4.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/mssql\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"40d24d0f-b8f9-4fe0-9e6c-b06c0f3f45e4\",\"name\":\"Zendesk - Chat\",\"dockerRepository\":\"airbyte/source-zendesk-chat\",\"dockerImageTag\":\"0.1.7\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/zendesk-chat\",\"icon\":\"\\nimage/svg+xml\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"12928b32-bf0a-4f1e-964f-07e12e37153a\",\"name\":\"Mixpanel\",\"dockerRepository\":\"airbyte/source-mixpanel\",\"dockerImageTag\":\"0.1.17\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/mixpanel\",\"icon\":\"\\n\\n\",\"releaseStage\":\"beta\"},{\"sourceDefinitionId\":\"d19ae824-e289-4b14-995a-0632eb46d246\",\"name\":\"Google - Directory\",\"dockerRepository\":\"airbyte/source-google-directory\",\"dockerImageTag\":\"0.1.9\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/google-directory\",\"icon\":\"\\n\\n\\n\\n - \ \\n\\n\\n \\n\\n\\n - \ \\n\\n\\n\\n \\n\\n\\n - \ \\n\\n\\n - \ \\n\\n\\n\\n \\n\\n\\n - \ \\n\\n\\n - \ \\n\\n\\n\\n \\n\\n\\n - \ \\n\\n\\n \\n\\n\\n\\n \\n\\n\\n \\n\\n\\n \\n\\n\\n\\n \\n\\n\\n - \ \\n\\n\\n \\n\\n\\n\\n \\n\\n\\n - \ \\n\\n\\n \\n\\n\\n\\n \\n\\n\\n - \ \\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"e2b40e36-aa0e-4bed-b41b-bcea6fa348b1\",\"name\":\"Exchange - Rates Api\",\"dockerRepository\":\"airbyte/source-exchange-rates\",\"dockerImageTag\":\"0.2.6\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/exchangeratesapi\",\"icon\":\"\\n\\n \\n logo\\n - \ Created with Sketch.\\n \\n \\n - \ \\n \\n \\n \\n - \ \\n \\n \\n \\n \\n \\n - \ \\n \\n \\n \\n - \ \\n \\n \\n \\n - \ \\n \\n - \ \\n \\n \\n - \ \\n \\n \\n \\n - \ \\n \\n \\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"0dad1a35-ccf8-4d03-b73e-6788c00b13ae\",\"name\":\"TiDB\",\"dockerRepository\":\"airbyte/source-tidb\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/tidb\",\"icon\":\"\\n - \ \\n \\n - \ \\n \\n \\n \\n \\n - \ \\n \\n - \ \\n - \ \\n - \ \\n - \ \\n \\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"dfd88b22-b603-4c3d-aad7-3701784586b1\",\"name\":\"Faker\",\"dockerRepository\":\"airbyte/source-faker\",\"dockerImageTag\":\"0.1.5\",\"documentationUrl\":\"https://docs.airbyte.com/integrations/source-faker\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"db04ecd1-42e7-4115-9cec-95812905c626\",\"name\":\"Retently\",\"dockerRepository\":\"airbyte/source-retently\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/retently\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"137ece28-5434-455c-8f34-69dc3782f451\",\"name\":\"LinkedIn - Ads\",\"dockerRepository\":\"airbyte/source-linkedin-ads\",\"dockerImageTag\":\"0.1.8\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/linkedin-ads\",\"icon\":\"\\n\\n\\n - \ \\n \\n \\n - \ \\n\\n\",\"releaseStage\":\"generally_available\"},{\"sourceDefinitionId\":\"d0243522-dccf-4978-8ba0-37ed47a0bdbf\",\"name\":\"Asana\",\"dockerRepository\":\"airbyte/source-asana\",\"dockerImageTag\":\"0.1.3\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/asana\",\"icon\":\"\\n\\n - \ \\n \\n \\n \\n - \ \\n - \ \\n \\n \\n \\n - \ \\n \\n \\n - \ \\n - \ \\n - \ \\n - \ \\n \\n - \ \\n \\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"9fa5862c-da7c-11eb-8d19-0242ac130003\",\"name\":\"Cockroachdb\",\"dockerRepository\":\"airbyte/source-cockroachdb\",\"dockerImageTag\":\"0.1.12\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/cockroachdb\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"36c891d9-4bd9-43ac-bad2-10e12756272c\",\"name\":\"HubSpot\",\"dockerRepository\":\"airbyte/source-hubspot\",\"dockerImageTag\":\"0.1.70\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/hubspot\",\"icon\":\"\\n\\n\",\"releaseStage\":\"generally_available\"},{\"sourceDefinitionId\":\"5b9cb09e-1003-4f9c-983d-5779d1b2cd51\",\"name\":\"Mailgun\",\"dockerRepository\":\"airbyte/source-mailgun\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/mailgun\",\"icon\":\"\\n - \ \\n \\n - \ \\n \\n \\n \\n \\n \\n - \ \\n \\n \\n \\n \\n \\n \\n - \ \\n - \ \\n \\n \\n - \ \\n - \ \\n \\n \\n \\n - \ \\n \\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"e094cb9a-26de-4645-8761-65c0c425d1de\",\"name\":\"Stripe\",\"dockerRepository\":\"airbyte/source-stripe\",\"dockerImageTag\":\"0.1.33\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/stripe\",\"icon\":\"Asset - 32Stone - Hub\",\"releaseStage\":\"generally_available\"},{\"sourceDefinitionId\":\"c4cfaeda-c757-489a-8aba-859fb08b6970\",\"name\":\"US - Census\",\"dockerRepository\":\"airbyte/source-us-census\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/us-census\",\"icon\":\"\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"fe2b4084-3386-4d3b-9ad6-308f61a6f1e6\",\"name\":\"Harvest\",\"dockerRepository\":\"airbyte/source-harvest\",\"dockerImageTag\":\"0.1.8\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/harvest\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"b03a9f3e-22a5-11eb-adc1-0242ac120002\",\"name\":\"Mailchimp\",\"dockerRepository\":\"airbyte/source-mailchimp\",\"dockerImageTag\":\"0.2.14\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/mailchimp\",\"icon\":\"\",\"releaseStage\":\"generally_available\"},{\"sourceDefinitionId\":\"983fd355-6bf3-4709-91b5-37afa391eeb6\",\"name\":\"Amazon - SQS\",\"dockerRepository\":\"airbyte/source-amazon-sqs\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/amazon-sqs\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"0b5c867e-1b12-4d02-ab74-97b2184ff6d7\",\"name\":\"Dixa\",\"dockerRepository\":\"airbyte/source-dixa\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/dixa\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"decd338e-5647-4c0b-adf4-da0e75f5a750\",\"name\":\"Postgres\",\"dockerRepository\":\"airbyte/source-postgres\",\"dockerImageTag\":\"0.4.25\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/postgres\",\"icon\":\"\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\t\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"47f17145-fe20-4ef5-a548-e29b048adf84\",\"name\":\"Apify - Dataset\",\"dockerRepository\":\"airbyte/source-apify-dataset\",\"dockerImageTag\":\"0.1.11\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/apify-dataset\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"e7eff203-90bf-43e5-a240-19ea3056c474\",\"name\":\"Typeform\",\"dockerRepository\":\"airbyte/source-typeform\",\"dockerImageTag\":\"0.1.6\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/typeform\",\"icon\":\"\\n - \ \\n \\n \\n - \ \\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"8da67652-004c-11ec-9a03-0242ac130003\",\"name\":\"Trello\",\"dockerRepository\":\"airbyte/source-trello\",\"dockerImageTag\":\"0.1.6\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/trello\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"445831eb-78db-4b1f-8f1f-0d96ad8739e2\",\"name\":\"Drift\",\"dockerRepository\":\"airbyte/source-drift\",\"dockerImageTag\":\"0.2.5\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/drift\",\"icon\":\"\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"5e6175e5-68e1-4c17-bff9-56103bbb0d80\",\"name\":\"Gitlab\",\"dockerRepository\":\"airbyte/source-gitlab\",\"dockerImageTag\":\"0.1.5\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/gitlab\",\"icon\":\"\\n\\n\\n\\n\\n\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\n\\n\\n\\t\\n\\t\\n\\t\\n\\t\\n\\tH: 2.5 x\\n\\t1/2 - x\\n\\t1x\\n\\t1x\\n\\t\\n\\t1x\\n\\t\\n\\t1x\\n\\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"9bb85338-ea95-4c93-b267-6be89125b267\",\"name\":\"Freshservice\",\"dockerRepository\":\"airbyte/source-freshservice\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/freshservice\",\"icon\":\"\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"3490c201-5d95-4783-b600-eaf07a4c7787\",\"name\":\"Outreach\",\"dockerRepository\":\"airbyte/source-outreach\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/outreach\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"6fe89830-d04d-401b-aad6-6552ffa5c4af\",\"name\":\"Harness\",\"dockerRepository\":\"farosai/airbyte-harness-source\",\"dockerImageTag\":\"0.1.23\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/harness\",\"icon\":\"\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"325e0640-e7b3-4e24-b823-3361008f603f\",\"name\":\"Zendesk - Sunshine\",\"dockerRepository\":\"airbyte/source-zendesk-sunshine\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/zendesk-sunshine\",\"icon\":\"\\nimage/svg+xml\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"69589781-7828-43c5-9f63-8925b1c1ccc2\",\"name\":\"S3\",\"dockerRepository\":\"airbyte/source-s3\",\"dockerImageTag\":\"0.1.15\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/s3\",\"icon\":\"\\n\\n Icon-Resource/Storage/Res_Amazon-Simple-Storage_Service-Standard_48_Light\\n - \ \\n - \ \\n \\n\\n\",\"releaseStage\":\"beta\"},{\"sourceDefinitionId\":\"7a4327c4-315a-11ec-8d3d-0242ac130003\",\"name\":\"Strava\",\"dockerRepository\":\"airbyte/source-strava\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/strava\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"77225a51-cd15-4a13-af02-65816bd0ecf4\",\"name\":\"Square\",\"dockerRepository\":\"airbyte/source-square\",\"dockerImageTag\":\"0.1.4\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/square\",\"icon\":\"\\n\\n\\n\\n\\n\\n\\n\\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"eaf50f04-21dd-4620-913b-2a83f5635227\",\"name\":\"Microsoft - teams\",\"dockerRepository\":\"airbyte/source-microsoft-teams\",\"dockerImageTag\":\"0.2.5\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/microsoft-teams\",\"icon\":\"\\n\\n\\n\\t\\n\\t\\n\\t\\n\\t\\n\\t\\n\\t\\n\\t\\n]>\\n\\n\\n\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\t\\n\\t\\n\\t\\n\\n\\n\\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"d78e5de0-aa44-4744-aa4f-74c818ccfe19\",\"name\":\"RKI - Covid\",\"dockerRepository\":\"airbyte/source-rki-covid\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/rki-covid\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"e55879a8-0ef8-4557-abcf-ab34c53ec460\",\"name\":\"Amazon - Seller Partner\",\"dockerRepository\":\"airbyte/source-amazon-seller-partner\",\"dockerImageTag\":\"0.2.21\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/amazon-seller-partner\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"00405b19-9768-4e0c-b1ae-9fc2ee2b2a8c\",\"name\":\"Looker\",\"dockerRepository\":\"airbyte/source-looker\",\"dockerImageTag\":\"0.2.7\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/looker\",\"icon\":\"\\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"ed799e2b-2158-4c66-8da4-b40fe63bc72a\",\"name\":\"Plaid\",\"dockerRepository\":\"airbyte/source-plaid\",\"dockerImageTag\":\"0.3.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/plaid\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"e2d65910-8c8b-40a1-ae7d-ee2416b2bfa2\",\"name\":\"Snowflake\",\"dockerRepository\":\"airbyte/source-snowflake\",\"dockerImageTag\":\"0.1.12\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/snowflake\",\"icon\":\"\\n\\n \\n Group\\n Created - with Sketch.\\n \\n \\n - \ \\n \\n \\n \\n - \ \\n \\n \\n - \ \\n - \ \\n - \ \\n - \ \\n \\n - \ \\n \\n \\n - \ \\n \\n \\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"ec4b9503-13cb-48ab-a4ab-6ade4be46567\",\"name\":\"Freshdesk\",\"dockerRepository\":\"airbyte/source-freshdesk\",\"dockerImageTag\":\"0.3.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/freshdesk\",\"icon\":\"\\nimage/svg+xml\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"d60f5393-f99e-4310-8d05-b1876820f40e\",\"name\":\"Pivotal - Tracker\",\"dockerRepository\":\"airbyte/source-pivotal-tracker\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/pivotal-tracker\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"afa734e4-3571-11ec-991a-1e0031268139\",\"name\":\"YouTube - Analytics\",\"dockerRepository\":\"airbyte/source-youtube-analytics\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/youtube-analytics\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"6acf6b55-4f1e-4fca-944e-1a3caef8aba8\",\"name\":\"Instagram\",\"dockerRepository\":\"airbyte/source-instagram\",\"dockerImageTag\":\"0.1.9\",\"documentationUrl\":\"https://docs.airbyte.com/integrations/sources/instagram\",\"icon\":\"\",\"releaseStage\":\"beta\"},{\"sourceDefinitionId\":\"b08e4776-d1de-4e80-ab5c-1e51dad934a2\",\"name\":\"Qualaroo\",\"dockerRepository\":\"airbyte/source-qualaroo\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/qualaroo\",\"icon\":\"\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"badc5925-0485-42be-8caa-b34096cb71b5\",\"name\":\"SurveyMonkey\",\"dockerRepository\":\"airbyte/source-surveymonkey\",\"dockerImageTag\":\"0.1.8\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/surveymonkey\",\"icon\":\"Horizontal_Sabaeus_RGB\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"722ba4bf-06ec-45a4-8dd5-72e4a5cf3903\",\"name\":\"My - Hours\",\"dockerRepository\":\"airbyte/source-my-hours\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/my-hours\",\"icon\":\"\\n\\n - \ \\n \\n \\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"bb1a6d31-6879-4819-a2bd-3eed299ea8e2\",\"name\":\"Cart.com\",\"dockerRepository\":\"airbyte/source-cart\",\"dockerImageTag\":\"0.1.5\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/cart\",\"icon\":\"\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\t\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"f00d2cf4-3c28-499a-ba93-b50b6f26359e\",\"name\":\"TalkDesk - Explore\",\"dockerRepository\":\"airbyte/source-talkdesk-explore\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/talkdesk-explore\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"7e20ce3e-d820-4327-ad7a-88f3927fd97a\",\"name\":\"VictorOps\",\"dockerRepository\":\"farosai/airbyte-victorops-source\",\"dockerImageTag\":\"0.1.23\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/victorops\",\"icon\":\"\\n\\n\\t\\n\\t\\t\\n\\t\\t\\n\\t\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"41991d12-d4b5-439e-afd0-260a31d4c53f\",\"name\":\"SalesLoft\",\"dockerRepository\":\"airbyte/source-salesloft\",\"dockerImageTag\":\"0.1.3\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/salesloft\",\"icon\":\"\\n\\n - \ \\n \\n \\n image/svg+xml\\n - \ \\n \\n \\n \\n - \ \\n \\n \\n - \ \\n \\n \\n \\n \\n \\n \\n \\n - \ \\n \\n \\n - \ \\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"4bfac00d-ce15-44ff-95b9-9e3c3e8fbd35\",\"name\":\"TikTok - Marketing\",\"dockerRepository\":\"airbyte/source-tiktok-marketing\",\"dockerImageTag\":\"0.1.12\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/tiktok-marketing\",\"icon\":\"\\n\\n \\n \u7F16\u7EC4\\n - \ Created with Sketch.\\n \\n \\n - \ \\n \\n \\n - \ \\n \\n \\n - \ \\n - \ \\n \\n \\n \\n\",\"releaseStage\":\"beta\"},{\"sourceDefinitionId\":\"2e875208-0c0b-4ee4-9e92-1cb3156ea799\",\"name\":\"Iterable\",\"dockerRepository\":\"airbyte/source-iterable\",\"dockerImageTag\":\"0.1.15\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/iterable\",\"icon\":\"\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\t\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\r\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"3981c999-bd7d-4afc-849b-e53dea90c948\",\"name\":\"Lever - Hiring\",\"dockerRepository\":\"airbyte/source-lever-hiring\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/lever-hiring\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"3981c999-bd7d-4afc-849b-e53dea90c948\",\"name\":\"Lever + Hiring\",\"dockerRepository\":\"airbyte/source-lever-hiring\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/lever-hiring\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"798ae795-5189-42b6-b64e-3cb91db93338\",\"name\":\"Azure + fill=\\\"#a878c8\\\" fill-rule=\\\"evenodd\\\">\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"778daa7c-feaf-4db6-96f3-70fd645acc77\",\"name\":\"File\",\"dockerRepository\":\"airbyte/source-file\",\"dockerImageTag\":\"0.2.10\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/file\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"aea2fd0d-377d-465e-86c0-4fdc4f688e51\",\"name\":\"Zoom\",\"dockerRepository\":\"airbyte/source-zoom-singer\",\"dockerImageTag\":\"0.2.4\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/zoom\",\"icon\":\"\\n\\n \\n \\n + \ \\n image/svg+xml\\n + \ \\n \\n \\n \\n + \ \\n \\n \\n \\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"b39a7370-74c3-45a6-ac3a-380d48520a83\",\"name\":\"Oracle + DB\",\"dockerRepository\":\"airbyte/source-oracle\",\"dockerImageTag\":\"0.3.17\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/oracle\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"b117307c-14b6-41aa-9422-947e34922962\",\"name\":\"Salesforce\",\"dockerRepository\":\"airbyte/source-salesforce\",\"dockerImageTag\":\"1.0.10\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/salesforce\",\"icon\":\"\\n\\n\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\n\",\"releaseStage\":\"generally_available\"},{\"sourceDefinitionId\":\"b5ea17b1-f170-46dc-bc31-cc744ca984c1\",\"name\":\"Microsoft + SQL Server (MSSQL)\",\"dockerRepository\":\"airbyte/source-mssql\",\"dockerImageTag\":\"0.4.5\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/mssql\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"c2281cee-86f9-4a86-bb48-d23286b4c7bd\",\"name\":\"Slack\",\"dockerRepository\":\"airbyte/source-slack\",\"dockerImageTag\":\"0.1.15\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/slack\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"90916976-a132-4ce9-8bce-82a03dd58788\",\"name\":\"BambooHR\",\"dockerRepository\":\"airbyte/source-bamboo-hr\",\"dockerImageTag\":\"0.2.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/bamboo-hr\",\"icon\":\"\\n\\n \\n BambooHR\\n Created + with Sketch.\\n \\n \\n \\n + \ \\n \\n \\n \\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"eb4c9e00-db83-4d63-a386-39cfa91012a8\",\"name\":\"Google + Search Console\",\"dockerRepository\":\"airbyte/source-google-search-console\",\"dockerImageTag\":\"0.1.12\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/google-search-console\",\"icon\":\"\\n\\n \\n Artboard\\n + \ Created with Sketch.\\n \\n \\n + \ \\n \\n + \ \\n \\n + \ \\n \\n + \ \\n + \ \\n + \ \\n \\n \\n \\n \\n + \ \\n + \ \\n \\n \\n \\n \\n \\n + \ \\n \\n + \ \\n \\n \\n + \ \\n \\n \\n\",\"releaseStage\":\"beta\"},{\"sourceDefinitionId\":\"eff3616a-f9c3-11eb-9a03-0242ac130003\",\"name\":\"Google + Analytics\",\"dockerRepository\":\"airbyte/source-google-analytics-v4\",\"dockerImageTag\":\"0.1.21\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/google-analytics-v4\",\"icon\":\"\\n\\n\\n\\n\\t\\n\\t\\t\\n\\t\\n\\t\\n\\t\\t\\n\\t\\n\\t\\n\\t\\t\\n\\t\\n\\n\\n\",\"releaseStage\":\"beta\"},{\"sourceDefinitionId\":\"ef69ef6e-aa7f-4af1-a01d-ef775033524e\",\"name\":\"GitHub\",\"dockerRepository\":\"airbyte/source-github\",\"dockerImageTag\":\"0.2.36\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/github\",\"icon\":\"\\n\\n\\n\",\"releaseStage\":\"beta\"},{\"sourceDefinitionId\":\"cd06e646-31bf-4dc8-af48-cbc6530fcad3\",\"name\":\"Kustomer\",\"dockerRepository\":\"airbyte/source-kustomer-singer\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/kustomer\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"fbb5fbe2-16ad-4cf4-af7d-ff9d9c316c87\",\"name\":\"Sendgrid\",\"dockerRepository\":\"airbyte/source-sendgrid\",\"dockerImageTag\":\"0.2.6\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/sendgrid\",\"icon\":\"\\nimage/svg+xml\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"d6f73702-d7a0-4e95-9758-b0fb1af0bfba\",\"name\":\"Jenkins\",\"dockerRepository\":\"farosai/airbyte-jenkins-source\",\"dockerImageTag\":\"0.1.23\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/jenkins\",\"icon\":\"\\n\\n\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"2817b3f0-04e4-4c7a-9f32-7a5e8a83db95\",\"name\":\"PagerDuty\",\"dockerRepository\":\"farosai/airbyte-pagerduty-source\",\"dockerImageTag\":\"0.1.23\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/pagerduty\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"008b2e26-11a3-11ec-82a8-0242ac130003\",\"name\":\"Commercetools\",\"dockerRepository\":\"airbyte/source-commercetools\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/commercetools\",\"icon\":\"\\n\\n\\n\\n\\n\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\t\\n\\t\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\n\\n\\n\\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"200330b2-ea62-4d11-ac6d-cfe3e3f8ab2b\",\"name\":\"Snapchat + Marketing\",\"dockerRepository\":\"airbyte/source-snapchat-marketing\",\"dockerImageTag\":\"0.1.4\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/snapchat-marketing\",\"icon\":\"\\n\\n\\n\\n + \ \\n \\n \\n + \ \\n \\n image/svg+xml\\n + \ \\n \\n \\n \\n + \ \\n \\n + \ \\n + \ \\n + \ \\n \\n \\n \\n \\n \\n \\n + \ \\n\\t\\n\\t\\t\\n\\n\\t\\n\\n\\t\\n\\n\\n \\n \\n\\t.st0{fill:#FFFFFF;}\\n\\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"cf40a7f8-71f8-45ce-a7fa-fca053e4028c\",\"name\":\"Confluence\",\"dockerRepository\":\"airbyte/source-confluence\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/confluence\",\"icon\":\"\\n\\n + \ \\n \\n + \ \\n + \ \\n + \ \\n \\n + \ \\n + \ \\n + \ \\n \\n \\n\\t\\t\\t\\t\\n\\t\\t\\t\\t\\n\\t\\t\\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"447e0381-3780-4b46-bb62-00a4e3c8b8e2\",\"name\":\"IBM + Db2\",\"dockerRepository\":\"airbyte/source-db2\",\"dockerImageTag\":\"0.1.11\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/db2\",\"icon\":\"\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"2af123bf-0aaf-4e0d-9784-cb497f23741a\",\"name\":\"Appstore\",\"dockerRepository\":\"airbyte/source-appstore-singer\",\"dockerImageTag\":\"0.2.6\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/appstore\",\"icon\":\"\\n\\n + \ \\n \\n \\n \\n + \ \\n \\n\\t\\t\\n\\t\\t\\t\\t\\n\\t\\t\\t\\t\\n\\t\\t\\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"c8630570-086d-4a40-99ae-ea5b18673071\",\"name\":\"Zendesk + Talk\",\"dockerRepository\":\"airbyte/source-zendesk-talk\",\"dockerImageTag\":\"0.1.3\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/zendesk-talk\",\"icon\":\"\\nimage/svg+xml\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"bfd1ddf8-ae8a-4620-b1d7-55597d2ba08c\",\"name\":\"BigQuery\",\"dockerRepository\":\"airbyte/source-bigquery\",\"dockerImageTag\":\"0.1.8\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/bigquery\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"eca08d79-7b92-4065-b7f3-79c14836ebe7\",\"name\":\"Freshsales\",\"dockerRepository\":\"airbyte/source-freshsales\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/freshsales\",\"icon\":\"freshsales_logo_color\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"2fed2292-5586-480c-af92-9944e39fe12d\",\"name\":\"Short.io\",\"dockerRepository\":\"airbyte/source-shortio\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/shortio\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"79c1aa37-dae3-42ae-b333-d1c105477715\",\"name\":\"Zendesk + Support\",\"dockerRepository\":\"airbyte/source-zendesk-support\",\"dockerImageTag\":\"0.2.10\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/zendesk-support\",\"icon\":\"\\nimage/svg+xml\\n\",\"releaseStage\":\"generally_available\"},{\"sourceDefinitionId\":\"010eb12f-837b-4685-892d-0a39f76a98f5\",\"name\":\"Facebook + Pages\",\"dockerRepository\":\"airbyte/source-facebook-pages\",\"dockerImageTag\":\"0.1.6\",\"documentationUrl\":\"https://docs.airbyte.com/integrations/sources/facebook-pages\",\"icon\":\"\\nimage/svg+xml\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"fa9f58c6-2d03-4237-aaa4-07d75e0c1396\",\"name\":\"Amplitude\",\"dockerRepository\":\"airbyte/source-amplitude\",\"dockerImageTag\":\"0.1.10\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/amplitude\",\"icon\":\"\\n\\t\\n\\t\\n\\t\\n\",\"releaseStage\":\"generally_available\"},{\"sourceDefinitionId\":\"8d7ef552-2c0f-11ec-8d3d-0242ac130003\",\"name\":\"SearchMetrics\",\"dockerRepository\":\"airbyte/source-search-metrics\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/search-metrics\",\"icon\":\"\\n\\n\\n\\nCreated by potrace 1.16, written by Peter Selinger + 2001-2019\\n\\n\\n\\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"bb6afd81-87d5-47e3-97c4-e2c2901b1cf8\",\"name\":\"OneSignal\",\"dockerRepository\":\"airbyte/source-onesignal\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/onesignal\",\"icon\":\"\\n\\n \\n \\n + \ \\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"dfffecb7-9a13-43e9-acdc-b92af7997ca9\",\"name\":\"Close.com\",\"dockerRepository\":\"airbyte/source-close-com\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/close-com\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"29b409d9-30a5-4cc8-ad50-886eb846fea3\",\"name\":\"QuickBooks\",\"dockerRepository\":\"airbyte/source-quickbooks-singer\",\"dockerImageTag\":\"0.1.5\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/quickbooks\",\"icon\":\" qb-logoCreated with Sketch. + \",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"cd42861b-01fc-4658-a8ab-5d11d0510f01\",\"name\":\"Recurly\",\"dockerRepository\":\"airbyte/source-recurly\",\"dockerImageTag\":\"0.4.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/recurly\",\"icon\":\"\\nimage/svg+xml\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"72d405a3-56d8-499f-a571-667c03406e43\",\"name\":\"Dockerhub\",\"dockerRepository\":\"airbyte/source-dockerhub\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/dockerhub\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"d8313939-3782-41b0-be29-b3ca20d8dd3a\",\"name\":\"Intercom\",\"dockerRepository\":\"airbyte/source-intercom\",\"dockerImageTag\":\"0.1.19\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/intercom\",\"icon\":\"\",\"releaseStage\":\"generally_available\"},{\"sourceDefinitionId\":\"ef580275-d9a9-48bb-af5e-db0f5855be04\",\"name\":\"Webflow\",\"dockerRepository\":\"airbyte/source-webflow\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/webflow\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"7b86879e-26c5-4ef6-a5ce-2be5c7b46d1e\",\"name\":\"Linnworks\",\"dockerRepository\":\"airbyte/source-linnworks\",\"dockerImageTag\":\"0.1.5\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/linnworks\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"6e00b415-b02e-4160-bf02-58176a0ae687\",\"name\":\"Notion\",\"dockerRepository\":\"airbyte/source-notion\",\"dockerImageTag\":\"0.1.3\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/notion\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"14c6e7ea-97ed-4f5e-a7b5-25e9a80b8212\",\"name\":\"Airtable\",\"dockerRepository\":\"airbyte/source-airtable\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/airtable\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"80a54ea2-9959-4040-aac1-eee42423ec9b\",\"name\":\"Monday\",\"dockerRepository\":\"airbyte/source-monday\",\"dockerImageTag\":\"0.1.3\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/monday\",\"icon\":\"\\n\\n + \ \\n \\n \\n image/svg+xml\\n + \ \\n \\n \\n \\n \\n \\n Logo / monday.com\\n \\n + \ \\n \\n \\n \\n \\n \\n \\n \\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"6ff047c0-f5d5-4ce5-8c81-204a830fa7e1\",\"name\":\"AWS + CloudTrail\",\"dockerRepository\":\"airbyte/source-aws-cloudtrail\",\"dockerImageTag\":\"0.1.4\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/aws-cloudtrail\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"c47d6804-8b98-449f-970a-5ddb5cb5d7aa\",\"name\":\"Customer.io\",\"dockerRepository\":\"farosai/airbyte-customer-io-source\",\"dockerImageTag\":\"0.1.23\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/customer-io\",\"icon\":\"Logo-Color-NEW\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"686473f1-76d9-4994-9cc7-9b13da46147c\",\"name\":\"Chargebee\",\"dockerRepository\":\"airbyte/source-chargebee\",\"dockerImageTag\":\"0.1.11\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/chargebee\",\"icon\":\"\\n\\n + \ \\n \\n + \ \\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"b2e713cd-cc36-4c0a-b5bd-b47cb8a0561e\",\"name\":\"MongoDb\",\"dockerRepository\":\"airbyte/source-mongodb-v2\",\"dockerImageTag\":\"0.1.15\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/mongodb-v2\",\"icon\":\"\\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"253487c0-2246-43ba-a21f-5116b20a2c50\",\"name\":\"Google + Ads\",\"dockerRepository\":\"airbyte/source-google-ads\",\"dockerImageTag\":\"0.1.42\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/google-ads\",\"icon\":\"\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\",\"releaseStage\":\"generally_available\"},{\"sourceDefinitionId\":\"95e8cffd-b8c4-4039-968e-d32fb4a69bde\",\"name\":\"Klaviyo\",\"dockerRepository\":\"airbyte/source-klaviyo\",\"dockerImageTag\":\"0.1.4\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/klaviyo\",\"icon\":\"\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"bad83517-5e54-4a3d-9b53-63e85fbd4d7c\",\"name\":\"ClickHouse\",\"dockerRepository\":\"airbyte/source-clickhouse\",\"dockerImageTag\":\"0.1.10\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/clickhouse\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"d913b0f2-cc51-4e55-a44c-8ba1697b9239\",\"name\":\"Paypal + Transaction\",\"dockerRepository\":\"airbyte/source-paypal-transaction\",\"dockerImageTag\":\"0.1.6\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/paypal-transaction\",\"icon\":\"\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"374ebc65-6636-4ea0-925c-7d35999a8ffc\",\"name\":\"Smartsheets\",\"dockerRepository\":\"airbyte/source-smartsheets\",\"dockerImageTag\":\"0.1.12\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/smartsheets\",\"icon\":\"\",\"releaseStage\":\"beta\"},{\"sourceDefinitionId\":\"47f25999-dd5e-4636-8c39-e7cea2453331\",\"name\":\"Bing + Ads\",\"dockerRepository\":\"airbyte/source-bing-ads\",\"dockerImageTag\":\"0.1.7\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/bing-ads\",\"icon\":\"\\n\\n \\n \\n \\n\",\"releaseStage\":\"beta\"},{\"sourceDefinitionId\":\"d60a46d4-709f-4092-a6b7-2457f7d455f5\",\"name\":\"Prestashop\",\"dockerRepository\":\"airbyte/source-prestashop\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/presta-shop\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"71607ba1-c0ac-4799-8049-7f4b90dd50f7\",\"name\":\"Google + Sheets\",\"dockerRepository\":\"airbyte/source-google-sheets\",\"dockerImageTag\":\"0.2.15\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/google-sheets\",\"icon\":\"\\n\\n\\n\\n\\t\\n\\t\\n\\t\\n\\n\\n\",\"releaseStage\":\"generally_available\"},{\"sourceDefinitionId\":\"492b56d1-937c-462e-8076-21ad2031e784\",\"name\":\"Hellobaton\",\"dockerRepository\":\"airbyte/source-hellobaton\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/hellobaton\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"d8540a80-6120-485d-b7d6-272bca477d9b\",\"name\":\"OpenWeather\",\"dockerRepository\":\"airbyte/source-openweather\",\"dockerImageTag\":\"0.1.5\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/openweather\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"45d2e135-2ede-49e1-939f-3e3ec357a65e\",\"name\":\"Recharge\",\"dockerRepository\":\"airbyte/source-recharge\",\"dockerImageTag\":\"0.1.5\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/recharge\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"d53f9084-fa6b-4a5a-976c-5b8392f4ad8a\",\"name\":\"E2E + Testing\",\"dockerRepository\":\"airbyte/source-e2e-test\",\"dockerImageTag\":\"2.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/e2e-test\",\"icon\":\"\\n \\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"b9dc6155-672e-42ea-b10d-9f1f1fb95ab1\",\"name\":\"Twilio\",\"dockerRepository\":\"airbyte/source-twilio\",\"dockerImageTag\":\"0.1.5\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/twilio\",\"icon\":\"\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"1d4fdb25-64fc-4569-92da-fcdca79a8372\",\"name\":\"Okta\",\"dockerRepository\":\"airbyte/source-okta\",\"dockerImageTag\":\"0.1.4\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/okta\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"193bdcb8-1dd9-48d1-aade-91cadfd74f9b\",\"name\":\"Paystack\",\"dockerRepository\":\"airbyte/source-paystack\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/paystack\",\"icon\":\"\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"6f2ac653-8623-43c4-8950-19218c7caf3d\",\"name\":\"Firebolt\",\"dockerRepository\":\"airbyte/source-firebolt\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.firebolt.io/\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"63cea06f-1c75-458d-88fe-ad48c7cb27fd\",\"name\":\"Braintree\",\"dockerRepository\":\"airbyte/source-braintree\",\"dockerImageTag\":\"0.1.3\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/braintree\",\"icon\":\"\\n\\n + \ \\n \\n + \ \\n \\n + \ \\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"b6604cbd-1b12-4c08-8767-e140d0fb0877\",\"name\":\"Chartmogul\",\"dockerRepository\":\"airbyte/source-chartmogul\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/chartmogul\",\"icon\":\"\\n\\n\\n\\n\\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"d8286229-c680-4063-8c59-23b9b391c700\",\"name\":\"Pipedrive\",\"dockerRepository\":\"airbyte/source-pipedrive\",\"dockerImageTag\":\"0.1.12\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/pipedrive\",\"icon\":\"\\n\\n\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"3dc3037c-5ce8-4661-adc2-f7a9e3c5ece5\",\"name\":\"Zuora\",\"dockerRepository\":\"airbyte/source-zuora\",\"dockerImageTag\":\"0.1.3\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/zuora\",\"icon\":\"\\n\\n\\nimage/svg+xml\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"59f1e50a-331f-4f09-b3e8-2e8d4d355f44\",\"name\":\"Greenhouse\",\"dockerRepository\":\"airbyte/source-greenhouse\",\"dockerImageTag\":\"0.2.7\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/greenhouse\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"789f8e7a-2d28-11ec-8d3d-0242ac130003\",\"name\":\"Lemlist\",\"dockerRepository\":\"airbyte/source-lemlist\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/lemlist\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"f1e4c7f6-db5c-4035-981f-d35ab4998794\",\"name\":\"Zenloop\",\"dockerRepository\":\"airbyte/source-zenloop\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/zenloop\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"9e0556f4-69df-4522-a3fb-03264d36b348\",\"name\":\"Marketo\",\"dockerRepository\":\"airbyte/source-marketo\",\"dockerImageTag\":\"0.1.4\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/marketo\",\"icon\":\"\\nimage/svg+xml\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"7f0455fb-4518-4ec0-b7a3-d808bf8081cc\",\"name\":\"Orb\",\"dockerRepository\":\"airbyte/source-orb\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/orb\",\"icon\":\"\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"4942d392-c7b5-4271-91f9-3b4f4e51eb3e\",\"name\":\"ZohoCRM\",\"dockerRepository\":\"airbyte/source-zoho-crm\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.com/integrations/sources/zoho-crm\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"9b2d3607-7222-4709-9fa2-c2abdebbdd88\",\"name\":\"Chargify\",\"dockerRepository\":\"airbyte/source-chargify\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/chargify\",\"icon\":\"\\n\\n\\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"40d24d0f-b8f9-4fe0-9e6c-b06c0f3f45e4\",\"name\":\"Zendesk + Chat\",\"dockerRepository\":\"airbyte/source-zendesk-chat\",\"dockerImageTag\":\"0.1.7\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/zendesk-chat\",\"icon\":\"\\nimage/svg+xml\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"12928b32-bf0a-4f1e-964f-07e12e37153a\",\"name\":\"Mixpanel\",\"dockerRepository\":\"airbyte/source-mixpanel\",\"dockerImageTag\":\"0.1.17\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/mixpanel\",\"icon\":\"\\n\\n\",\"releaseStage\":\"beta\"},{\"sourceDefinitionId\":\"d19ae824-e289-4b14-995a-0632eb46d246\",\"name\":\"Google + Directory\",\"dockerRepository\":\"airbyte/source-google-directory\",\"dockerImageTag\":\"0.1.9\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/google-directory\",\"icon\":\"\\n\\n\\n\\n + \ \\n\\n\\n \\n\\n\\n + \ \\n\\n\\n\\n \\n\\n\\n + \ \\n\\n\\n + \ \\n\\n\\n\\n \\n\\n\\n + \ \\n\\n\\n + \ \\n\\n\\n\\n \\n\\n\\n + \ \\n\\n\\n \\n\\n\\n\\n \\n\\n\\n \\n\\n\\n \\n\\n\\n\\n \\n\\n\\n + \ \\n\\n\\n \\n\\n\\n\\n \\n\\n\\n + \ \\n\\n\\n \\n\\n\\n\\n \\n\\n\\n + \ \\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"e2b40e36-aa0e-4bed-b41b-bcea6fa348b1\",\"name\":\"Exchange + Rates Api\",\"dockerRepository\":\"airbyte/source-exchange-rates\",\"dockerImageTag\":\"0.2.6\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/exchangeratesapi\",\"icon\":\"\\n\\n \\n logo\\n + \ Created with Sketch.\\n \\n \\n + \ \\n \\n \\n \\n + \ \\n \\n \\n \\n \\n \\n + \ \\n \\n \\n \\n + \ \\n \\n \\n \\n + \ \\n \\n + \ \\n \\n \\n + \ \\n \\n \\n \\n + \ \\n \\n \\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"0dad1a35-ccf8-4d03-b73e-6788c00b13ae\",\"name\":\"TiDB\",\"dockerRepository\":\"airbyte/source-tidb\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/tidb\",\"icon\":\"\\n + \ \\n \\n + \ \\n \\n \\n \\n \\n + \ \\n \\n + \ \\n + \ \\n + \ \\n + \ \\n \\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"dfd88b22-b603-4c3d-aad7-3701784586b1\",\"name\":\"Faker\",\"dockerRepository\":\"airbyte/source-faker\",\"dockerImageTag\":\"0.1.5\",\"documentationUrl\":\"https://docs.airbyte.com/integrations/source-faker\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"db04ecd1-42e7-4115-9cec-95812905c626\",\"name\":\"Retently\",\"dockerRepository\":\"airbyte/source-retently\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/retently\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"d917a47b-8537-4d0d-8c10-36a9928d4265\",\"name\":\"Kafka\",\"dockerRepository\":\"airbyte/source-kafka\",\"dockerImageTag\":\"0.1.7\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/kafka\",\"icon\":\"\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"137ece28-5434-455c-8f34-69dc3782f451\",\"name\":\"LinkedIn + Ads\",\"dockerRepository\":\"airbyte/source-linkedin-ads\",\"dockerImageTag\":\"0.1.8\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/linkedin-ads\",\"icon\":\"\\n\\n\\n + \ \\n \\n \\n + \ \\n\\n\",\"releaseStage\":\"generally_available\"},{\"sourceDefinitionId\":\"d0243522-dccf-4978-8ba0-37ed47a0bdbf\",\"name\":\"Asana\",\"dockerRepository\":\"airbyte/source-asana\",\"dockerImageTag\":\"0.1.3\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/asana\",\"icon\":\"\\n\\n + \ \\n \\n \\n \\n + \ \\n + \ \\n \\n \\n \\n + \ \\n \\n \\n + \ \\n + \ \\n + \ \\n + \ \\n \\n + \ \\n \\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"9fa5862c-da7c-11eb-8d19-0242ac130003\",\"name\":\"Cockroachdb\",\"dockerRepository\":\"airbyte/source-cockroachdb\",\"dockerImageTag\":\"0.1.12\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/cockroachdb\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"36c891d9-4bd9-43ac-bad2-10e12756272c\",\"name\":\"HubSpot\",\"dockerRepository\":\"airbyte/source-hubspot\",\"dockerImageTag\":\"0.1.72\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/hubspot\",\"icon\":\"\\n\\n\",\"releaseStage\":\"generally_available\"},{\"sourceDefinitionId\":\"6371b14b-bc68-4236-bfbd-468e8df8e968\",\"name\":\"PokeAPI\",\"dockerRepository\":\"airbyte/source-pokeapi\",\"dockerImageTag\":\"0.1.5\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/pokeapi\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"5b9cb09e-1003-4f9c-983d-5779d1b2cd51\",\"name\":\"Mailgun\",\"dockerRepository\":\"airbyte/source-mailgun\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/mailgun\",\"icon\":\"\\n + \ \\n \\n + \ \\n \\n \\n \\n \\n \\n + \ \\n \\n \\n \\n \\n \\n \\n + \ \\n + \ \\n \\n \\n + \ \\n + \ \\n \\n \\n \\n + \ \\n \\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"e094cb9a-26de-4645-8761-65c0c425d1de\",\"name\":\"Stripe\",\"dockerRepository\":\"airbyte/source-stripe\",\"dockerImageTag\":\"0.1.33\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/stripe\",\"icon\":\"Asset + 32Stone + Hub\",\"releaseStage\":\"generally_available\"},{\"sourceDefinitionId\":\"c4cfaeda-c757-489a-8aba-859fb08b6970\",\"name\":\"US + Census\",\"dockerRepository\":\"airbyte/source-us-census\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/us-census\",\"icon\":\"\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"fe2b4084-3386-4d3b-9ad6-308f61a6f1e6\",\"name\":\"Harvest\",\"dockerRepository\":\"airbyte/source-harvest\",\"dockerImageTag\":\"0.1.8\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/harvest\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"b03a9f3e-22a5-11eb-adc1-0242ac120002\",\"name\":\"Mailchimp\",\"dockerRepository\":\"airbyte/source-mailchimp\",\"dockerImageTag\":\"0.2.14\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/mailchimp\",\"icon\":\"\",\"releaseStage\":\"generally_available\"},{\"sourceDefinitionId\":\"983fd355-6bf3-4709-91b5-37afa391eeb6\",\"name\":\"Amazon + SQS\",\"dockerRepository\":\"airbyte/source-amazon-sqs\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/amazon-sqs\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"0b5c867e-1b12-4d02-ab74-97b2184ff6d7\",\"name\":\"Dixa\",\"dockerRepository\":\"airbyte/source-dixa\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/dixa\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"decd338e-5647-4c0b-adf4-da0e75f5a750\",\"name\":\"Postgres\",\"dockerRepository\":\"airbyte/source-postgres\",\"dockerImageTag\":\"0.4.28\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/postgres\",\"icon\":\"\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\t\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"47f17145-fe20-4ef5-a548-e29b048adf84\",\"name\":\"Apify + Dataset\",\"dockerRepository\":\"airbyte/source-apify-dataset\",\"dockerImageTag\":\"0.1.11\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/apify-dataset\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"e7eff203-90bf-43e5-a240-19ea3056c474\",\"name\":\"Typeform\",\"dockerRepository\":\"airbyte/source-typeform\",\"dockerImageTag\":\"0.1.7\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/typeform\",\"icon\":\"\\n + \ \\n \\n \\n + \ \\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"8da67652-004c-11ec-9a03-0242ac130003\",\"name\":\"Trello\",\"dockerRepository\":\"airbyte/source-trello\",\"dockerImageTag\":\"0.1.6\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/trello\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"445831eb-78db-4b1f-8f1f-0d96ad8739e2\",\"name\":\"Drift\",\"dockerRepository\":\"airbyte/source-drift\",\"dockerImageTag\":\"0.2.5\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/drift\",\"icon\":\"\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"5e6175e5-68e1-4c17-bff9-56103bbb0d80\",\"name\":\"Gitlab\",\"dockerRepository\":\"airbyte/source-gitlab\",\"dockerImageTag\":\"0.1.5\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/gitlab\",\"icon\":\"\\n\\n\\n\\n\\n\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\n\\n\\n\\t\\n\\t\\n\\t\\n\\t\\n\\tH: 2.5 x\\n\\t1/2 + x\\n\\t1x\\n\\t1x\\n\\t\\n\\t1x\\n\\t\\n\\t1x\\n\\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"9bb85338-ea95-4c93-b267-6be89125b267\",\"name\":\"Freshservice\",\"dockerRepository\":\"airbyte/source-freshservice\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/freshservice\",\"icon\":\"\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"3490c201-5d95-4783-b600-eaf07a4c7787\",\"name\":\"Outreach\",\"dockerRepository\":\"airbyte/source-outreach\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/outreach\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"6fe89830-d04d-401b-aad6-6552ffa5c4af\",\"name\":\"Harness\",\"dockerRepository\":\"farosai/airbyte-harness-source\",\"dockerImageTag\":\"0.1.23\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/harness\",\"icon\":\"\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"325e0640-e7b3-4e24-b823-3361008f603f\",\"name\":\"Zendesk + Sunshine\",\"dockerRepository\":\"airbyte/source-zendesk-sunshine\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/zendesk-sunshine\",\"icon\":\"\\nimage/svg+xml\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"69589781-7828-43c5-9f63-8925b1c1ccc2\",\"name\":\"S3\",\"dockerRepository\":\"airbyte/source-s3\",\"dockerImageTag\":\"0.1.15\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/s3\",\"icon\":\"\\n\\n Icon-Resource/Storage/Res_Amazon-Simple-Storage_Service-Standard_48_Light\\n + \ \\n + \ \\n \\n\\n\",\"releaseStage\":\"beta\"},{\"sourceDefinitionId\":\"7a4327c4-315a-11ec-8d3d-0242ac130003\",\"name\":\"Strava\",\"dockerRepository\":\"airbyte/source-strava\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/strava\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"7e20ce3e-d820-4327-ad7a-88f3927fd97a\",\"name\":\"VictorOps\",\"dockerRepository\":\"farosai/airbyte-victorops-source\",\"dockerImageTag\":\"0.1.23\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/victorops\",\"icon\":\"\\n\\n\\t\\n\\t\\t\\n\\t\\t\\n\\t\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"77225a51-cd15-4a13-af02-65816bd0ecf4\",\"name\":\"Square\",\"dockerRepository\":\"airbyte/source-square\",\"dockerImageTag\":\"0.1.4\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/square\",\"icon\":\"\\n\\n\\n\\n\\n\\n\\n\\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"eaf50f04-21dd-4620-913b-2a83f5635227\",\"name\":\"Microsoft + teams\",\"dockerRepository\":\"airbyte/source-microsoft-teams\",\"dockerImageTag\":\"0.2.5\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/microsoft-teams\",\"icon\":\"\\n\\n\\n\\t\\n\\t\\n\\t\\n\\t\\n\\t\\n\\t\\n\\t\\n]>\\n\\n\\n\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\t\\n\\t\\n\\t\\n\\n\\n\\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"d78e5de0-aa44-4744-aa4f-74c818ccfe19\",\"name\":\"RKI + Covid\",\"dockerRepository\":\"airbyte/source-rki-covid\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/rki-covid\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"e55879a8-0ef8-4557-abcf-ab34c53ec460\",\"name\":\"Amazon + Seller Partner\",\"dockerRepository\":\"airbyte/source-amazon-seller-partner\",\"dockerImageTag\":\"0.2.22\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/amazon-seller-partner\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"00405b19-9768-4e0c-b1ae-9fc2ee2b2a8c\",\"name\":\"Looker\",\"dockerRepository\":\"airbyte/source-looker\",\"dockerImageTag\":\"0.2.7\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/looker\",\"icon\":\"\\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"ed799e2b-2158-4c66-8da4-b40fe63bc72a\",\"name\":\"Plaid\",\"dockerRepository\":\"airbyte/source-plaid\",\"dockerImageTag\":\"0.3.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/plaid\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"e2d65910-8c8b-40a1-ae7d-ee2416b2bfa2\",\"name\":\"Snowflake\",\"dockerRepository\":\"airbyte/source-snowflake\",\"dockerImageTag\":\"0.1.12\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/snowflake\",\"icon\":\"\\n\\n \\n Group\\n Created + with Sketch.\\n \\n \\n + \ \\n \\n \\n \\n + \ \\n \\n \\n + \ \\n + \ \\n + \ \\n + \ \\n \\n + \ \\n \\n \\n + \ \\n \\n \\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"ec4b9503-13cb-48ab-a4ab-6ade4be46567\",\"name\":\"Freshdesk\",\"dockerRepository\":\"airbyte/source-freshdesk\",\"dockerImageTag\":\"0.3.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/freshdesk\",\"icon\":\"\\nimage/svg+xml\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"d60f5393-f99e-4310-8d05-b1876820f40e\",\"name\":\"Pivotal + Tracker\",\"dockerRepository\":\"airbyte/source-pivotal-tracker\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/pivotal-tracker\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"afa734e4-3571-11ec-991a-1e0031268139\",\"name\":\"YouTube + Analytics\",\"dockerRepository\":\"airbyte/source-youtube-analytics\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/youtube-analytics\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"6acf6b55-4f1e-4fca-944e-1a3caef8aba8\",\"name\":\"Instagram\",\"dockerRepository\":\"airbyte/source-instagram\",\"dockerImageTag\":\"0.1.9\",\"documentationUrl\":\"https://docs.airbyte.com/integrations/sources/instagram\",\"icon\":\"\",\"releaseStage\":\"beta\"},{\"sourceDefinitionId\":\"b08e4776-d1de-4e80-ab5c-1e51dad934a2\",\"name\":\"Qualaroo\",\"dockerRepository\":\"airbyte/source-qualaroo\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/qualaroo\",\"icon\":\"\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"badc5925-0485-42be-8caa-b34096cb71b5\",\"name\":\"SurveyMonkey\",\"dockerRepository\":\"airbyte/source-surveymonkey\",\"dockerImageTag\":\"0.1.8\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/surveymonkey\",\"icon\":\"Horizontal_Sabaeus_RGB\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"722ba4bf-06ec-45a4-8dd5-72e4a5cf3903\",\"name\":\"My + Hours\",\"dockerRepository\":\"airbyte/source-my-hours\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/my-hours\",\"icon\":\"\\n\\n + \ \\n \\n \\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"e7778cfc-e97c-4458-9ecb-b4f2bba8946c\",\"name\":\"Facebook + Marketing\",\"dockerRepository\":\"airbyte/source-facebook-marketing\",\"dockerImageTag\":\"0.2.53\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/facebook-marketing\",\"icon\":\"\\nimage/svg+xml\\n\",\"releaseStage\":\"generally_available\"},{\"sourceDefinitionId\":\"bb1a6d31-6879-4819-a2bd-3eed299ea8e2\",\"name\":\"Cart.com\",\"dockerRepository\":\"airbyte/source-cart\",\"dockerImageTag\":\"0.1.5\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/cart\",\"icon\":\"\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\t\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"f00d2cf4-3c28-499a-ba93-b50b6f26359e\",\"name\":\"TalkDesk + Explore\",\"dockerRepository\":\"airbyte/source-talkdesk-explore\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/talkdesk-explore\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"c7cb421b-942e-4468-99ee-e369bcabaec5\",\"name\":\"Metabase\",\"dockerRepository\":\"airbyte/source-metabase\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/metabase\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"41991d12-d4b5-439e-afd0-260a31d4c53f\",\"name\":\"SalesLoft\",\"dockerRepository\":\"airbyte/source-salesloft\",\"dockerImageTag\":\"0.1.3\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/salesloft\",\"icon\":\"\\n\\n + \ \\n \\n \\n image/svg+xml\\n + \ \\n \\n \\n \\n + \ \\n \\n \\n + \ \\n \\n \\n \\n \\n \\n \\n \\n + \ \\n \\n \\n + \ \\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"4bfac00d-ce15-44ff-95b9-9e3c3e8fbd35\",\"name\":\"TikTok + Marketing\",\"dockerRepository\":\"airbyte/source-tiktok-marketing\",\"dockerImageTag\":\"0.1.12\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/tiktok-marketing\",\"icon\":\"\\n\\n \\n \u7F16\u7EC4\\n + \ Created with Sketch.\\n \\n \\n + \ \\n \\n \\n + \ \\n \\n \\n + \ \\n + \ \\n \\n \\n \\n\",\"releaseStage\":\"beta\"},{\"sourceDefinitionId\":\"2e875208-0c0b-4ee4-9e92-1cb3156ea799\",\"name\":\"Iterable\",\"dockerRepository\":\"airbyte/source-iterable\",\"dockerImageTag\":\"0.1.15\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/iterable\",\"icon\":\"\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\t\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\r\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"798ae795-5189-42b6-b64e-3cb91db93338\",\"name\":\"Azure Table Storage\",\"dockerRepository\":\"airbyte/source-azure-table\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/azure-table\",\"icon\":\"\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"cdaf146a-9b75-49fd-9dd2-9d64a0bb4781\",\"name\":\"Sentry\",\"dockerRepository\":\"airbyte/source-sentry\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/sentry\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"5cb7e5fe-38c2-11ec-8d3d-0242ac130003\",\"name\":\"Pinterest\",\"dockerRepository\":\"airbyte/source-pinterest\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/pinterest\",\"icon\":\"\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"d1aa448b-7c54-498e-ad95-263cbebcd2db\",\"name\":\"Tempo\",\"dockerRepository\":\"airbyte/source-tempo\",\"dockerImageTag\":\"0.2.5\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/tempo\",\"icon\":\"\\n\\n\\n \\n \\n \\n \\n\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"5cb7e5fe-38c2-11ec-8d3d-0242ac130003\",\"name\":\"Pinterest\",\"dockerRepository\":\"airbyte/source-pinterest\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/pinterest\",\"icon\":\"\\n\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"a827c52e-791c-4135-a245-e233c5255199\",\"name\":\"SFTP\",\"dockerRepository\":\"airbyte/source-sftp\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.com/integrations/sources/sftp\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"ed9dfefa-1bbc-419d-8c5e-4d78f0ef6734\",\"name\":\"Google + 20.256 6.224-43.305c-32.864-27.114-58.722-62.13-73.798-101.966z\\\"/>\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"a827c52e-791c-4135-a245-e233c5255199\",\"name\":\"SFTP\",\"dockerRepository\":\"airbyte/source-sftp\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.com/integrations/sources/sftp\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"cdaf146a-9b75-49fd-9dd2-9d64a0bb4781\",\"name\":\"Sentry\",\"dockerRepository\":\"airbyte/source-sentry\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/sentry\",\"icon\":\"\",\"releaseStage\":\"alpha\"},{\"sourceDefinitionId\":\"ed9dfefa-1bbc-419d-8c5e-4d78f0ef6734\",\"name\":\"Google Workspace Admin Reports\",\"dockerRepository\":\"airbyte/source-google-workspace-admin-reports\",\"dockerImageTag\":\"0.1.8\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/google-workspace-admin-reports\",\"icon\":\"\\n \\n str: + """Helper function to build help message consistently for all the commands in this module. + Args: + resource_type (str): source, destination or connection + Returns: + str: The generated help message. + """ + return f"Import an existing {resource_type} to manage it with octavia-cli." + + +def import_source_or_destination( + api_client: airbyte_api_client.ApiClient, + workspace_id: str, + ResourceClass: Type[Union[UnmanagedSource, UnmanagedDestination]], + resource_to_get: str, +) -> str: + """Helper function to import sources & destinations. + + Args: + api_client (airbyte_api_client.ApiClient): the Airbyte API client. + workspace_id (str): current Airbyte workspace id. + ResourceClass (Union[UnmanagedSource, UnmanagedDestination]): the Airbyte Resource Class. + resource_to_get (str): the name or ID of the resource in the current Airbyte workspace id. + + Returns: + str: The generated import message. + """ + remote_configuration = json.loads(get_json_representation(api_client, workspace_id, ResourceClass, resource_to_get)) + + resource_type = ResourceClass.__name__.lower() + + definition = definitions.factory(resource_type, api_client, workspace_id, remote_configuration[f"{resource_type}_definition_id"]) + + renderer = renderers.ConnectorSpecificationRenderer(remote_configuration["name"], definition) + + new_configuration_path = renderer.import_configuration(project_path=".", configuration=remote_configuration["connection_configuration"]) + managed_resource, state = resources.factory(api_client, workspace_id, new_configuration_path).manage( + remote_configuration[f"{resource_type}_id"] + ) + message = f"✅ - Imported {resource_type} {managed_resource.name} in {new_configuration_path}. State stored in {state.path}" + click.echo(click.style(message, fg="green")) + message = f"⚠️ - Please update any secrets stored in {new_configuration_path}" + click.echo(click.style(message, fg="yellow")) + + +def import_connection( + api_client: airbyte_api_client.ApiClient, + workspace_id: str, + resource_to_get: str, +) -> str: + """Helper function to import connection. + + Args: + api_client (airbyte_api_client.ApiClient): the Airbyte API client. + workspace_id (str): current Airbyte workspace id. + resource_to_get (str): the name or ID of the resource in the current Airbyte workspace id. + + Returns: + str: The generated import message. + """ + remote_configuration = json.loads(get_json_representation(api_client, workspace_id, UnmanagedConnection, resource_to_get)) + source_name, destination_name = remote_configuration["source"]["name"], remote_configuration["destination"]["name"] + source_configuration_path = renderers.ConnectorSpecificationRenderer.get_output_path( + project_path=".", definition_type="source", resource_name=source_name + ) + destination_configuration_path = renderers.ConnectorSpecificationRenderer.get_output_path( + project_path=".", definition_type="destination", resource_name=destination_name + ) + if not source_configuration_path.is_file(): + raise MissingResourceDependencyError( + f"The source {source_name} is not managed by octavia-cli, please import and apply it before importing your connection." + ) + elif not destination_configuration_path.is_file(): + raise MissingResourceDependencyError( + f"The destination {destination_name} is not managed by octavia-cli, please import and apply it before importing your connection." + ) + else: + source = resources.factory(api_client, workspace_id, source_configuration_path) + destination = resources.factory(api_client, workspace_id, destination_configuration_path) + if not source.was_created: + raise resources.NonExistingResourceError( + f"The source defined at {source_configuration_path} does not exists. Please run octavia apply before creating this connection." + ) + if not destination.was_created: + raise resources.NonExistingResourceError( + f"The destination defined at {destination_configuration_path} does not exists. Please run octavia apply before creating this connection." + ) + + connection_name, connection_id = remote_configuration["name"], remote_configuration["connection_id"] + connection_renderer = renderers.ConnectionRenderer(connection_name, source, destination) + new_configuration_path = connection_renderer.import_configuration(".", remote_configuration) + managed_resource, state = resources.factory(api_client, workspace_id, new_configuration_path).manage(connection_id) + message = f"✅ - Imported connection {managed_resource.name} in {new_configuration_path}. State stored in {state.path}" + click.echo(click.style(message, fg="green")) + + +@click.group( + "import", + help=f'{build_help_message("source, destination or connection")}. ID or name can be used as argument. Example: \'octavia import source "My Pokemon source"\' or \'octavia import source cb5413b2-4159-46a2-910a-dc282a439d2d\'', +) +@click.pass_context +def _import(ctx: click.Context): # pragma: no cover + pass + + +@_import.command(cls=OctaviaCommand, name="source", help=build_help_message("source")) +@click.argument("resource", type=click.STRING) +@click.pass_context +@requires_init +def source(ctx: click.Context, resource: str): + click.echo(import_source_or_destination(ctx.obj["API_CLIENT"], ctx.obj["WORKSPACE_ID"], UnmanagedSource, resource)) + + +@_import.command(cls=OctaviaCommand, name="destination", help=build_help_message("destination")) +@click.argument("resource", type=click.STRING) +@click.pass_context +@requires_init +def destination(ctx: click.Context, resource: str): + click.echo(import_source_or_destination(ctx.obj["API_CLIENT"], ctx.obj["WORKSPACE_ID"], UnmanagedDestination, resource)) + + +@_import.command(cls=OctaviaCommand, name="connection", help=build_help_message("connection")) +@click.argument("resource", type=click.STRING) +@click.pass_context +@requires_init +def connection(ctx: click.Context, resource: str): + click.echo(import_connection(ctx.obj["API_CLIENT"], ctx.obj["WORKSPACE_ID"], resource)) + + +AVAILABLE_COMMANDS: List[click.Command] = [source, destination, connection] + + +def add_commands_to_list(): + for command in AVAILABLE_COMMANDS: + _import.add_command(command) + + +add_commands_to_list() diff --git a/octavia-cli/octavia_cli/apply/resources.py b/octavia-cli/octavia_cli/apply/resources.py index b846fa675a98..101e3ee0de9f 100644 --- a/octavia-cli/octavia_cli/apply/resources.py +++ b/octavia-cli/octavia_cli/apply/resources.py @@ -7,7 +7,7 @@ import time from copy import deepcopy from pathlib import Path -from typing import Callable, List, Optional, Set, Type, Union +from typing import Callable, List, Optional, Set, Tuple, Type, Union import airbyte_api_client import click @@ -75,10 +75,14 @@ class MissingStateError(click.ClickException): class ResourceState: def __init__( - self, configuration_path: str, workspace_id: Optional[str], resource_id: str, generation_timestamp: int, configuration_hash: str + self, + configuration_path: Union[str, Path], + workspace_id: Optional[str], + resource_id: str, + generation_timestamp: int, + configuration_hash: str, ): """This constructor is meant to be private. Construction shall be made with create or from_file class methods. - Args: configuration_path (str): Path to the configuration this state relates to. workspace_id Optional(str): Id of the workspace the state relates to. #TODO mark this a not optional after the user base has upgraded to >= 0.39.18 @@ -86,7 +90,7 @@ def __init__( generation_timestamp (int): State generation timestamp. configuration_hash (str): Hash of the loaded configuration file. """ - self.configuration_path = configuration_path + self.configuration_path = str(configuration_path) self.resource_id = resource_id self.generation_timestamp = generation_timestamp self.configuration_hash = configuration_hash @@ -110,12 +114,10 @@ def _save(self) -> None: @classmethod def create(cls, configuration_path: str, configuration_hash: str, workspace_id: str, resource_id: str) -> "ResourceState": """Create a state for a resource configuration. - Args: configuration_path (str): Path to the YAML file defining the resource. configuration_hash (str): Hash of the loaded configuration fie. resource_id (str): UUID of the resource. - Returns: ResourceState: state representing the resource. """ @@ -131,10 +133,8 @@ def delete(self) -> None: @classmethod def from_file(cls, file_path: str) -> "ResourceState": """Deserialize a state from a YAML path. - Args: file_path (str): Path to the YAML state. - Returns: ResourceState: state deserialized from YAML. """ @@ -142,7 +142,8 @@ def from_file(cls, file_path: str) -> "ResourceState": raw_state = yaml.safe_load(f) return ResourceState( raw_state["configuration_path"], - raw_state.get("workspace_id"), # TODO: workspace id should not be nullable after the user base has upgraded to >= 0.39.18 + # TODO: workspace id should not be nullable after the user base has upgraded to >= 0.39.18 + raw_state.get("workspace_id"), raw_state["resource_id"], raw_state["generation_timestamp"], raw_state["configuration_hash"], @@ -161,11 +162,9 @@ def from_configuration_path_and_workspace(cls, configuration_path, workspace_id) @classmethod def migrate(self, state_to_migrate_path: str, workspace_id: str) -> "ResourceState": """Create a per workspace state from a legacy state file and remove the legacy state file. - Args: state_to_migrate_path (str): Path to the legacy state file to migrate. workspace_id (str): Workspace id for which the new state will be stored. - Returns: ResourceState: The new state after migration. """ @@ -178,7 +177,8 @@ def migrate(self, state_to_migrate_path: str, workspace_id: str) -> "ResourceSta class BaseResource(abc.ABC): - APPLY_PRIORITY = 0 # Priority of the resource during the apply. 0 means the resource is top priority. + # Priority of the resource during the apply. 0 means the resource is top priority. + APPLY_PRIORITY = 0 @property @abc.abstractmethod @@ -247,7 +247,6 @@ def __init__( self, api_client: airbyte_api_client.ApiClient, workspace_id: str, raw_configuration: dict, configuration_path: str ) -> None: """Create a BaseResource object. - Args: api_client (airbyte_api_client.ApiClient): the Airbyte API client. workspace_id (str): the workspace id. @@ -276,7 +275,6 @@ def __init__( def _deserialize_raw_configuration(self): """Deserialize a raw configuration into another object and perform extra validation if needed. The base implementation does nothing except extracting the configuration field and returning a copy of it. - Returns: dict: Deserialized configuration """ @@ -285,12 +283,10 @@ def _deserialize_raw_configuration(self): @staticmethod def _check_for_invalid_configuration_keys(dict_to_check: dict, invalid_keys: Set[str], error_message: str): """Utils function to check if a configuration dictionnary has legacy keys that were removed/renamed after an octavia update. - Args: dict_to_check (dict): The dictionnary for which keys should be checked invalid_keys (Set[str]): The set of invalid keys we want to check the existence error_message (str): The error message to display to the user - Raises: InvalidConfigurationError: Raised if an invalid key was found in the dict_to_check """ @@ -317,7 +313,6 @@ def was_created(self): def _get_remote_resource(self) -> Union[SourceRead, DestinationRead, ConnectionRead]: """Retrieve a resources on the remote Airbyte instance. - Returns: Union[SourceReadList, DestinationReadList, ConnectionReadList]: Search results """ @@ -326,7 +321,6 @@ def _get_remote_resource(self) -> Union[SourceRead, DestinationRead, ConnectionR @staticmethod def _get_state_from_file(configuration_file: str, workspace_id: str) -> Optional[ResourceState]: """Retrieve a state object from a local YAML file if it exists. - Returns: Optional[ResourceState]: the deserialized resource state if YAML file found. """ @@ -351,10 +345,8 @@ def _get_state_from_file(configuration_file: str, workspace_id: str) -> Optional def get_diff_with_remote_resource(self) -> str: """Compute the diff between current resource and the remote resource. - Raises: NonExistingResourceError: Raised if the remote resource does not exist. - Returns: str: The prettyfied diff. """ @@ -373,7 +365,6 @@ def _create_or_update( ], ) -> Union[SourceRead, DestinationRead]: """Wrapper to trigger create or update of remote resource. - Args: operation_fn (Callable): The API function to run. payload (Union[SourceCreate, SourceUpdate, DestinationCreate, DestinationUpdate]): The payload to send to create or update the resource. @@ -381,7 +372,6 @@ def _create_or_update( Raises: InvalidConfigurationError: Raised if the create or update payload is invalid. ApiException: Raised in case of other API errors. - Returns: Union[SourceRead, DestinationRead, ConnectionRead]: The created or updated resource. """ @@ -399,9 +389,23 @@ def _create_or_update( else: raise api_error + def manage( + self, resource_id: str + ) -> Union[Tuple[SourceRead, ResourceState], Tuple[DestinationRead, ResourceState], Tuple[ConnectionRead, ResourceState]]: + """Declare a remote resource as locally managed by creating a local state + + Args: + resource_id (str): Remote resource ID. + + Returns: + Union[Tuple[SourceRead, ResourceState], Tuple[DestinationRead, ResourceState], Tuple[ConnectionRead, ResourceState]]: The remote resource model instance and its local state. + """ + self.state = ResourceState.create(self.configuration_path, self.configuration_hash, self.workspace_id, resource_id) + + return self.remote_resource, self.state + def create(self) -> Union[SourceRead, DestinationRead, ConnectionRead]: """Public function to create the resource on the remote Airbyte instance. - Returns: Union[SourceRead, DestinationRead, ConnectionRead]: The created resource. """ @@ -409,7 +413,6 @@ def create(self) -> Union[SourceRead, DestinationRead, ConnectionRead]: def update(self) -> Union[SourceRead, DestinationRead, ConnectionRead]: """Public function to update the resource on the remote Airbyte instance. - Returns: Union[SourceRead, DestinationRead, ConnectionRead]: The updated resource. """ @@ -418,7 +421,6 @@ def update(self) -> Union[SourceRead, DestinationRead, ConnectionRead]: @property def resource_id(self) -> Optional[str]: """Exposes the resource UUID of the remote resource - Returns: str: Remote resource's UUID """ @@ -482,10 +484,8 @@ def update_payload(self): @property def source_discover_schema_request_body(self) -> SourceDiscoverSchemaRequestBody: """Creates SourceDiscoverSchemaRequestBody from resource id. - Raises: NonExistingResourceError: raised if the resource id is None. - Returns: SourceDiscoverSchemaRequestBody: The SourceDiscoverSchemaRequestBody model instance. """ @@ -496,7 +496,6 @@ def source_discover_schema_request_body(self) -> SourceDiscoverSchemaRequestBody @property def catalog(self) -> AirbyteCatalog: """Retrieves the source's Airbyte catalog. - Returns: AirbyteCatalog: The catalog issued by schema discovery. """ @@ -521,7 +520,6 @@ class Destination(SourceAndDestination): @property def create_payload(self) -> DestinationCreate: """Defines the payload to create the remote resource. - Returns: DestinationCreate: The DestinationCreate model instance """ @@ -539,7 +537,6 @@ def get_payload(self) -> Optional[DestinationRead]: @property def update_payload(self) -> DestinationUpdate: """Defines the payload to update a remote resource. - Returns: DestinationUpdate: The DestinationUpdate model instance. """ @@ -557,7 +554,8 @@ def definition(self) -> DestinationDefinitionSpecificationRead: class Connection(BaseResource): - APPLY_PRIORITY = 1 # Set to 1 to create connection after source or destination. + # Set to 1 to create connection after source or destination. + APPLY_PRIORITY = 1 api = web_backend_api.WebBackendApi create_function_name = "web_backend_create_connection" update_function_name = "web_backend_update_connection" @@ -581,14 +579,14 @@ class Connection(BaseResource): "latest_sync_job_created_at", ] # We do not allow local editing of these keys - remote_operation_level_keys_to_filter_out = ["workspace_id", "operation_id"] # We do not allow local editing of these keys + # We do not allow local editing of these keys + remote_operation_level_keys_to_filter_out = ["workspace_id", "operation_id"] def _deserialize_raw_configuration(self): """Deserialize a raw configuration into another dict and perform serialization if needed. In this implementation we cast raw types to Airbyte API client models types for validation. Args: raw_configuration (dict): The raw configuration - Returns: dict: Deserialized connection configuration """ @@ -599,17 +597,16 @@ def _deserialize_raw_configuration(self): configuration["namespace_definition"] = NamespaceDefinitionType(configuration["namespace_definition"]) if "schedule" in configuration: configuration["schedule"] = ConnectionSchedule(**configuration["schedule"]) - configuration["resource_requirements"] = ResourceRequirements(**configuration["resource_requirements"]) + if "resource_requirements" in configuration: + configuration["resource_requirements"] = ResourceRequirements(**configuration["resource_requirements"]) configuration["status"] = ConnectionStatus(configuration["status"]) return configuration @property def source_id(self): """Retrieve the source id from the source state file of the current workspace. - Raises: MissingStateError: Raised if the state file of the current workspace is not found. - Returns: str: source id """ @@ -626,10 +623,8 @@ def source_id(self): @property def destination_id(self): """Retrieve the destination id from the destination state file of the current workspace. - Raises: MissingStateError: Raised if the state file of the current workspace is not found. - Returns: str: destination id """ @@ -646,7 +641,6 @@ def destination_id(self): @property def create_payload(self) -> WebBackendConnectionCreate: """Defines the payload to create the remote connection. - Returns: WebBackendConnectionCreate: The WebBackendConnectionCreate model instance """ @@ -671,7 +665,6 @@ def get_payload(self) -> Optional[WebBackendConnectionRequestBody]: @property def update_payload(self) -> WebBackendConnectionUpdate: """Defines the payload to update a remote connection. - Returns: WebBackendConnectionUpdate: The DestinationUpdate model instance. """ @@ -690,10 +683,8 @@ def update(self) -> dict: @staticmethod def _create_configured_catalog(sync_catalog: dict) -> AirbyteCatalog: """Deserialize a sync_catalog represented as dict to an AirbyteCatalog. - Args: sync_catalog (dict): The sync catalog represented as a dict. - Returns: AirbyteCatalog: The configured catalog. """ @@ -714,14 +705,11 @@ def _deserialize_operations( self, operations: List[dict], outputModelClass: Union[Type[OperationCreate], Type[WebBackendOperationCreateOrUpdate]] ) -> List[Union[OperationCreate, WebBackendOperationCreateOrUpdate]]: """Deserialize operations to OperationCreate (to create connection) or WebBackendOperationCreateOrUpdate (to update connection) models. - Args: operations (List[dict]): List of operations to deserialize outputModelClass (Union[Type[OperationCreate], Type[WebBackendOperationCreateOrUpdate]]): The model to which the operation dict will be deserialized - Raises: ValueError: Raised if the operator type declared in the configuration is not supported - Returns: List[Union[OperationCreate, WebBackendOperationCreateOrUpdate]]: Deserialized operations """ @@ -754,7 +742,6 @@ def _deserialize_operations( def _check_for_legacy_connection_configuration_keys(self, configuration_to_check): """We changed connection configuration keys from camelCase to snake_case in 0.37.0. This function check if the connection configuration has some camelCase keys and display a meaningful error message. - Args: configuration_to_check (dict): Configuration to validate """ @@ -797,15 +784,12 @@ def _get_remote_comparable_configuration(self) -> dict: def factory(api_client: airbyte_api_client.ApiClient, workspace_id: str, configuration_path: str) -> Union[Source, Destination, Connection]: """Create resource object according to the definition type field in their YAML configuration. - Args: api_client (airbyte_api_client.ApiClient): The Airbyte API client. workspace_id (str): The current workspace id. configuration_path (str): Path to the YAML file with the configuration. - Raises: NotImplementedError: Raised if the definition type found in the YAML is not a supported resource. - Returns: Union[Source, Destination, Connection]: The resource object created from the YAML config. """ diff --git a/octavia-cli/octavia_cli/entrypoint.py b/octavia-cli/octavia_cli/entrypoint.py index e42846cb243b..2aa28bb0f584 100644 --- a/octavia-cli/octavia_cli/entrypoint.py +++ b/octavia-cli/octavia_cli/entrypoint.py @@ -10,6 +10,7 @@ from airbyte_api_client.api import workspace_api from airbyte_api_client.model.workspace_id_request_body import WorkspaceIdRequestBody +from ._import import commands as import_commands from .api_http_headers import ApiHttpHeader, merge_api_headers, set_api_headers_on_api_client from .apply import commands as apply_commands from .check_context import check_api_health, check_is_initialized, check_workspace_exists @@ -22,6 +23,7 @@ AVAILABLE_COMMANDS: List[click.Command] = [ list_commands._list, get_commands.get, + import_commands._import, init_commands.init, generate_commands.generate, apply_commands.apply, @@ -154,11 +156,6 @@ def add_commands_to_octavia(): octavia.add_command(command) -@octavia.command(name="import", help="[NOT IMPLEMENTED] Import an existing resources from the Airbyte instance.") -def _import() -> None: - raise click.ClickException("The import command is not yet implemented.") - - @octavia.command(help="[NOT IMPLEMENTED] Delete resources") def delete() -> None: raise click.ClickException("The delete command is not yet implemented.") diff --git a/octavia-cli/octavia_cli/generate/renderers.py b/octavia-cli/octavia_cli/generate/renderers.py index f4e3ebc1c9fb..5a56264fdce4 100644 --- a/octavia-cli/octavia_cli/generate/renderers.py +++ b/octavia-cli/octavia_cli/generate/renderers.py @@ -4,12 +4,15 @@ import abc import os +from pathlib import Path from typing import Any, Callable, List +import click import yaml from airbyte_api_client.model.airbyte_catalog import AirbyteCatalog from jinja2 import Environment, PackageLoader, Template, select_autoescape from octavia_cli.apply import resources +from slugify import slugify from .definitions import BaseDefinition, ConnectionDefinition from .yaml_dumpers import CatalogDumper @@ -20,7 +23,6 @@ class FieldToRender: def __init__(self, name: str, required: bool, field_metadata: dict) -> None: """Initialize a FieldToRender instance - Args: name (str): name of the field required (bool): whether it's a required field or not @@ -45,10 +47,8 @@ def __init__(self, name: str, required: bool, field_metadata: dict) -> None: def __getattr__(self, name: str) -> Any: """Map field_metadata keys to attributes of Field. - Args: name (str): attribute name - Returns: [Any]: attribute value """ @@ -78,7 +78,6 @@ def _get_one_of_values(self) -> List[List["FieldToRender"]]: def _get_array_items(self) -> List["FieldToRender"]: """If the field is an array of objects, retrieve fields of these objects. - Returns: [list]: List of fields """ @@ -147,44 +146,73 @@ def TEMPLATE( def __init__(self, resource_name: str) -> None: self.resource_name = resource_name - def _get_output_path(self, project_path: str, definition_type: str) -> str: + @classmethod + def get_output_path(cls, project_path: str, definition_type: str, resource_name: str) -> Path: """Get rendered file output path - Args: project_path (str): Current project path. definition_type (str): Current definition_type. - + resource_name (str): Current resource_name. Returns: - str: Full path to the output path. + Path: Full path to the output path. """ - directory = os.path.join(project_path, f"{definition_type}s", self.resource_name) + directory = os.path.join(project_path, f"{definition_type}s", slugify(resource_name, separator="_")) if not os.path.exists(directory): os.makedirs(directory) - return os.path.join(directory, "configuration.yaml") + return Path(os.path.join(directory, "configuration.yaml")) + + @staticmethod + def _confirm_overwrite(output_path): + """User input to determine if the configuration paqth should be overwritten. + Args: + output_path (str): Path of the configuration file to overwrite + Returns: + bool: Boolean representing if the configuration file is to be overwritten + """ + overwrite = True + if output_path.is_file(): + overwrite = click.confirm( + f"The configuration octavia-cli is about to create already exists, do you want to replace it? ({output_path})" + ) + return overwrite @abc.abstractmethod def _render(self): # pragma: no cover """Runs the template rendering. - Raises: NotImplementedError: Must be implemented on subclasses. """ raise NotImplementedError - def write_yaml(self, project_path: str) -> str: + def write_yaml(self, project_path: Path) -> str: """Write rendered specification to a YAML file in local project path. - Args: project_path (str): Path to directory hosting the octavia project. - Returns: str: Path to the rendered specification. """ - output_path = self._get_output_path(project_path, self.definition.type) - rendered = self._render() + output_path = self.get_output_path(project_path, self.definition.type, self.resource_name) + if self._confirm_overwrite(output_path): + with open(output_path, "w") as f: + rendered_yaml = self._render() + f.write(rendered_yaml) + return output_path - with open(output_path, "w") as f: - f.write(rendered) + def import_configuration(self, project_path: str, configuration: dict) -> Path: + """Import the resource configuration. Save the yaml file to disk and return its path. + Args: + project_path (str): Current project path. + configuration (dict): The configuration of the resource. + Returns: + Path: Path to the resource configuration. + """ + rendered = self._render() + data = yaml.safe_load(rendered) + data["configuration"] = configuration + output_path = self.get_output_path(project_path, self.definition.type, self.resource_name) + if self._confirm_overwrite(output_path): + with open(output_path, "wb") as f: + yaml.safe_dump(data, f, default_flow_style=False, sort_keys=False, allow_unicode=True, encoding="utf-8") return output_path @@ -193,7 +221,6 @@ class ConnectorSpecificationRenderer(BaseRenderer): def __init__(self, resource_name: str, definition: BaseDefinition) -> None: """Connector specification renderer constructor. - Args: resource_name (str): Name of the source or destination. definition (BaseDefinition): The definition related to a source or a destination. @@ -203,7 +230,6 @@ def __init__(self, resource_name: str, definition: BaseDefinition) -> None: def _parse_connection_specification(self, schema: dict) -> List[List["FieldToRender"]]: """Create a renderable structure from the specification schema - Returns: List[List["FieldToRender"]]: List of list of fields to render. """ @@ -228,10 +254,23 @@ class ConnectionRenderer(BaseRenderer): TEMPLATE = JINJA_ENV.get_template("connection.yaml.j2") definition = ConnectionDefinition + KEYS_TO_REMOVE_FROM_REMOTE_CONFIGURATION = [ + "connection_id", + "name", + "source_id", + "destination_id", + "latest_sync_job_created_at", + "latest_sync_job_status", + "source", + "destination", + "is_syncing", + "operation_ids", + "catalog_id", + "catalog_diff", + ] def __init__(self, connection_name: str, source: resources.Source, destination: resources.Destination) -> None: """Connection renderer constructor. - Args: connection_name (str): Name of the connection to render. source (resources.Source): Connection's source. @@ -244,10 +283,8 @@ def __init__(self, connection_name: str, source: resources.Source, destination: @staticmethod def catalog_to_yaml(catalog: AirbyteCatalog) -> str: """Convert the source catalog to a YAML string. - Args: catalog (AirbyteCatalog): Source's catalog. - Returns: str: Catalog rendered as yaml. """ @@ -265,3 +302,27 @@ def _render(self) -> str: "supports_dbt": self.destination.definition.supports_dbt, } ) + + def import_configuration(self, project_path: Path, configuration: dict) -> Path: + """Import the connection configuration. Save the yaml file to disk and return its path. + Args: + project_path (str): Current project path. + configuration (dict): The configuration of the connection. + Returns: + Path: Path to the connection configuration. + """ + rendered = self._render() + data = yaml.safe_load(rendered) + data["configuration"] = {k: v for k, v in configuration.items() if k not in self.KEYS_TO_REMOVE_FROM_REMOTE_CONFIGURATION} + if "operations" in data["configuration"] and len(data["configuration"]["operations"]) == 0: + data["configuration"].pop("operations") + [ + operation.pop(field_to_remove, "") + for field_to_remove in ["workspace_id", "operation_id"] + for operation in data["configuration"].get("operations", {}) + ] + output_path = self.get_output_path(project_path, self.definition.type, self.resource_name) + if self._confirm_overwrite(output_path): + with open(output_path, "wb") as f: + yaml.safe_dump(data, f, default_flow_style=False, sort_keys=False, allow_unicode=True, encoding="utf-8") + return output_path diff --git a/octavia-cli/octavia_cli/generate/templates/connection.yaml.j2 b/octavia-cli/octavia_cli/generate/templates/connection.yaml.j2 index 9a342da3be4d..78d1fe8ba937 100644 --- a/octavia-cli/octavia_cli/generate/templates/connection.yaml.j2 +++ b/octavia-cli/octavia_cli/generate/templates/connection.yaml.j2 @@ -1,6 +1,6 @@ # Configuration for connection {{ connection_name }} definition_type: connection -resource_name: {{ connection_name }} +resource_name: "{{ connection_name }}" source_configuration_path: {{ source_configuration_path }} destination_configuration_path: {{ destination_configuration_path }} diff --git a/octavia-cli/octavia_cli/generate/templates/source_or_destination.yaml.j2 b/octavia-cli/octavia_cli/generate/templates/source_or_destination.yaml.j2 index 9f5131e1789b..c8a33f708577 100644 --- a/octavia-cli/octavia_cli/generate/templates/source_or_destination.yaml.j2 +++ b/octavia-cli/octavia_cli/generate/templates/source_or_destination.yaml.j2 @@ -1,6 +1,6 @@ # Configuration for {{ definition.docker_repository }} # Documentation about this connector can be found at {{ definition.documentation_url }} -resource_name: {{ resource_name}} +resource_name: "{{ resource_name}}" definition_type: {{ definition.type}} definition_id: {{ definition.id }} definition_image: {{ definition.docker_repository }} diff --git a/octavia-cli/octavia_cli/get/commands.py b/octavia-cli/octavia_cli/get/commands.py index fea5c6d96377..db50b9176ad8 100644 --- a/octavia-cli/octavia_cli/get/commands.py +++ b/octavia-cli/octavia_cli/get/commands.py @@ -47,7 +47,7 @@ def get_resource_id_or_name(resource: str) -> Tuple[Optional[str], Optional[str] def get_json_representation( api_client: airbyte_api_client.ApiClient, workspace_id: str, - ResourceCls: Type[Union[Source, Destination, Connection]], + ResourceClass: Type[Union[Source, Destination, Connection]], resource_to_get: str, ) -> str: """Helper function to retrieve a resource json representation and avoid repeating the same logic for Source/Destination and connection. @@ -56,14 +56,14 @@ def get_json_representation( Args: api_client (airbyte_api_client.ApiClient): The Airbyte API client. workspace_id (str): Current workspace id. - ResourceCls (Type[Union[Source, Destination, Connection]]): Resource class to use + ResourceClass (Type[Union[Source, Destination, Connection]]): Resource class to use resource_to_get (str): resource name or id to get JSON representation for. Returns: str: The resource's JSON representation. """ resource_id, resource_name = get_resource_id_or_name(resource_to_get) - resource = ResourceCls(api_client, workspace_id, resource_id=resource_id, resource_name=resource_name) + resource = ResourceClass(api_client, workspace_id, resource_id=resource_id, resource_name=resource_name) return resource.to_json() diff --git a/octavia-cli/setup.py b/octavia-cli/setup.py index 431c222cb440..ddf8bcfcfe76 100644 --- a/octavia-cli/setup.py +++ b/octavia-cli/setup.py @@ -49,6 +49,7 @@ "deepdiff~=5.7.0", "pyyaml~=6.0", "analytics-python~=1.4.0", + "python-slugify~=6.1.2", ], python_requires=">=3.9.11", extras_require={ diff --git a/octavia-cli/unit_tests/test__import/__init__.py b/octavia-cli/unit_tests/test__import/__init__.py new file mode 100644 index 000000000000..1100c1c58cf5 --- /dev/null +++ b/octavia-cli/unit_tests/test__import/__init__.py @@ -0,0 +1,3 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# diff --git a/octavia-cli/unit_tests/test__import/test_commands.py b/octavia-cli/unit_tests/test__import/test_commands.py new file mode 100644 index 000000000000..28bea101b0ca --- /dev/null +++ b/octavia-cli/unit_tests/test__import/test_commands.py @@ -0,0 +1,198 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + +import pytest +from click.testing import CliRunner +from octavia_cli._import import commands + + +@pytest.fixture +def patch_click(mocker): + mocker.patch.object(commands, "click") + + +@pytest.fixture +def context_object(mock_api_client, mock_telemetry_client): + return { + "PROJECT_IS_INITIALIZED": True, + "API_CLIENT": mock_api_client, + "WORKSPACE_ID": "workspace_id", + "TELEMETRY_CLIENT": mock_telemetry_client, + } + + +def test_build_help_message(): + assert commands.build_help_message("source") == "Import an existing source to manage it with octavia-cli." + + +@pytest.mark.parametrize("ResourceClass", [commands.UnmanagedSource, commands.UnmanagedDestination]) +def test_import_source_or_destination(mocker, context_object, ResourceClass): + resource_type = ResourceClass.__name__.lower() + mocker.patch.object(commands.click, "style") + mocker.patch.object(commands.click, "echo") + mocker.patch.object(commands, "get_json_representation") + mocker.patch.object( + commands.json, + "loads", + mocker.Mock( + return_value={ + "name": "foo", + "connection_configuration": "bar", + f"{resource_type}_definition_id": f"{resource_type}_definition_id", + f"{resource_type}_id": f"my_{resource_type}_id", + } + ), + ) + mocker.patch.object(commands.definitions, "factory") + mocker.patch.object(commands.renderers, "ConnectorSpecificationRenderer") + expected_managed_resource, expected_state = (mocker.Mock(), mocker.Mock()) + mocker.patch.object( + commands.resources, + "factory", + mocker.Mock(return_value=mocker.Mock(manage=mocker.Mock(return_value=(expected_managed_resource, expected_state)))), + ) + commands.import_source_or_destination(context_object["API_CLIENT"], context_object["WORKSPACE_ID"], ResourceClass, "resource_to_get") + commands.get_json_representation.assert_called_with( + context_object["API_CLIENT"], context_object["WORKSPACE_ID"], ResourceClass, "resource_to_get" + ) + commands.json.loads.assert_called_with(commands.get_json_representation.return_value) + remote_configuration = commands.json.loads.return_value + commands.definitions.factory.assert_called_with( + resource_type, context_object["API_CLIENT"], context_object["WORKSPACE_ID"], f"{resource_type}_definition_id" + ) + commands.renderers.ConnectorSpecificationRenderer.assert_called_with("foo", commands.definitions.factory.return_value) + renderer = commands.renderers.ConnectorSpecificationRenderer.return_value + renderer.import_configuration.assert_called_with(project_path=".", configuration=remote_configuration["connection_configuration"]) + commands.resources.factory.assert_called_with( + context_object["API_CLIENT"], context_object["WORKSPACE_ID"], renderer.import_configuration.return_value + ) + commands.resources.factory.return_value.manage.assert_called_with(remote_configuration[f"{resource_type}_id"]) + commands.click.style.assert_has_calls( + [ + mocker.call( + f"✅ - Imported {resource_type} {expected_managed_resource.name} in {renderer.import_configuration.return_value}. State stored in {expected_state.path}", + fg="green", + ), + mocker.call(f"⚠️ - Please update any secrets stored in {renderer.import_configuration.return_value}", fg="yellow"), + ] + ) + assert commands.click.echo.call_count == 2 + + +@pytest.mark.parametrize( + "source_exists, source_was_created, destination_exists, destination_was_created", + [ + (True, True, True, True), + (False, False, False, False), + (True, False, True, False), + (True, True, False, False), + (True, True, True, False), + ], +) +def test_import_connection(mocker, context_object, source_exists, source_was_created, destination_exists, destination_was_created): + mocker.patch.object(commands.click, "style") + mocker.patch.object(commands.click, "echo") + mocker.patch.object(commands, "get_json_representation") + mocker.patch.object( + commands.json, + "loads", + mocker.Mock( + return_value={ + "source": {"name": "my_source"}, + "destination": {"name": "my_destination"}, + "name": "my_connection", + "connection_id": "my_connection_id", + } + ), + ) + remote_configuration = commands.json.loads.return_value + mocker.patch.object(commands.definitions, "factory") + mock_source_configuration_path = mocker.Mock(is_file=mocker.Mock(return_value=source_exists)) + mock_destination_configuration_path = mocker.Mock(is_file=mocker.Mock(return_value=destination_exists)) + + mocker.patch.object( + commands.renderers.ConnectorSpecificationRenderer, + "get_output_path", + mocker.Mock(side_effect=[mock_source_configuration_path, mock_destination_configuration_path]), + ) + mocker.patch.object(commands.renderers, "ConnectionRenderer") + mock_managed_source = mocker.Mock(was_created=source_was_created) + mock_managed_destination = mocker.Mock(was_created=destination_was_created) + mock_remote_connection, mock_connection_state = mocker.Mock(), mocker.Mock() + mock_managed_connection = mocker.Mock(manage=mocker.Mock(return_value=(mock_remote_connection, mock_connection_state))) + + mocker.patch.object( + commands.resources, "factory", mocker.Mock(side_effect=[mock_managed_source, mock_managed_destination, mock_managed_connection]) + ) + if all([source_exists, destination_exists, source_was_created, destination_was_created]): + + commands.import_connection(context_object["API_CLIENT"], context_object["WORKSPACE_ID"], "resource_to_get") + commands.get_json_representation.assert_called_with( + context_object["API_CLIENT"], context_object["WORKSPACE_ID"], commands.UnmanagedConnection, "resource_to_get" + ) + commands.renderers.ConnectorSpecificationRenderer.get_output_path.assert_has_calls( + [ + mocker.call(project_path=".", definition_type="source", resource_name="my_source"), + mocker.call(project_path=".", definition_type="destination", resource_name="my_destination"), + ] + ) + commands.resources.factory.assert_has_calls( + [ + mocker.call(context_object["API_CLIENT"], context_object["WORKSPACE_ID"], mock_source_configuration_path), + mocker.call(context_object["API_CLIENT"], context_object["WORKSPACE_ID"], mock_destination_configuration_path), + mocker.call( + context_object["API_CLIENT"], + context_object["WORKSPACE_ID"], + commands.renderers.ConnectionRenderer.return_value.import_configuration.return_value, + ), + ] + ) + commands.renderers.ConnectionRenderer.assert_called_with( + remote_configuration["name"], mock_managed_source, mock_managed_destination + ) + commands.renderers.ConnectionRenderer.return_value.import_configuration.assert_called_with(".", remote_configuration) + new_configuration_path = commands.renderers.ConnectionRenderer.return_value.import_configuration.return_value + commands.click.style.assert_called_with( + f"✅ - Imported connection {mock_remote_connection.name} in {new_configuration_path}. State stored in {mock_connection_state.path}", + fg="green", + ) + commands.click.echo.assert_called_with(commands.click.style.return_value) + if not source_exists or not destination_exists: + with pytest.raises( + commands.MissingResourceDependencyError, + match="is not managed by octavia-cli, please import and apply it before importing your connection.", + ): + commands.import_connection(context_object["API_CLIENT"], context_object["WORKSPACE_ID"], "resource_to_get") + if source_exists and destination_exists and (not source_was_created or not destination_was_created): + with pytest.raises(commands.resources.NonExistingResourceError, match="Please run octavia apply before creating this connection."): + commands.import_connection(context_object["API_CLIENT"], context_object["WORKSPACE_ID"], "resource_to_get") + + +@pytest.mark.parametrize("command", [commands.source, commands.destination, commands.connection]) +def test_import_not_initialized(command): + runner = CliRunner() + result = runner.invoke(command, obj={"PROJECT_IS_INITIALIZED": False}) + assert result.exit_code == 1 + + +@pytest.mark.parametrize( + "command, ResourceClass, import_function", + [ + (commands.source, commands.UnmanagedSource, "import_source_or_destination"), + (commands.destination, commands.UnmanagedDestination, "import_source_or_destination"), + (commands.connection, None, "import_connection"), + ], +) +def test_import_commands(mocker, context_object, ResourceClass, command, import_function): + runner = CliRunner() + mock_import_function = mocker.Mock() + mocker.patch.object(commands, import_function, mock_import_function) + result = runner.invoke(command, ["resource_to_import"], obj=context_object) + if import_function == "import_source_or_destination": + mock_import_function.assert_called_with( + context_object["API_CLIENT"], context_object["WORKSPACE_ID"], ResourceClass, "resource_to_import" + ) + else: + mock_import_function.assert_called_with(context_object["API_CLIENT"], context_object["WORKSPACE_ID"], "resource_to_import") + assert result.exit_code == 0 diff --git a/octavia-cli/unit_tests/test_apply/test_resources.py b/octavia-cli/unit_tests/test_apply/test_resources.py index 3cd63d711941..3d4cfe6c41f2 100644 --- a/octavia-cli/unit_tests/test_apply/test_resources.py +++ b/octavia-cli/unit_tests/test_apply/test_resources.py @@ -282,6 +282,15 @@ def test_update(self, mocker, resource): assert resource.update() == resource._create_or_update.return_value resource._create_or_update.assert_called_with(resource._update_fn, resource.update_payload) + def test_manage(self, mocker, resource): + mocker.patch.object(resources, "ResourceState") + remote_resource, new_state = resource.manage("resource_id") + resources.ResourceState.create.assert_called_with( + resource.configuration_path, resource.configuration_hash, resource.workspace_id, "resource_id" + ) + assert new_state == resources.ResourceState.create.return_value + assert remote_resource == resource.remote_resource + @pytest.mark.parametrize( "configuration, invalid_keys, expect_error", [ diff --git a/octavia-cli/unit_tests/test_entrypoint.py b/octavia-cli/unit_tests/test_entrypoint.py index 0e4a9b7cea33..e6dca8877090 100644 --- a/octavia-cli/unit_tests/test_entrypoint.py +++ b/octavia-cli/unit_tests/test_entrypoint.py @@ -204,7 +204,7 @@ def test_commands_in_octavia_group(): @pytest.mark.parametrize( "command", - [entrypoint.delete, entrypoint._import], + [entrypoint.delete], ) def test_not_implemented_commands(command): runner = CliRunner() @@ -217,6 +217,7 @@ def test_available_commands(): assert entrypoint.AVAILABLE_COMMANDS == [ entrypoint.list_commands._list, entrypoint.get_commands.get, + entrypoint.import_commands._import, entrypoint.init_commands.init, entrypoint.generate_commands.generate, entrypoint.apply_commands.apply, diff --git a/octavia-cli/unit_tests/test_generate/test_renderers.py b/octavia-cli/unit_tests/test_generate/test_renderers.py index fe38f4eebde9..478c71ffd249 100644 --- a/octavia-cli/unit_tests/test_generate/test_renderers.py +++ b/octavia-cli/unit_tests/test_generate/test_renderers.py @@ -2,6 +2,7 @@ # Copyright (c) 2022 Airbyte, Inc., all rights reserved. # +from pathlib import Path from unittest.mock import mock_open, patch import pytest @@ -130,6 +131,7 @@ def test__get_example_comment(self, examples_value, expected_output): [ ({"const": "foo", "default": "bar"}, "foo"), ({"default": "bar"}, "bar"), + ({"airbyte_secret": True}, "${FIELD_NAME}"), ({}, None), ], ) @@ -175,21 +177,63 @@ def test_init(self, patch_base_class): def test_get_output_path(self, patch_base_class, mocker): mocker.patch.object(renderers, "os") + mocker.patch.object(renderers, "slugify") renderers.os.path.exists.return_value = False spec_renderer = renderers.BaseRenderer("my_resource_name") renderers.os.path.join.side_effect = [ "./my_definition_types/my_resource_name", "./my_definition_types/my_resource_name/configuration.yaml", ] - output_path = spec_renderer._get_output_path(".", "my_definition_type") + output_path = spec_renderer.get_output_path(".", "my_definition_type", "my_resource_name") renderers.os.makedirs.assert_called_once() + renderers.slugify.assert_called_with("my_resource_name", separator="_") renderers.os.path.join.assert_has_calls( [ - mocker.call(".", "my_definition_types", "my_resource_name"), + mocker.call(".", "my_definition_types", renderers.slugify.return_value), mocker.call("./my_definition_types/my_resource_name", "configuration.yaml"), ] ) - assert output_path == "./my_definition_types/my_resource_name/configuration.yaml" + assert output_path == Path("./my_definition_types/my_resource_name/configuration.yaml") + + @pytest.mark.parametrize("file_exists, confirmed_overwrite", [(True, True), (False, None), (True, False)]) + def test__confirm_overwrite(self, mocker, file_exists, confirmed_overwrite): + mock_output_path = mocker.Mock(is_file=mocker.Mock(return_value=file_exists)) + mocker.patch.object(renderers.click, "confirm", mocker.Mock(return_value=confirmed_overwrite)) + overwrite = renderers.BaseRenderer._confirm_overwrite(mock_output_path) + if file_exists: + assert overwrite == confirmed_overwrite + else: + assert overwrite is True + + @pytest.mark.parametrize("confirmed_overwrite", [True, False]) + def test_import_configuration(self, mocker, patch_base_class, confirmed_overwrite): + configuration = {"foo": "bar"} + mocker.patch.object(renderers.BaseRenderer, "_render") + mocker.patch.object(renderers.BaseRenderer, "get_output_path") + mocker.patch.object(renderers.yaml, "safe_load", mocker.Mock(return_value={})) + mocker.patch.object(renderers.yaml, "safe_dump") + mocker.patch.object(renderers.BaseRenderer, "_confirm_overwrite", mocker.Mock(return_value=confirmed_overwrite)) + spec_renderer = renderers.BaseRenderer("my_resource_name") + spec_renderer.definition = mocker.Mock(type="my_definition") + expected_output_path = renderers.BaseRenderer.get_output_path.return_value + with patch("builtins.open", mock_open()) as mock_file: + output_path = spec_renderer.import_configuration(project_path=".", configuration=configuration) + spec_renderer._render.assert_called_once() + renderers.yaml.safe_load.assert_called_with(spec_renderer._render.return_value) + assert renderers.yaml.safe_load.return_value["configuration"] == configuration + spec_renderer.get_output_path.assert_called_with(".", spec_renderer.definition.type, spec_renderer.resource_name) + spec_renderer._confirm_overwrite.assert_called_with(expected_output_path) + if confirmed_overwrite: + mock_file.assert_called_with(expected_output_path, "wb") + renderers.yaml.safe_dump.assert_called_with( + renderers.yaml.safe_load.return_value, + mock_file.return_value, + default_flow_style=False, + sort_keys=False, + allow_unicode=True, + encoding="utf-8", + ) + assert output_path == renderers.BaseRenderer.get_output_path.return_value class TestConnectorSpecificationRenderer: @@ -221,26 +265,31 @@ def test__parse_connection_specification_one_of(self, mocker): assert len(parsed_schema) == len(schema["oneOf"]) renderers.parse_fields.assert_called_with(["free"], {"free": "beer"}) - def test_write_yaml(self, mocker): + @pytest.mark.parametrize("overwrite", [True, False]) + def test_write_yaml(self, mocker, overwrite): - mocker.patch.object(renderers.ConnectorSpecificationRenderer, "_get_output_path") + mocker.patch.object(renderers.ConnectorSpecificationRenderer, "get_output_path") mocker.patch.object(renderers.ConnectorSpecificationRenderer, "_parse_connection_specification") mocker.patch.object( renderers.ConnectorSpecificationRenderer, "TEMPLATE", mocker.Mock(render=mocker.Mock(return_value="rendered_string")) ) + mocker.patch.object(renderers.ConnectorSpecificationRenderer, "_confirm_overwrite", mocker.Mock(return_value=overwrite)) spec_renderer = renderers.ConnectorSpecificationRenderer("my_resource_name", mocker.Mock(type="source")) - with patch("builtins.open", mock_open()) as mock_file: + if overwrite: + with patch("builtins.open", mock_open()) as mock_file: + output_path = spec_renderer.write_yaml(".") + spec_renderer.TEMPLATE.render.assert_called_with( + { + "resource_name": "my_resource_name", + "definition": spec_renderer.definition, + "configuration_fields": spec_renderer._parse_connection_specification.return_value, + } + ) + mock_file.assert_called_with(output_path, "w") + else: output_path = spec_renderer.write_yaml(".") - assert output_path == spec_renderer._get_output_path.return_value - spec_renderer.TEMPLATE.render.assert_called_with( - { - "resource_name": "my_resource_name", - "definition": spec_renderer.definition, - "configuration_fields": spec_renderer._parse_connection_specification.return_value, - } - ) - mock_file.assert_called_with(output_path, "w") + assert output_path == spec_renderer.get_output_path.return_value def test__render(self, mocker): mocker.patch.object(renderers.ConnectorSpecificationRenderer, "_parse_connection_specification") @@ -285,28 +334,34 @@ def test_catalog_to_yaml(self, mocker): yaml_catalog = renderers.ConnectionRenderer.catalog_to_yaml(catalog) assert yaml_catalog == yaml.dump(catalog.to_dict(), Dumper=yaml_dumpers.CatalogDumper, default_flow_style=False) - def test_write_yaml(self, mocker, mock_source, mock_destination): - mocker.patch.object(renderers.ConnectionRenderer, "_get_output_path") + @pytest.mark.parametrize("overwrite", [True, False]) + def test_write_yaml(self, mocker, mock_source, mock_destination, overwrite): + mocker.patch.object(renderers.ConnectionRenderer, "get_output_path") mocker.patch.object(renderers.ConnectionRenderer, "catalog_to_yaml") mocker.patch.object(renderers.ConnectionRenderer, "TEMPLATE") + mocker.patch.object(renderers.ConnectionRenderer, "_confirm_overwrite", mocker.Mock(return_value=overwrite)) connection_renderer = renderers.ConnectionRenderer("my_resource_name", mock_source, mock_destination) - with patch("builtins.open", mock_open()) as mock_file: + if overwrite: + with patch("builtins.open", mock_open()) as mock_file: + output_path = connection_renderer.write_yaml(".") + connection_renderer.get_output_path.assert_called_with(".", renderers.ConnectionDefinition.type, "my_resource_name") + connection_renderer.catalog_to_yaml.assert_called_with(mock_source.catalog) + mock_file.assert_called_with(output_path, "w") + mock_file.return_value.write.assert_called_with(connection_renderer.TEMPLATE.render.return_value) + connection_renderer.TEMPLATE.render.assert_called_with( + { + "connection_name": connection_renderer.resource_name, + "source_configuration_path": mock_source.configuration_path, + "destination_configuration_path": mock_destination.configuration_path, + "catalog": connection_renderer.catalog_to_yaml.return_value, + "supports_normalization": connection_renderer.destination.definition.supports_normalization, + "supports_dbt": connection_renderer.destination.definition.supports_dbt, + } + ) + else: output_path = connection_renderer.write_yaml(".") - connection_renderer._get_output_path.assert_called_with(".", renderers.ConnectionDefinition.type) - connection_renderer.catalog_to_yaml.assert_called_with(mock_source.catalog) - mock_file.assert_called_with(output_path, "w") - mock_file.return_value.write.assert_called_with(connection_renderer.TEMPLATE.render.return_value) - connection_renderer.TEMPLATE.render.assert_called_with( - { - "connection_name": connection_renderer.resource_name, - "source_configuration_path": mock_source.configuration_path, - "destination_configuration_path": mock_destination.configuration_path, - "catalog": connection_renderer.catalog_to_yaml.return_value, - "supports_normalization": connection_renderer.destination.definition.supports_normalization, - "supports_dbt": connection_renderer.destination.definition.supports_dbt, - } - ) + assert output_path == connection_renderer.get_output_path.return_value def test__render(self, mocker): mocker.patch.object(renderers.ConnectionRenderer, "catalog_to_yaml") @@ -325,3 +380,36 @@ def test__render(self, mocker): } ) assert rendered == connection_renderer.TEMPLATE.render.return_value + + @pytest.mark.parametrize("confirmed_overwrite, operations", [(True, []), (False, []), (True, [{}]), (False, [{}])]) + def test_import_configuration(self, mocker, confirmed_overwrite, operations): + configuration = {"foo": "bar", "bar": "foo", "operations": operations} + mocker.patch.object(renderers.ConnectionRenderer, "KEYS_TO_REMOVE_FROM_REMOTE_CONFIGURATION", ["bar"]) + mocker.patch.object(renderers.ConnectionRenderer, "_render") + mocker.patch.object(renderers.ConnectionRenderer, "get_output_path") + mocker.patch.object(renderers.yaml, "safe_load", mocker.Mock(return_value={})) + mocker.patch.object(renderers.yaml, "safe_dump") + mocker.patch.object(renderers.ConnectionRenderer, "_confirm_overwrite", mocker.Mock(return_value=confirmed_overwrite)) + spec_renderer = renderers.ConnectionRenderer("my_resource_name", mocker.Mock(), mocker.Mock()) + expected_output_path = renderers.ConnectionRenderer.get_output_path.return_value + with patch("builtins.open", mock_open()) as mock_file: + output_path = spec_renderer.import_configuration(project_path=".", configuration=configuration) + spec_renderer._render.assert_called_once() + renderers.yaml.safe_load.assert_called_with(spec_renderer._render.return_value) + if operations: + assert renderers.yaml.safe_load.return_value["configuration"] == {"foo": "bar", "operations": operations} + else: + assert renderers.yaml.safe_load.return_value["configuration"] == {"foo": "bar"} + spec_renderer.get_output_path.assert_called_with(".", spec_renderer.definition.type, spec_renderer.resource_name) + spec_renderer._confirm_overwrite.assert_called_with(expected_output_path) + if confirmed_overwrite: + mock_file.assert_called_with(expected_output_path, "wb") + renderers.yaml.safe_dump.assert_called_with( + renderers.yaml.safe_load.return_value, + mock_file.return_value, + default_flow_style=False, + sort_keys=False, + allow_unicode=True, + encoding="utf-8", + ) + assert output_path == renderers.ConnectionRenderer.get_output_path.return_value