omnata-plugin-devkit 0.13.2a182__tar.gz → 0.13.2a184__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/PKG-INFO +1 -1
  2. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/pyproject.toml +1 -1
  3. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/jinja_templates/RETRIEVE_SECRETS_UDF.sql.jinja +2 -0
  4. omnata_plugin_devkit-0.13.2a184/src/omnata_plugin_devkit/jinja_templates/SET_CONNECTION_OBJECTS.sql.jinja +367 -0
  5. omnata_plugin_devkit-0.13.2a182/src/omnata_plugin_devkit/jinja_templates/SET_CONNECTION_OBJECTS.sql.jinja +0 -375
  6. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/LICENSE +0 -0
  7. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/README.md +0 -0
  8. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/__init__.py +0 -0
  9. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/airbyte_wrapper.py +0 -0
  10. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/cli/__init__.py +0 -0
  11. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/development.ipynb +0 -0
  12. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/development_session.py +0 -0
  13. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/initialiser.py +0 -0
  14. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/jinja_templates/API_LIMITS.sql.jinja +0 -0
  15. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/jinja_templates/ASSIGN_OUTBOUND_TARGET_TYPE.sql.jinja +0 -0
  16. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/jinja_templates/CHECK_CONNECTION_PROGRESS.sql.jinja +0 -0
  17. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/jinja_templates/CONFIGURATION_FORM.sql.jinja +0 -0
  18. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/jinja_templates/CONFIGURE_APIS.sql.jinja +0 -0
  19. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/jinja_templates/CONNECTION_FORM.sql.jinja +0 -0
  20. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/jinja_templates/CONNECTION_TEST.sql.jinja +0 -0
  21. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/jinja_templates/CONSTRUCT_FORM_OPTION.sql.jinja +0 -0
  22. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/jinja_templates/CREATE_BILLING_EVENTS.sql.jinja +0 -0
  23. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/jinja_templates/CREATE_GENERIC_SECRET_OBJECT.sql.jinja +0 -0
  24. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/jinja_templates/CREATE_GENERIC_SECRET_OBJECT_FROM_EXISTING.sql.jinja +0 -0
  25. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/jinja_templates/CREATE_NETWORK_RULE_OBJECT.sql.jinja +0 -0
  26. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/jinja_templates/CREATE_NETWORK_RULE_OBJECT_FROM_EXISTING.sql.jinja +0 -0
  27. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/jinja_templates/CREATE_OAUTH_SECRET_OBJECT.sql.jinja +0 -0
  28. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/jinja_templates/DROP_NETWORK_RULES.sql.jinja +0 -0
  29. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/jinja_templates/DROP_SECRETS.sql.jinja +0 -0
  30. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/jinja_templates/FETCH_CONNECTIONS.sql.jinja +0 -0
  31. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/jinja_templates/FETCH_SYNCS.sql.jinja +0 -0
  32. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/jinja_templates/FETCH_SYNC_BRANCHES.sql.jinja +0 -0
  33. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/jinja_templates/GET_MISSING_APP_PRIVILEGES.sql.jinja +0 -0
  34. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/jinja_templates/INBOUND_LIST_STREAMS.sql.jinja +0 -0
  35. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/jinja_templates/LIST_APP_SPECIFICATIONS.sql.jinja +0 -0
  36. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/jinja_templates/LIST_STAGES.sql.jinja +0 -0
  37. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/jinja_templates/NETWORK_ADDRESSES.sql.jinja +0 -0
  38. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/jinja_templates/NGROK_POST_TUNNEL_FIELDS.sql.jinja +0 -0
  39. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/jinja_templates/OUTBOUND_RECORD_VALIDATOR.sql.jinja +0 -0
  40. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/jinja_templates/PENDING_API_CONFIGURATION.sql.jinja +0 -0
  41. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/jinja_templates/POST_INSTALL_ACTIONS.sql.jinja +0 -0
  42. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/jinja_templates/RENAME_CONNECTION_METHODS.sql.jinja +0 -0
  43. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/jinja_templates/RETRIEVE_NETWORK_RULE_OBJECT.sql.jinja +0 -0
  44. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/jinja_templates/RETRIEVE_SECRETS.sql.jinja +0 -0
  45. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/jinja_templates/SET_EAI_ENABLED.sql.jinja +0 -0
  46. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/jinja_templates/SET_EAI_SPECIFICATION.sql.jinja +0 -0
  47. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/jinja_templates/SET_SI_SPECIFICATION.sql.jinja +0 -0
  48. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/jinja_templates/SYNC.sql.jinja +0 -0
  49. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/jinja_templates/TEST_CALLBACK.sql.jinja +0 -0
  50. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/jinja_templates/TEST_OAUTH_TOKEN_EXISTS.sql.jinja +0 -0
  51. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/jinja_templates/TUNNEL_TEST.sql.jinja +0 -0
  52. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/jinja_templates/UPDATE_API_CONFIGURATION.sql.jinja +0 -0
  53. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/jinja_templates/UPDATE_GENERIC_SECRET_OBJECT.sql.jinja +0 -0
  54. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/jinja_templates/UPDATE_GENERIC_SECRET_OBJECT_OLD.sql.jinja +0 -0
  55. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/jinja_templates/UPDATE_NETWORK_RULE_OBJECT.sql.jinja +0 -0
  56. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/jinja_templates/manifest.yml.jinja +0 -0
  57. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/native_app_packaging.py +0 -0
  58. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/plugin_registration.py +0 -0
  59. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/plugin_template/icon.svg +0 -0
  60. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/plugin_template/plugin.py +0 -0
  61. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/plugin_template/plugin_development.ipynb +0 -0
  62. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/plugin_template/requirements.txt +0 -0
  63. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/plugin_uploader.py +0 -0
  64. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/snowcli/__init__.py +0 -0
  65. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/snowcli/cli/__init__.py +0 -0
  66. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/snowcli/cli/api/__init__.py +0 -0
  67. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/snowcli/cli/api/config.py +0 -0
  68. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/snowcli/cli/api/constants.py +0 -0
  69. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/snowcli/cli/api/exceptions.py +0 -0
  70. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/snowcli/cli/api/secure_path.py +0 -0
  71. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/snowcli/cli/api/secure_utils.py +0 -0
  72. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/snowcli/cli/api/utils/__init__.py +0 -0
  73. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/snowcli/cli/api/utils/types.py +0 -0
  74. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/snowcli/cli/app/__init__.py +0 -0
  75. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/snowcli/cli/app/snow_connector.py +0 -0
  76. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/snowcli/cli/plugins/__init__.py +0 -0
  77. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/snowcli/cli/plugins/snowpark/__init__.py +0 -0
  78. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/snowcli/cli/plugins/snowpark/models.py +0 -0
  79. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/snowcli/cli/plugins/snowpark/package/__init__.py +0 -0
  80. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/snowcli/cli/plugins/snowpark/package_utils.py +0 -0
  81. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/snowcli/cli/plugins/snowpark/snowpark_shared.py +0 -0
  82. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/snowcli/cli/plugins/snowpark/venv.py +0 -0
  83. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/snowcli/cli/plugins/snowpark/zipper.py +0 -0
  84. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/snowcli/cli/templates/environment.yml.jinja +0 -0
  85. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/streamlit/plugin_configuration.py +0 -0
  86. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/test_step_definitions.py +0 -0
  87. {omnata_plugin_devkit-0.13.2a182 → omnata_plugin_devkit-0.13.2a184}/src/omnata_plugin_devkit/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: omnata-plugin-devkit
3
- Version: 0.13.2a182
3
+ Version: 0.13.2a184
4
4
  Summary:
5
5
  License-File: LICENSE
6
6
  Author: James Weakley
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "omnata-plugin-devkit"
3
- version = "0.13.2a182"
3
+ version = "0.13.2a184"
4
4
  description = ""
5
5
  authors = ["James Weakley <james.weakley@omnata.com>"]
6
6
  readme = "README.md"
@@ -25,11 +25,13 @@ def run(oauth_secret_name,other_secrets_name):
25
25
  other_secrets_name
26
26
  )
27
27
  if len(secret_string_content) > 2:
28
+ logger.info(f"Raw secret string: {secret_string_content}")
28
29
  other_secrets = json.loads(secret_string_content)
29
30
  connection_secrets = {
30
31
  **connection_secrets,
31
32
  **other_secrets,
32
33
  }
34
+ logger.info(f"Parsed secret string: {connection_secrets}")
33
35
  except Exception as exception:
34
36
  logger.error(f"Error parsing secrets content for secret {other_secrets_name}: {str(exception)}")
35
37
  raise ValueError(f"Error parsing secrets content: {str(exception)}") from exception
@@ -0,0 +1,367 @@
1
+ create or replace procedure PLUGIN.SET_CONNECTION_OBJECTS(PARAMETERS OBJECT)
2
+ returns object
3
+ language python
4
+ RUNTIME_VERSION = '3.10'
5
+ PACKAGES = ({{packages}})
6
+ IMPORTS = ('/app.zip')
7
+ HANDLER = 'run'
8
+ COMMENT = $$
9
+ Creates or replaces every Snowflake object required to provision a connection: network
10
+ rules, OAuth security integration, OAuth secret, secret authorization configuration,
11
+ other_secrets generic secret, External Access Integration, EAI application specification,
12
+ and Security Integration application specification. OAuth-related objects are only created
13
+ when oauth_security_integration_name is provided.
14
+
15
+ Expected keys on PARAMETERS:
16
+ external_access_integration_name (required; account-level, no schema)
17
+ network_rule_name (required; unqualified — DATA schema is prepended)
18
+ network_addresses (required, array of strings)
19
+ network_rule_name_privatelink (optional; unqualified — DATA schema is prepended)
20
+ network_rule_addresses_privatelink (required if privatelink rule set)
21
+ oauth_security_integration_name (optional; account-level, no schema — enables the oauth branch)
22
+ oauth_parameters (required if oauth SI set; object with
23
+ oauth_grant, oauth_token_endpoint,
24
+ oauth_authorization_endpoint,
25
+ oauth_allowed_scopes, oauth_client_id,
26
+ oauth_client_secret)
27
+ oauth_secret_name (optional; unqualified — DATA schema is prepended; requires oauth SI)
28
+ oauth_secret_configuration_name (optional; application-scope, no schema; requires oauth_secret_name)
29
+ other_secrets_name (required; unqualified — DATA schema is prepended)
30
+ connection_secrets (optional object; merged into the generic secret
31
+ contents, overwriting any matching keys from
32
+ merge_with_secret_name)
33
+ merge_with_secret_name (optional; unqualified name of an existing generic
34
+ secret in DATA whose contents are read via
35
+ RETRIEVE_SECRETS_UDF and used as the baseline for
36
+ the new other_secrets generic secret.)
37
+ external_access_integration_spec_name (required; application-scope, no schema)
38
+ security_integration_spec_name (optional; application-scope, no schema; requires oauth SI)
39
+ connection_slug (required; human-readable connection identifier
40
+ included in spec labels/descriptions so the
41
+ consumer knows which connection they are
42
+ approving)
43
+ $$
44
+ execute as owner
45
+ as
46
+ $$
47
+ import json
48
+ from logging import getLogger
49
+ from typing import Any, Dict, List, Literal, Optional
50
+ from pydantic import BaseModel, ConfigDict, Field, model_validator
51
+ from omnata_plugin_runtime.logging import log_exception
52
+ logger = getLogger(__name__)
53
+
54
+ _IDENT_PATTERN = r'^[A-Za-z_][A-Za-z0-9_$.]*$'
55
+ _BARE_IDENT_PATTERN = r'^[A-Za-z_][A-Za-z0-9_$]*$'
56
+ _BARE_IDENT_HINT = "must be an unqualified identifier (no schema prefix; DATA. is prepended automatically)"
57
+
58
+
59
+ class OAuthParameters(BaseModel):
60
+ model_config = ConfigDict(extra='allow')
61
+ oauth_grant: Literal['authorization_code', 'client_credentials'] = 'authorization_code'
62
+ oauth_token_endpoint: Optional[str] = None
63
+ oauth_authorization_endpoint: Optional[str] = None
64
+ oauth_allowed_scopes: Optional[List[str]] = None
65
+ oauth_client_id: Optional[str] = None
66
+ oauth_client_secret: Optional[str] = None
67
+
68
+
69
+ class SetConnectionParameters(BaseModel):
70
+ model_config = ConfigDict(extra='ignore')
71
+
72
+ external_access_integration_name: str = Field(pattern=_IDENT_PATTERN, max_length=255)
73
+ network_rule_name: str = Field(pattern=_BARE_IDENT_PATTERN, max_length=255, description=_BARE_IDENT_HINT)
74
+ network_addresses: List[str]
75
+ network_rule_name_privatelink: Optional[str] = Field(default=None, pattern=_BARE_IDENT_PATTERN, max_length=255, description=_BARE_IDENT_HINT)
76
+ network_rule_addresses_privatelink: Optional[List[str]] = None
77
+ oauth_security_integration_name: Optional[str] = Field(default=None, pattern=_IDENT_PATTERN, max_length=255)
78
+ oauth_parameters: Optional[OAuthParameters] = None
79
+ oauth_secret_name: Optional[str] = Field(default=None, pattern=_BARE_IDENT_PATTERN, max_length=255, description=_BARE_IDENT_HINT)
80
+ oauth_secret_configuration_name: Optional[str] = Field(default=None, pattern=_IDENT_PATTERN, max_length=255)
81
+ other_secrets_name: str = Field(pattern=_BARE_IDENT_PATTERN, max_length=255, description=_BARE_IDENT_HINT)
82
+ connection_secrets: Optional[Dict[str, Any]] = None
83
+ merge_with_secret_name: Optional[str] = Field(default=None, pattern=_BARE_IDENT_PATTERN, max_length=255, description=_BARE_IDENT_HINT)
84
+ external_access_integration_spec_name: str = Field(pattern=_IDENT_PATTERN, max_length=255)
85
+ security_integration_spec_name: Optional[str] = Field(default=None, pattern=_IDENT_PATTERN, max_length=255)
86
+ connection_slug: str = Field(min_length=1, max_length=255)
87
+
88
+ @model_validator(mode='after')
89
+ def _check_combinations(self):
90
+ if self.network_rule_name_privatelink and self.network_rule_addresses_privatelink is None:
91
+ raise ValueError("network_rule_addresses_privatelink is required when network_rule_name_privatelink is provided")
92
+ if self.oauth_security_integration_name and self.oauth_parameters is None:
93
+ raise ValueError("oauth_parameters (object) is required when oauth_security_integration_name is provided")
94
+ if self.oauth_secret_name and not self.oauth_security_integration_name:
95
+ raise ValueError("oauth_security_integration_name is required when oauth_secret_name is provided")
96
+ if self.oauth_secret_configuration_name and not self.oauth_secret_name:
97
+ raise ValueError("oauth_secret_name is required when oauth_secret_configuration_name is provided")
98
+ if self.security_integration_spec_name and not self.oauth_security_integration_name:
99
+ raise ValueError("oauth_security_integration_name is required when security_integration_spec_name is provided")
100
+ return self
101
+
102
+
103
+ def _esc_sql(s):
104
+ return str(s).replace("'", "''")
105
+
106
+
107
+ def _call_proc(session, sql, params):
108
+ """Run `CALL PLUGIN.X(...)` and unwrap the {success, data|error} envelope.
109
+ Raises RuntimeError when success is false; returns `data` on success."""
110
+ rows = session.sql(sql, params=params).collect()
111
+ if not rows:
112
+ raise RuntimeError(f"delegate proc returned no rows: {sql}")
113
+ val = rows[0][0]
114
+ if isinstance(val, str):
115
+ try:
116
+ val = json.loads(val)
117
+ except json.JSONDecodeError:
118
+ pass
119
+ if isinstance(val, dict) and val.get('success') is False:
120
+ raise RuntimeError(val.get('error') or f"delegate proc failed: {sql}")
121
+ return val.get('data') if isinstance(val, dict) else val
122
+
123
+
124
+ def run(session, parameters):
125
+ try:
126
+ params = SetConnectionParameters.model_validate(parameters or {})
127
+
128
+ eai_name = params.external_access_integration_name
129
+ network_rule_name = params.network_rule_name
130
+ network_rule_fqn = f"DATA.{network_rule_name}"
131
+ network_addresses = params.network_addresses
132
+ eai_spec_name = params.external_access_integration_spec_name
133
+ connection_slug = params.connection_slug
134
+
135
+ privatelink_rule_name = params.network_rule_name_privatelink
136
+ privatelink_rule_fqn = f"DATA.{privatelink_rule_name}" if privatelink_rule_name else None
137
+ privatelink_addresses = params.network_rule_addresses_privatelink
138
+
139
+ oauth_si_name = params.oauth_security_integration_name
140
+ oauth_parameters = params.oauth_parameters
141
+ oauth_secret_name = params.oauth_secret_name
142
+ oauth_secret_fqn = f"DATA.{oauth_secret_name}" if oauth_secret_name else None
143
+ oauth_secret_config_name = params.oauth_secret_configuration_name
144
+ si_spec_name = params.security_integration_spec_name
145
+
146
+ other_secrets_name = params.other_secrets_name
147
+ other_secrets_fqn = f"DATA.{other_secrets_name}"
148
+ merge_with_secret_name = params.merge_with_secret_name
149
+ connection_secrets = params.connection_secrets
150
+
151
+ logger.info(f"SET_CONNECTION_OBJECTS: provisioning connection '{connection_slug}' (EAI {eai_name})")
152
+
153
+ # (1) Direct network rule — delegate to CREATE_NETWORK_RULE_OBJECT.
154
+ _call_proc(
155
+ session,
156
+ "call PLUGIN.CREATE_NETWORK_RULE_OBJECT(?, PARSE_JSON(?), ?)",
157
+ [network_rule_fqn, json.dumps(network_addresses), 'HOST_PORT'],
158
+ )
159
+
160
+ # (2) Privatelink network rule — optional.
161
+ if privatelink_rule_fqn:
162
+ _call_proc(
163
+ session,
164
+ "call PLUGIN.CREATE_NETWORK_RULE_OBJECT(?, PARSE_JSON(?), ?)",
165
+ [privatelink_rule_fqn, json.dumps(privatelink_addresses), 'PRIVATE_HOST_PORT'],
166
+ )
167
+
168
+ # (3) OAuth security integration — inline DDL (no existing proc).
169
+ if oauth_si_name:
170
+ op = oauth_parameters
171
+ oauth_grant = op.oauth_grant
172
+ oauth_token_ep = op.oauth_token_endpoint or ''
173
+ oauth_auth_ep = op.oauth_authorization_endpoint or ''
174
+ oauth_scopes = op.oauth_allowed_scopes or []
175
+ oauth_client_id = op.oauth_client_id
176
+ oauth_client_secret = op.oauth_client_secret
177
+
178
+ clauses = [
179
+ "TYPE = API_AUTHENTICATION",
180
+ "AUTH_TYPE = OAUTH2",
181
+ "OAUTH_CLIENT_AUTH_METHOD = CLIENT_SECRET_POST",
182
+ f"OAUTH_GRANT = {oauth_grant.upper()}",
183
+ ]
184
+ if oauth_grant == 'authorization_code' and oauth_auth_ep:
185
+ clauses.append(f"OAUTH_AUTHORIZATION_ENDPOINT = '{_esc_sql(oauth_auth_ep)}'")
186
+ if oauth_token_ep:
187
+ clauses.append(f"OAUTH_TOKEN_ENDPOINT = '{_esc_sql(oauth_token_ep)}'")
188
+ if oauth_scopes:
189
+ scope_parts = [f"'{_esc_sql(s)}'" for s in oauth_scopes]
190
+ clauses.append(f"OAUTH_ALLOWED_SCOPES = ({', '.join(scope_parts)})")
191
+ if oauth_client_id is not None:
192
+ clauses.append(f"OAUTH_CLIENT_ID = '{_esc_sql(oauth_client_id)}'")
193
+ if oauth_client_secret is not None:
194
+ clauses.append(f"OAUTH_CLIENT_SECRET = '{_esc_sql(oauth_client_secret)}'")
195
+ clauses.append("ENABLED = TRUE")
196
+
197
+ si_sql = "CREATE OR REPLACE SECURITY INTEGRATION IDENTIFIER(?)\n " + "\n ".join(clauses)
198
+ logger.info(f"Executing SQL: {si_sql}")
199
+ session.sql(si_sql, params=[oauth_si_name]).collect()
200
+ # USAGE on the security integration is required for any APPLICATION_ROLES listed in an
201
+ # ALTER APPLICATION SET CONFIGURATION DEFINITION SECRET_AUTHORIZATION (the referenced
202
+ # OAuth secret's API_AUTHENTICATION points to this integration).
203
+ session.sql(
204
+ "grant usage on integration IDENTIFIER(?) to application role OMNATA_MANAGEMENT",
205
+ params=[oauth_si_name],
206
+ ).collect()
207
+
208
+ # (4) OAuth secret — delegate to CREATE_OAUTH_SECRET_OBJECT.
209
+ if oauth_secret_fqn:
210
+ _call_proc(
211
+ session,
212
+ "call PLUGIN.CREATE_OAUTH_SECRET_OBJECT(?, ?)",
213
+ [oauth_secret_fqn, oauth_si_name],
214
+ )
215
+ # CREATE_OAUTH_SECRET_OBJECT grants USAGE only. MODIFY is required on the secret for
216
+ # any APPLICATION_ROLES listed in an ALTER APPLICATION SET CONFIGURATION DEFINITION
217
+ # SECRET_AUTHORIZATION, otherwise the configuration fails with
218
+ # "role ... is missing 'MODIFY' privilege on Secret".
219
+ session.sql(
220
+ "grant modify, usage on secret IDENTIFIER(?) to application role OMNATA_MANAGEMENT",
221
+ params=[oauth_secret_fqn],
222
+ ).collect()
223
+
224
+ # (5) Secret authorization configuration.
225
+ # https://docs.snowflake.com/en/developer-guide/native-apps/app-configuration-secret-authorization#step-5-create-the-secret-authorization-configuration
226
+ if oauth_secret_config_name:
227
+ # The secret name alternates its trailing __A / __B suffix on each edit; include that
228
+ # letter in the label so the edit-flow configuration is distinct from the prior one
229
+ # (configuration definition labels must be unique).
230
+ secret_alt = oauth_secret_name[-1]
231
+ cfg_label = f"Authorize OAuth connection: {connection_slug} ({secret_alt})"
232
+ cfg_description = (
233
+ f"Complete the OAuth flow so this application can access the external service "
234
+ f"for connection '{connection_slug}'"
235
+ )
236
+ cfg_sql = (
237
+ f"ALTER APPLICATION SET CONFIGURATION DEFINITION {oauth_secret_config_name}\n"
238
+ f" TYPE = SECRET_AUTHORIZATION\n"
239
+ f" SECRET = {oauth_secret_fqn}\n"
240
+ f" LABEL = '{_esc_sql(cfg_label)}'\n"
241
+ f" DESCRIPTION = '{_esc_sql(cfg_description)}'\n"
242
+ f" APPLICATION_ROLES = (OMNATA_MANAGEMENT)"
243
+ )
244
+ logger.info(f"Executing SQL: {cfg_sql}")
245
+ session.sql(cfg_sql).collect()
246
+
247
+ # (6) Other secrets — seeded from an existing secret and/or caller-supplied values.
248
+ seed_secrets = {}
249
+ if merge_with_secret_name:
250
+ # RETRIEVE_SECRETS_UDF reads the secret contents via _snowflake.get_generic_secret_string,
251
+ # which takes the binding alias (the unqualified name) — not the FQN. The source secret
252
+ # must already be bound to RETRIEVE_SECRETS_UDF via a prior CONFIGURE_APIS run.
253
+ # Passing NULL for the OAuth secret name keeps the access_token key out of the returned object.
254
+ seed_rows = session.sql(
255
+ "select PLUGIN.RETRIEVE_SECRETS_UDF(NULL, ?)",
256
+ params=[merge_with_secret_name],
257
+ ).collect()
258
+ seed_value = seed_rows[0][0] if seed_rows else None
259
+ if isinstance(seed_value, str):
260
+ try:
261
+ seed_value = json.loads(seed_value)
262
+ except json.JSONDecodeError:
263
+ seed_value = None
264
+ if isinstance(seed_value, dict):
265
+ seed_secrets = dict(seed_value)
266
+ if connection_secrets:
267
+ for k, v in connection_secrets.items():
268
+ seed_secrets[k] = v
269
+ # json.dumps emits strict JSON: every U+0000–U+001F byte is escaped, so PEM line breaks
270
+ # in caller-supplied values land in SECRET_STRING as `\n` (two bytes) rather than as a
271
+ # raw LF that would later trip strict json.loads on the read paths.
272
+ _call_proc(
273
+ session,
274
+ "call PLUGIN.CREATE_GENERIC_SECRET_OBJECT(?, ?)",
275
+ [other_secrets_fqn, json.dumps(seed_secrets)],
276
+ )
277
+
278
+ # (7) External Access Integration.
279
+ eai_rules = [network_rule_fqn]
280
+ if privatelink_rule_fqn:
281
+ eai_rules.append(privatelink_rule_fqn)
282
+ eai_secrets = [other_secrets_fqn]
283
+ if oauth_secret_fqn:
284
+ eai_secrets.append(oauth_secret_fqn)
285
+
286
+ eai_rules_sql = ', '.join(eai_rules)
287
+ eai_secrets_clause = ''
288
+ if eai_secrets:
289
+ eai_secrets_clause = f"\n ALLOWED_AUTHENTICATION_SECRETS = ({', '.join(eai_secrets)})"
290
+ eai_sql = (
291
+ f"CREATE OR REPLACE EXTERNAL ACCESS INTEGRATION IDENTIFIER(?)\n"
292
+ f" ALLOWED_NETWORK_RULES = ({eai_rules_sql})"
293
+ f"{eai_secrets_clause}\n"
294
+ f" ENABLED = FALSE"
295
+ )
296
+ logger.info(f"Executing SQL: {eai_sql}")
297
+ session.sql(eai_sql, params=[eai_name]).collect()
298
+ # Grant USAGE so the sync engine (via OMNATA_MANAGEMENT) can see/use the EAI.
299
+ session.sql(
300
+ "grant usage on integration IDENTIFIER(?) to application role OMNATA_MANAGEMENT",
301
+ params=[eai_name],
302
+ ).collect()
303
+
304
+ # (8) EAI application specification — delegate to SET_EAI_SPECIFICATION.
305
+ # Unlike for security integrations/configurations, this specification does not need to alternate when editing as it's not bound to any objects
306
+ eai_spec_props = {
307
+ "LABEL": f"External access for connection: {connection_slug}",
308
+ "DESCRIPTION": f"Allows this app to communicate with external services for connection '{connection_slug}'",
309
+ "HOST_PORTS": network_addresses,
310
+ }
311
+ if privatelink_addresses:
312
+ eai_spec_props["PRIVATE_HOST_PORTS"] = privatelink_addresses
313
+ _call_proc(
314
+ session,
315
+ "call PLUGIN.SET_EAI_SPECIFICATION(?, PARSE_JSON(?))",
316
+ [eai_spec_name, json.dumps(eai_spec_props)],
317
+ )
318
+
319
+ # (9) SI application specification — delegate to SET_SI_SPECIFICATION (OAuth only).
320
+ if si_spec_name:
321
+ op2 = oauth_parameters
322
+ grant_uc = op2.oauth_grant.upper()
323
+ # Include the alternating suffix letter so the edit-flow spec gets a distinct label.
324
+ si_spec_alt = si_spec_name[-1]
325
+ si_spec_props = {
326
+ "LABEL": f"OAuth integration for connection: {connection_slug} ({si_spec_alt})",
327
+ "DESCRIPTION": f"Allows this app to use an OAuth security integration for connection '{connection_slug}'",
328
+ "OAUTH_TYPE": grant_uc,
329
+ }
330
+ if op2.oauth_token_endpoint:
331
+ si_spec_props["OAUTH_TOKEN_ENDPOINT"] = op2.oauth_token_endpoint
332
+ if grant_uc == 'AUTHORIZATION_CODE' and op2.oauth_authorization_endpoint:
333
+ si_spec_props["OAUTH_AUTHORIZATION_ENDPOINT"] = op2.oauth_authorization_endpoint
334
+ if op2.oauth_allowed_scopes:
335
+ si_spec_props["OAUTH_ALLOWED_SCOPES"] = op2.oauth_allowed_scopes
336
+ _call_proc(
337
+ session,
338
+ "call PLUGIN.SET_SI_SPECIFICATION(?, PARSE_JSON(?))",
339
+ [si_spec_name, json.dumps(si_spec_props)],
340
+ )
341
+
342
+ return {
343
+ "success": True,
344
+ "data": {
345
+ "connection_slug": connection_slug,
346
+ "external_access_integration_name": eai_name,
347
+ "network_rule_name": network_rule_fqn,
348
+ "network_rule_name_privatelink": privatelink_rule_fqn,
349
+ "oauth_security_integration_name": oauth_si_name,
350
+ "oauth_secret_name": oauth_secret_fqn,
351
+ "oauth_secret_configuration_name": oauth_secret_config_name,
352
+ "other_secrets_name": other_secrets_fqn,
353
+ "external_access_integration_spec_name": eai_spec_name,
354
+ "security_integration_spec_name": si_spec_name,
355
+ },
356
+ }
357
+ except Exception as exception:
358
+ log_exception(exception, logger)
359
+ return {
360
+ "success": False,
361
+ "error": f"SET_CONNECTION_OBJECTS: {str(exception)}",
362
+ }
363
+ $$
364
+ ;
365
+
366
+ grant usage on procedure PLUGIN.SET_CONNECTION_OBJECTS(OBJECT)
367
+ to application role OMNATA_MANAGEMENT;
@@ -1,375 +0,0 @@
1
- create or replace procedure PLUGIN.SET_CONNECTION_OBJECTS(PARAMETERS OBJECT)
2
- returns object
3
- language javascript
4
- COMMENT = $$
5
- Creates or replaces every Snowflake object required to provision a connection: network
6
- rules, OAuth security integration, OAuth secret, secret authorization configuration,
7
- other_secrets generic secret, External Access Integration, EAI application specification,
8
- and Security Integration application specification. OAuth-related objects are only created
9
- when oauth_security_integration_name is provided.
10
-
11
- Expected keys on PARAMETERS:
12
- external_access_integration_name (required; account-level, no schema)
13
- network_rule_name (required; unqualified — DATA schema is prepended)
14
- network_addresses (required, array of strings)
15
- network_rule_name_privatelink (optional; unqualified — DATA schema is prepended)
16
- network_rule_addresses_privatelink (required if privatelink rule set)
17
- oauth_security_integration_name (optional; account-level, no schema — enables the oauth branch)
18
- oauth_parameters (required if oauth SI set; object with
19
- oauth_grant, oauth_token_endpoint,
20
- oauth_authorization_endpoint,
21
- oauth_allowed_scopes, oauth_client_id,
22
- oauth_client_secret)
23
- oauth_secret_name (optional; unqualified — DATA schema is prepended; requires oauth SI)
24
- oauth_secret_configuration_name (optional; application-scope, no schema; requires oauth_secret_name)
25
- other_secrets_name (required; unqualified — DATA schema is prepended)
26
- connection_secrets (optional object; merged into the generic secret
27
- contents, overwriting any matching keys from
28
- merge_with_secret_name)
29
- merge_with_secret_name (optional; unqualified name of an existing generic
30
- secret in DATA whose contents are read via
31
- RETRIEVE_SECRETS_UDF and used as the baseline for
32
- the new other_secrets generic secret.)
33
- external_access_integration_spec_name (required; application-scope, no schema)
34
- security_integration_spec_name (optional; application-scope, no schema; requires oauth SI)
35
- connection_slug (required; human-readable connection identifier
36
- included in spec labels/descriptions so the
37
- consumer knows which connection they are
38
- approving)
39
- $$
40
- execute as owner
41
- as
42
- $$
43
- try{
44
- var p = PARAMETERS || {};
45
-
46
- function isIdent(n){
47
- return typeof n === 'string' && /^[A-Za-z_][A-Za-z0-9_$.]*$/.test(n) && n.length <= 255;
48
- }
49
- function isBareIdent(n){
50
- return typeof n === 'string' && /^[A-Za-z_][A-Za-z0-9_$]*$/.test(n) && n.length <= 255;
51
- }
52
- function requireIdent(key){
53
- if (!isIdent(p[key])){ throw `parameter '${key}' is required and must be a valid identifier`; }
54
- return p[key];
55
- }
56
- function optIdent(key){
57
- if (p[key] == null) return null;
58
- if (!isIdent(p[key])){ throw `parameter '${key}' must be a valid identifier`; }
59
- return p[key];
60
- }
61
- function requireBareIdent(key){
62
- if (!isBareIdent(p[key])){ throw `parameter '${key}' is required and must be an unqualified identifier (no schema prefix; DATA. is prepended automatically)`; }
63
- return p[key];
64
- }
65
- function optBareIdent(key){
66
- if (p[key] == null) return null;
67
- if (!isBareIdent(p[key])){ throw `parameter '${key}' must be an unqualified identifier (no schema prefix; DATA. is prepended automatically)`; }
68
- return p[key];
69
- }
70
- function requireArrayOfStrings(key){
71
- if (!Array.isArray(p[key])){ throw `parameter '${key}' must be an array of strings`; }
72
- p[key].forEach(function(s){ if (typeof s !== 'string'){ throw `parameter '${key}' entries must be strings`; } });
73
- return p[key];
74
- }
75
- function optArrayOfStrings(key){
76
- if (p[key] == null) return null;
77
- return requireArrayOfStrings(key);
78
- }
79
- function esc(s){ return String(s).replace(/'/g, "''"); }
80
- // JSON.stringify per spec must escape control characters in strings, but
81
- // PEM-bearing values (RSA private keys with literal LF/CR line breaks) have
82
- // produced stored secrets that strict json.loads rejects with
83
- // "Invalid control character at...". Re-escape any control character that
84
- // survives serialization so the stored secret is always strict-JSON-parseable.
85
- function strictJsonStringify(v){
86
- return JSON.stringify(v).replace(/[\x00-\x1f]/g, function(c){
87
- switch(c){
88
- case '\b': return '\\b';
89
- case '\f': return '\\f';
90
- case '\n': return '\\n';
91
- case '\r': return '\\r';
92
- case '\t': return '\\t';
93
- default:
94
- return '\\u' + ('0000' + c.charCodeAt(0).toString(16)).slice(-4);
95
- }
96
- });
97
- }
98
- function callProc(sql, binds){
99
- var rs = snowflake.createStatement({sqlText: sql, binds: binds}).execute();
100
- rs.next();
101
- var r = rs.getColumnValue(1);
102
- if (r && r.success === false){ throw r.error || `delegate proc failed: ${sql}`; }
103
- return r;
104
- }
105
-
106
- // Required parameters.
107
- var eaiName = requireIdent('external_access_integration_name');
108
- var networkRuleName = requireBareIdent('network_rule_name');
109
- var networkRuleFqn = `DATA.${networkRuleName}`;
110
- var networkAddresses = requireArrayOfStrings('network_addresses');
111
- var eaiSpecName = requireIdent('external_access_integration_spec_name');
112
- var connectionSlug = p.connection_slug;
113
- if (typeof connectionSlug !== 'string' || connectionSlug.length === 0 || connectionSlug.length > 255){
114
- throw `parameter 'connection_slug' is required and must be a non-empty string`;
115
- }
116
-
117
- // Optional privatelink network rule.
118
- var privatelinkRuleName = optBareIdent('network_rule_name_privatelink');
119
- var privatelinkRuleFqn = privatelinkRuleName ? `DATA.${privatelinkRuleName}` : null;
120
- var privatelinkAddresses = optArrayOfStrings('network_rule_addresses_privatelink');
121
- if (privatelinkRuleName && privatelinkAddresses === null){
122
- throw `network_rule_addresses_privatelink is required when network_rule_name_privatelink is provided`;
123
- }
124
-
125
- // Optional OAuth bundle.
126
- var oauthSiName = optIdent('oauth_security_integration_name');
127
- var oauthSecretName = optBareIdent('oauth_secret_name');
128
- var oauthSecretFqn = oauthSecretName ? `DATA.${oauthSecretName}` : null;
129
- var oauthSecretConfigName = optIdent('oauth_secret_configuration_name');
130
- var siSpecName = optIdent('security_integration_spec_name');
131
- var oauthParameters = (typeof p.oauth_parameters === 'object' && p.oauth_parameters !== null) ? p.oauth_parameters : null;
132
- if (oauthSiName && !oauthParameters){
133
- throw `oauth_parameters (object) is required when oauth_security_integration_name is provided`;
134
- }
135
- if (oauthSecretName && !oauthSiName){
136
- throw `oauth_security_integration_name is required when oauth_secret_name is provided`;
137
- }
138
- if (oauthSecretConfigName && !oauthSecretName){
139
- throw `oauth_secret_name is required when oauth_secret_configuration_name is provided`;
140
- }
141
- if (siSpecName && !oauthSiName){
142
- throw `oauth_security_integration_name is required when security_integration_spec_name is provided`;
143
- }
144
-
145
- // Other-secrets (mandatory) plus optional seed/merge inputs.
146
- var otherSecretsName = requireBareIdent('other_secrets_name');
147
- var otherSecretsFqn = `DATA.${otherSecretsName}`;
148
- var mergeWithSecretName = optBareIdent('merge_with_secret_name');
149
- var connectionSecrets = (typeof p.connection_secrets === 'object' && p.connection_secrets !== null && !Array.isArray(p.connection_secrets))
150
- ? p.connection_secrets
151
- : null;
152
- if (p.connection_secrets != null && connectionSecrets === null){
153
- throw `connection_secrets must be an object`;
154
- }
155
-
156
- snowflake.log("info", `SET_CONNECTION_OBJECTS: provisioning connection '${connectionSlug}' (EAI ${eaiName})`);
157
-
158
- // (1) Direct network rule — delegate to CREATE_NETWORK_RULE_OBJECT.
159
- callProc(
160
- `call PLUGIN.CREATE_NETWORK_RULE_OBJECT(?, PARSE_JSON(?), ?)`,
161
- [networkRuleFqn, JSON.stringify(networkAddresses), 'HOST_PORT']
162
- );
163
-
164
- // (2) Privatelink network rule — optional.
165
- if (privatelinkRuleFqn){
166
- callProc(
167
- `call PLUGIN.CREATE_NETWORK_RULE_OBJECT(?, PARSE_JSON(?), ?)`,
168
- [privatelinkRuleFqn, JSON.stringify(privatelinkAddresses), 'PRIVATE_HOST_PORT']
169
- );
170
- }
171
-
172
- // (3) OAuth security integration — inline DDL (no existing proc).
173
- if (oauthSiName){
174
- var op = oauthParameters;
175
- var oauthGrant = String(op.oauth_grant || 'authorization_code').toLowerCase();
176
- var oauthTokenEp = op.oauth_token_endpoint || '';
177
- var oauthAuthEp = op.oauth_authorization_endpoint || '';
178
- var oauthScopes = Array.isArray(op.oauth_allowed_scopes) ? op.oauth_allowed_scopes : [];
179
- var oauthClientId = op.oauth_client_id;
180
- var oauthClientSecret = op.oauth_client_secret;
181
-
182
- if (oauthGrant !== 'authorization_code' && oauthGrant !== 'client_credentials'){
183
- throw `oauth_parameters.oauth_grant must be 'authorization_code' or 'client_credentials'`;
184
- }
185
-
186
- var clauses = [
187
- "TYPE = API_AUTHENTICATION",
188
- "AUTH_TYPE = OAUTH2",
189
- "OAUTH_CLIENT_AUTH_METHOD = CLIENT_SECRET_POST",
190
- `OAUTH_GRANT = ${oauthGrant.toUpperCase()}`
191
- ];
192
- if (oauthGrant === 'authorization_code' && oauthAuthEp){
193
- clauses.push(`OAUTH_AUTHORIZATION_ENDPOINT = '${esc(oauthAuthEp)}'`);
194
- }
195
- if (oauthTokenEp){
196
- clauses.push(`OAUTH_TOKEN_ENDPOINT = '${esc(oauthTokenEp)}'`);
197
- }
198
- if (oauthScopes.length > 0){
199
- var scopesList = oauthScopes.map(function(s){
200
- if (typeof s !== 'string'){ throw `oauth_parameters.oauth_allowed_scopes entries must be strings`; }
201
- return `'${esc(s)}'`;
202
- }).join(', ');
203
- clauses.push(`OAUTH_ALLOWED_SCOPES = (${scopesList})`);
204
- }
205
- if (oauthClientId != null){
206
- if (typeof oauthClientId !== 'string'){ throw `oauth_parameters.oauth_client_id must be a string`; }
207
- clauses.push(`OAUTH_CLIENT_ID = '${esc(oauthClientId)}'`);
208
- }
209
- if (oauthClientSecret != null){
210
- if (typeof oauthClientSecret !== 'string'){ throw `oauth_parameters.oauth_client_secret must be a string`; }
211
- clauses.push(`OAUTH_CLIENT_SECRET = '${esc(oauthClientSecret)}'`);
212
- }
213
- clauses.push("ENABLED = TRUE");
214
-
215
- var siSql = `CREATE OR REPLACE SECURITY INTEGRATION IDENTIFIER(?)\n ` + clauses.join('\n ');
216
- snowflake.log("info", `Executing SQL: ${siSql}`);
217
- snowflake.createStatement({sqlText: siSql, binds: [oauthSiName]}).execute();
218
- // USAGE on the security integration is required for any APPLICATION_ROLES listed in an
219
- // ALTER APPLICATION SET CONFIGURATION DEFINITION SECRET_AUTHORIZATION (the referenced
220
- // OAuth secret's API_AUTHENTICATION points to this integration).
221
- snowflake.createStatement({
222
- sqlText: `grant usage on integration IDENTIFIER(?) to application role OMNATA_MANAGEMENT`,
223
- binds: [oauthSiName]
224
- }).execute();
225
- }
226
-
227
- // (4) OAuth secret — delegate to CREATE_OAUTH_SECRET_OBJECT.
228
- if (oauthSecretFqn){
229
- callProc(`call PLUGIN.CREATE_OAUTH_SECRET_OBJECT(?, ?)`, [oauthSecretFqn, oauthSiName]);
230
- // CREATE_OAUTH_SECRET_OBJECT grants USAGE only. MODIFY is required on the secret for
231
- // any APPLICATION_ROLES listed in an ALTER APPLICATION SET CONFIGURATION DEFINITION
232
- // SECRET_AUTHORIZATION, otherwise the configuration fails with
233
- // "role ... is missing 'MODIFY' privilege on Secret".
234
- snowflake.createStatement({
235
- sqlText: `grant modify, usage on secret IDENTIFIER(?) to application role OMNATA_MANAGEMENT`,
236
- binds: [oauthSecretFqn]
237
- }).execute();
238
- }
239
-
240
- // (5) Secret authorization configuration.
241
- // TODO: verify exact DDL and keywords against Snowflake docs; WebFetch was unavailable during authoring.
242
- // https://docs.snowflake.com/en/developer-guide/native-apps/app-configuration-secret-authorization#step-5-create-the-secret-authorization-configuration
243
- if (oauthSecretConfigName){
244
- // The secret name alternates its trailing __A / __B suffix on each edit; include that
245
- // letter in the label so the edit-flow configuration is distinct from the prior one
246
- // (configuration definition labels must be unique).
247
- var secretAlt = oauthSecretName.slice(-1);
248
- var cfgLabel = `Authorize OAuth connection: ${connectionSlug} (${secretAlt})`;
249
- var cfgDescription = `Complete the OAuth flow so this application can access the external service for connection '${connectionSlug}'`;
250
- var cfgSql = `ALTER APPLICATION SET CONFIGURATION DEFINITION ${oauthSecretConfigName}\n` +
251
- ` TYPE = SECRET_AUTHORIZATION\n` +
252
- ` SECRET = ${oauthSecretFqn}\n` +
253
- ` LABEL = '${esc(cfgLabel)}'\n` +
254
- ` DESCRIPTION = '${esc(cfgDescription)}'\n` +
255
- ` APPLICATION_ROLES = (OMNATA_MANAGEMENT)`;
256
- snowflake.log("info", `Executing SQL: ${cfgSql}`);
257
- snowflake.createStatement({sqlText: cfgSql, binds: []}).execute();
258
- }
259
-
260
- // (6) Other secrets — seeded from an existing secret and/or caller-supplied values.
261
- var seedSecrets = {};
262
- if (mergeWithSecretName){
263
- // RETRIEVE_SECRETS_UDF reads the secret contents via _snowflake.get_generic_secret_string,
264
- // which takes the binding alias (the unqualified name) — not the FQN. The source secret
265
- // must already be bound to RETRIEVE_SECRETS_UDF via a prior CONFIGURE_APIS run.
266
- // Passing NULL for the OAuth secret name keeps the access_token key out of the returned object.
267
- var seedRs = snowflake.createStatement({
268
- sqlText: `select PLUGIN.RETRIEVE_SECRETS_UDF(NULL, ?)`,
269
- binds: [mergeWithSecretName]
270
- }).execute();
271
- seedRs.next();
272
- var seedValue = seedRs.getColumnValue(1);
273
- if (seedValue && typeof seedValue === 'object'){
274
- seedSecrets = seedValue;
275
- }
276
- }
277
- if (connectionSecrets){
278
- Object.keys(connectionSecrets).forEach(function(k){
279
- seedSecrets[k] = connectionSecrets[k];
280
- });
281
- }
282
- callProc(`call PLUGIN.CREATE_GENERIC_SECRET_OBJECT(?, ?)`, [otherSecretsFqn, strictJsonStringify(seedSecrets)]);
283
-
284
- // (7) External Access Integration.
285
- var eaiRules = [networkRuleFqn];
286
- if (privatelinkRuleFqn){ eaiRules.push(privatelinkRuleFqn); }
287
- var eaiSecrets = [otherSecretsFqn];
288
- if (oauthSecretFqn){ eaiSecrets.push(oauthSecretFqn); }
289
-
290
- var eaiRulesSql = eaiRules.join(', ');
291
- var eaiSecretsClause = '';
292
- if (eaiSecrets.length > 0){
293
- eaiSecretsClause = `\n ALLOWED_AUTHENTICATION_SECRETS = (${eaiSecrets.join(', ')})`;
294
- }
295
- var eaiSql = `CREATE OR REPLACE EXTERNAL ACCESS INTEGRATION IDENTIFIER(?)\n` +
296
- ` ALLOWED_NETWORK_RULES = (${eaiRulesSql})` +
297
- eaiSecretsClause + `\n` +
298
- ` ENABLED = FALSE`;
299
- snowflake.log("info", `Executing SQL: ${eaiSql}`);
300
- snowflake.createStatement({sqlText: eaiSql, binds: [eaiName]}).execute();
301
- // Grant USAGE so the sync engine (via OMNATA_MANAGEMENT) can see/use the EAI.
302
- snowflake.createStatement({
303
- sqlText: `grant usage on integration IDENTIFIER(?) to application role OMNATA_MANAGEMENT`,
304
- binds: [eaiName]
305
- }).execute();
306
-
307
- // (8) EAI application specification — delegate to SET_EAI_SPECIFICATION.
308
- // Unlike for security integrations/configurations, this specification does not need to alternate when editing as it's not bound to any objects
309
- var eaiSpecProps = {
310
- LABEL: `External access for connection: ${connectionSlug}`,
311
- DESCRIPTION: `Allows this app to communicate with external services for connection '${connectionSlug}'`,
312
- HOST_PORTS: networkAddresses
313
- };
314
- if (privatelinkAddresses){
315
- eaiSpecProps.PRIVATE_HOST_PORTS = privatelinkAddresses;
316
- }
317
- callProc(
318
- `call PLUGIN.SET_EAI_SPECIFICATION(?, PARSE_JSON(?))`,
319
- [eaiSpecName, JSON.stringify(eaiSpecProps)]
320
- );
321
-
322
- // (9) SI application specification — delegate to SET_SI_SPECIFICATION (OAuth only).
323
- if (siSpecName){
324
- var op2 = oauthParameters;
325
- var grantUc = String(op2.oauth_grant || 'authorization_code').toUpperCase();
326
- // Include the alternating suffix letter so the edit-flow spec gets a distinct label.
327
- var siSpecAlt = siSpecName.slice(-1);
328
- var siSpecProps = {
329
- LABEL: `OAuth integration for connection: ${connectionSlug} (${siSpecAlt})`,
330
- DESCRIPTION: `Allows this app to use an OAuth security integration for connection '${connectionSlug}'`,
331
- OAUTH_TYPE: grantUc
332
- };
333
- if (op2.oauth_token_endpoint){
334
- siSpecProps.OAUTH_TOKEN_ENDPOINT = op2.oauth_token_endpoint;
335
- }
336
- if (grantUc === 'AUTHORIZATION_CODE' && op2.oauth_authorization_endpoint){
337
- siSpecProps.OAUTH_AUTHORIZATION_ENDPOINT = op2.oauth_authorization_endpoint;
338
- }
339
- if (Array.isArray(op2.oauth_allowed_scopes) && op2.oauth_allowed_scopes.length > 0){
340
- siSpecProps.OAUTH_ALLOWED_SCOPES = op2.oauth_allowed_scopes;
341
- }
342
- callProc(
343
- `call PLUGIN.SET_SI_SPECIFICATION(?, PARSE_JSON(?))`,
344
- [siSpecName, JSON.stringify(siSpecProps)]
345
- );
346
- }
347
-
348
- return {
349
- "success": true,
350
- "data": {
351
- "connection_slug": connectionSlug,
352
- "external_access_integration_name": eaiName,
353
- "network_rule_name": networkRuleFqn,
354
- "network_rule_name_privatelink": privatelinkRuleFqn,
355
- "oauth_security_integration_name": oauthSiName,
356
- "oauth_secret_name": oauthSecretFqn,
357
- "oauth_secret_configuration_name": oauthSecretConfigName,
358
- "other_secrets_name": otherSecretsFqn,
359
- "external_access_integration_spec_name": eaiSpecName,
360
- "security_integration_spec_name": siSpecName
361
- }
362
- };
363
- }
364
- catch(e){
365
- snowflake.log("error", e);
366
- return {
367
- "success": false,
368
- "error": `SET_CONNECTION_OBJECTS: ${String(e)}`
369
- };
370
- }
371
- $$
372
- ;
373
-
374
- grant usage on procedure PLUGIN.SET_CONNECTION_OBJECTS(OBJECT)
375
- to application role OMNATA_MANAGEMENT;