omnata-plugin-devkit 0.13.1a180__tar.gz → 0.13.2__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.
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/PKG-INFO +1 -1
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/pyproject.toml +1 -1
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/jinja_templates/CONFIGURE_APIS.sql.jinja +2 -1
- omnata_plugin_devkit-0.13.2/src/omnata_plugin_devkit/jinja_templates/CREATE_GENERIC_SECRET_OBJECT.sql.jinja +50 -0
- omnata_plugin_devkit-0.13.2/src/omnata_plugin_devkit/jinja_templates/CREATE_GENERIC_SECRET_OBJECT_FROM_EXISTING.sql.jinja +82 -0
- omnata_plugin_devkit-0.13.2/src/omnata_plugin_devkit/jinja_templates/SET_CONNECTION_OBJECTS.sql.jinja +357 -0
- omnata_plugin_devkit-0.13.1a180/src/omnata_plugin_devkit/jinja_templates/CREATE_GENERIC_SECRET_OBJECT.sql.jinja +0 -35
- omnata_plugin_devkit-0.13.1a180/src/omnata_plugin_devkit/jinja_templates/CREATE_GENERIC_SECRET_OBJECT_FROM_EXISTING.sql.jinja +0 -55
- omnata_plugin_devkit-0.13.1a180/src/omnata_plugin_devkit/jinja_templates/SET_CONNECTION_OBJECTS.sql.jinja +0 -357
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/LICENSE +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/README.md +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/__init__.py +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/airbyte_wrapper.py +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/cli/__init__.py +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/development.ipynb +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/development_session.py +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/initialiser.py +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/jinja_templates/API_LIMITS.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/jinja_templates/ASSIGN_OUTBOUND_TARGET_TYPE.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/jinja_templates/CHECK_CONNECTION_PROGRESS.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/jinja_templates/CONFIGURATION_FORM.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/jinja_templates/CONNECTION_FORM.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/jinja_templates/CONNECTION_TEST.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/jinja_templates/CONSTRUCT_FORM_OPTION.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/jinja_templates/CREATE_BILLING_EVENTS.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/jinja_templates/CREATE_NETWORK_RULE_OBJECT.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/jinja_templates/CREATE_NETWORK_RULE_OBJECT_FROM_EXISTING.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/jinja_templates/CREATE_OAUTH_SECRET_OBJECT.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/jinja_templates/DROP_NETWORK_RULES.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/jinja_templates/DROP_SECRETS.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/jinja_templates/FETCH_CONNECTIONS.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/jinja_templates/FETCH_SYNCS.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/jinja_templates/FETCH_SYNC_BRANCHES.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/jinja_templates/GET_MISSING_APP_PRIVILEGES.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/jinja_templates/INBOUND_LIST_STREAMS.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/jinja_templates/LIST_APP_SPECIFICATIONS.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/jinja_templates/LIST_STAGES.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/jinja_templates/NETWORK_ADDRESSES.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/jinja_templates/NGROK_POST_TUNNEL_FIELDS.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/jinja_templates/OUTBOUND_RECORD_VALIDATOR.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/jinja_templates/PENDING_API_CONFIGURATION.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/jinja_templates/POST_INSTALL_ACTIONS.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/jinja_templates/RENAME_CONNECTION_METHODS.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/jinja_templates/RETRIEVE_NETWORK_RULE_OBJECT.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/jinja_templates/RETRIEVE_SECRETS.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/jinja_templates/RETRIEVE_SECRETS_UDF.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/jinja_templates/SET_EAI_ENABLED.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/jinja_templates/SET_EAI_SPECIFICATION.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/jinja_templates/SET_SI_SPECIFICATION.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/jinja_templates/SYNC.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/jinja_templates/TEST_CALLBACK.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/jinja_templates/TEST_OAUTH_TOKEN_EXISTS.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/jinja_templates/TUNNEL_TEST.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/jinja_templates/UPDATE_API_CONFIGURATION.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/jinja_templates/UPDATE_GENERIC_SECRET_OBJECT.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/jinja_templates/UPDATE_GENERIC_SECRET_OBJECT_OLD.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/jinja_templates/UPDATE_NETWORK_RULE_OBJECT.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/jinja_templates/manifest.yml.jinja +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/native_app_packaging.py +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/plugin_registration.py +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/plugin_template/icon.svg +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/plugin_template/plugin.py +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/plugin_template/plugin_development.ipynb +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/plugin_template/requirements.txt +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/plugin_uploader.py +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/snowcli/__init__.py +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/snowcli/cli/__init__.py +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/snowcli/cli/api/__init__.py +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/snowcli/cli/api/config.py +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/snowcli/cli/api/constants.py +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/snowcli/cli/api/exceptions.py +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/snowcli/cli/api/secure_path.py +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/snowcli/cli/api/secure_utils.py +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/snowcli/cli/api/utils/__init__.py +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/snowcli/cli/api/utils/types.py +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/snowcli/cli/app/__init__.py +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/snowcli/cli/app/snow_connector.py +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/snowcli/cli/plugins/__init__.py +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/snowcli/cli/plugins/snowpark/__init__.py +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/snowcli/cli/plugins/snowpark/models.py +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/snowcli/cli/plugins/snowpark/package/__init__.py +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/snowcli/cli/plugins/snowpark/package_utils.py +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/snowcli/cli/plugins/snowpark/snowpark_shared.py +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/snowcli/cli/plugins/snowpark/venv.py +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/snowcli/cli/plugins/snowpark/zipper.py +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/snowcli/cli/templates/environment.yml.jinja +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/streamlit/plugin_configuration.py +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/test_step_definitions.py +0 -0
- {omnata_plugin_devkit-0.13.1a180 → omnata_plugin_devkit-0.13.2}/src/omnata_plugin_devkit/utils.py +0 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
create or replace procedure PLUGIN.CREATE_GENERIC_SECRET_OBJECT(SECRET_NAME VARCHAR, SECRET_CONTENTS VARCHAR)
|
|
2
|
+
returns object
|
|
3
|
+
language python
|
|
4
|
+
RUNTIME_VERSION = '3.10'
|
|
5
|
+
PACKAGES = ({{packages}})
|
|
6
|
+
HANDLER = 'run'
|
|
7
|
+
COMMENT = $$
|
|
8
|
+
Creates a generic secret object. SECRET_STRING is written via a dollar-quoted
|
|
9
|
+
literal rather than a bind parameter: bind binding for SECRET_STRING = ? was
|
|
10
|
+
observed to interpret backslash escapes inside the value, turning a JSON `\n`
|
|
11
|
+
two-byte escape into a raw LF byte and corrupting the stored JSON. Dollar
|
|
12
|
+
quoting is byte-faithful.
|
|
13
|
+
$$
|
|
14
|
+
execute as owner
|
|
15
|
+
as
|
|
16
|
+
$$
|
|
17
|
+
from logging import getLogger
|
|
18
|
+
logger = getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
# Avoid putting two literal dollar signs in this proc body (the body itself
|
|
21
|
+
# is dollar-quoted, so a stray pair of dollar signs would close it early).
|
|
22
|
+
_TWO_DOLLARS = "$" + "$"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def run(session, secret_name, secret_contents):
|
|
26
|
+
try:
|
|
27
|
+
if secret_contents is None:
|
|
28
|
+
secret_contents = ""
|
|
29
|
+
if _TWO_DOLLARS in secret_contents:
|
|
30
|
+
raise ValueError(
|
|
31
|
+
"secret_contents contains '" + _TWO_DOLLARS + "', which is "
|
|
32
|
+
"currently not supported due to Snowflake delimiter requirements."
|
|
33
|
+
)
|
|
34
|
+
sql = (
|
|
35
|
+
"CREATE OR REPLACE SECRET IDENTIFIER(?) TYPE = GENERIC_STRING "
|
|
36
|
+
"SECRET_STRING = " + _TWO_DOLLARS + secret_contents + _TWO_DOLLARS
|
|
37
|
+
)
|
|
38
|
+
session.sql(sql, params=[secret_name]).collect()
|
|
39
|
+
session.sql(
|
|
40
|
+
"grant usage on secret IDENTIFIER(?) to application role OMNATA_MANAGEMENT",
|
|
41
|
+
params=[secret_name],
|
|
42
|
+
).collect()
|
|
43
|
+
return {"success": True, "data": None}
|
|
44
|
+
except Exception as e:
|
|
45
|
+
return {"success": False, "error": f"CREATE_GENERIC_SECRET_OBJECT: {str(e)}"}
|
|
46
|
+
$$
|
|
47
|
+
;
|
|
48
|
+
|
|
49
|
+
grant usage on procedure PLUGIN.CREATE_GENERIC_SECRET_OBJECT(VARCHAR, VARCHAR)
|
|
50
|
+
to application role OMNATA_MANAGEMENT;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
create or replace procedure PLUGIN.CREATE_GENERIC_SECRET_OBJECT_FROM_EXISTING(
|
|
2
|
+
SECRET_NAME VARCHAR,
|
|
3
|
+
SECRET_TO_CLONE VARCHAR,
|
|
4
|
+
KEYS_TO_INCLUDE ARRAY,
|
|
5
|
+
NEW_CONTENTS_TO_MERGE VARCHAR DEFAULT NULL
|
|
6
|
+
)
|
|
7
|
+
returns object
|
|
8
|
+
language python
|
|
9
|
+
RUNTIME_VERSION = '3.10'
|
|
10
|
+
PACKAGES = ({{packages}})
|
|
11
|
+
HANDLER = 'run'
|
|
12
|
+
COMMENT = $$
|
|
13
|
+
Creates a generic secret object using an existing secret as the seed.
|
|
14
|
+
- KEYS_TO_INCLUDE (optional): if provided, restrict the cloned content to
|
|
15
|
+
these keys before any merging.
|
|
16
|
+
- NEW_CONTENTS_TO_MERGE (optional): JSON object string whose entries are
|
|
17
|
+
merged on top of the cloned content (overwriting matching keys). Use
|
|
18
|
+
this to add or update fields in one shot without a second proc call.
|
|
19
|
+
The write itself is delegated to CREATE_GENERIC_SECRET_OBJECT, which uses a
|
|
20
|
+
dollar-quoted SECRET_STRING literal to avoid bind-parameter escape
|
|
21
|
+
interpretation that corrupts newlines in PEM values.
|
|
22
|
+
$$
|
|
23
|
+
execute as owner
|
|
24
|
+
as
|
|
25
|
+
$$
|
|
26
|
+
import json
|
|
27
|
+
from logging import getLogger
|
|
28
|
+
logger = getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _call_proc(session, sql, params):
|
|
32
|
+
rows = session.sql(sql, params=params).collect()
|
|
33
|
+
if not rows:
|
|
34
|
+
raise RuntimeError(f"delegate proc returned no rows: {sql}")
|
|
35
|
+
val = rows[0][0]
|
|
36
|
+
if isinstance(val, str):
|
|
37
|
+
try:
|
|
38
|
+
val = json.loads(val)
|
|
39
|
+
except json.JSONDecodeError:
|
|
40
|
+
pass
|
|
41
|
+
if isinstance(val, dict) and val.get('success') is False:
|
|
42
|
+
raise RuntimeError(val.get('error') or f"delegate proc failed: {sql}")
|
|
43
|
+
return val.get('data') if isinstance(val, dict) else val
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def run(session, secret_name, secret_to_clone, keys_to_include, new_contents_to_merge):
|
|
47
|
+
try:
|
|
48
|
+
existing = _call_proc(
|
|
49
|
+
session,
|
|
50
|
+
"call PLUGIN.RETRIEVE_SECRETS(NULL, ?)",
|
|
51
|
+
[secret_to_clone],
|
|
52
|
+
) or {}
|
|
53
|
+
if not isinstance(existing, dict):
|
|
54
|
+
existing = {}
|
|
55
|
+
if keys_to_include is not None:
|
|
56
|
+
existing = {k: existing[k] for k in keys_to_include if k in existing}
|
|
57
|
+
if new_contents_to_merge is not None:
|
|
58
|
+
try:
|
|
59
|
+
new_dict = json.loads(new_contents_to_merge)
|
|
60
|
+
except (TypeError, json.JSONDecodeError) as parse_err:
|
|
61
|
+
raise ValueError(
|
|
62
|
+
f"NEW_CONTENTS_TO_MERGE must be a JSON object string: {parse_err}"
|
|
63
|
+
) from parse_err
|
|
64
|
+
if not isinstance(new_dict, dict):
|
|
65
|
+
raise ValueError("NEW_CONTENTS_TO_MERGE must decode to a JSON object")
|
|
66
|
+
existing = {**existing, **new_dict}
|
|
67
|
+
_call_proc(
|
|
68
|
+
session,
|
|
69
|
+
"call PLUGIN.CREATE_GENERIC_SECRET_OBJECT(?, ?)",
|
|
70
|
+
[secret_name, json.dumps(existing)],
|
|
71
|
+
)
|
|
72
|
+
return {"success": True, "data": None}
|
|
73
|
+
except Exception as e:
|
|
74
|
+
return {
|
|
75
|
+
"success": False,
|
|
76
|
+
"error": f"CREATE_GENERIC_SECRET_OBJECT_FROM_EXISTING: {str(e)}",
|
|
77
|
+
}
|
|
78
|
+
$$
|
|
79
|
+
;
|
|
80
|
+
|
|
81
|
+
grant usage on procedure PLUGIN.CREATE_GENERIC_SECRET_OBJECT_FROM_EXISTING(VARCHAR, VARCHAR, ARRAY, VARCHAR)
|
|
82
|
+
to application role OMNATA_MANAGEMENT;
|
|
@@ -0,0 +1,357 @@
|
|
|
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
|
+
import _snowflake
|
|
49
|
+
from logging import getLogger
|
|
50
|
+
from typing import Any, Dict, List, Literal, Optional
|
|
51
|
+
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
|
52
|
+
from omnata_plugin_runtime.logging import log_exception
|
|
53
|
+
logger = getLogger(__name__)
|
|
54
|
+
|
|
55
|
+
_IDENT_PATTERN = r'^[A-Za-z_][A-Za-z0-9_$.]*$'
|
|
56
|
+
_BARE_IDENT_PATTERN = r'^[A-Za-z_][A-Za-z0-9_$]*$'
|
|
57
|
+
_BARE_IDENT_HINT = "must be an unqualified identifier (no schema prefix; DATA. is prepended automatically)"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class OAuthParameters(BaseModel):
|
|
61
|
+
model_config = ConfigDict(extra='allow')
|
|
62
|
+
oauth_grant: Literal['authorization_code', 'client_credentials'] = 'authorization_code'
|
|
63
|
+
oauth_token_endpoint: Optional[str] = None
|
|
64
|
+
oauth_authorization_endpoint: Optional[str] = None
|
|
65
|
+
oauth_allowed_scopes: Optional[List[str]] = None
|
|
66
|
+
oauth_client_id: Optional[str] = None
|
|
67
|
+
oauth_client_secret: Optional[str] = None
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class SetConnectionParameters(BaseModel):
|
|
71
|
+
model_config = ConfigDict(extra='ignore')
|
|
72
|
+
|
|
73
|
+
external_access_integration_name: str = Field(pattern=_IDENT_PATTERN, max_length=255)
|
|
74
|
+
network_rule_name: str = Field(pattern=_BARE_IDENT_PATTERN, max_length=255, description=_BARE_IDENT_HINT)
|
|
75
|
+
network_addresses: List[str]
|
|
76
|
+
network_rule_name_privatelink: Optional[str] = Field(default=None, pattern=_BARE_IDENT_PATTERN, max_length=255, description=_BARE_IDENT_HINT)
|
|
77
|
+
network_rule_addresses_privatelink: Optional[List[str]] = None
|
|
78
|
+
oauth_security_integration_name: Optional[str] = Field(default=None, pattern=_IDENT_PATTERN, max_length=255)
|
|
79
|
+
oauth_parameters: Optional[OAuthParameters] = None
|
|
80
|
+
oauth_secret_name: Optional[str] = Field(default=None, pattern=_BARE_IDENT_PATTERN, max_length=255, description=_BARE_IDENT_HINT)
|
|
81
|
+
oauth_secret_configuration_name: Optional[str] = Field(default=None, pattern=_IDENT_PATTERN, max_length=255)
|
|
82
|
+
other_secrets_name: str = Field(pattern=_BARE_IDENT_PATTERN, max_length=255, description=_BARE_IDENT_HINT)
|
|
83
|
+
connection_secrets: Optional[Dict[str, Any]] = None
|
|
84
|
+
merge_with_secret_name: Optional[str] = Field(default=None, pattern=_BARE_IDENT_PATTERN, max_length=255, description=_BARE_IDENT_HINT)
|
|
85
|
+
external_access_integration_spec_name: str = Field(pattern=_IDENT_PATTERN, max_length=255)
|
|
86
|
+
security_integration_spec_name: Optional[str] = Field(default=None, pattern=_IDENT_PATTERN, max_length=255)
|
|
87
|
+
connection_slug: str = Field(min_length=1, max_length=255)
|
|
88
|
+
|
|
89
|
+
@model_validator(mode='after')
|
|
90
|
+
def _check_combinations(self):
|
|
91
|
+
if self.network_rule_name_privatelink and self.network_rule_addresses_privatelink is None:
|
|
92
|
+
raise ValueError("network_rule_addresses_privatelink is required when network_rule_name_privatelink is provided")
|
|
93
|
+
if self.oauth_security_integration_name and self.oauth_parameters is None:
|
|
94
|
+
raise ValueError("oauth_parameters (object) is required when oauth_security_integration_name is provided")
|
|
95
|
+
if self.oauth_secret_name and not self.oauth_security_integration_name:
|
|
96
|
+
raise ValueError("oauth_security_integration_name is required when oauth_secret_name is provided")
|
|
97
|
+
if self.oauth_secret_configuration_name and not self.oauth_secret_name:
|
|
98
|
+
raise ValueError("oauth_secret_name is required when oauth_secret_configuration_name is provided")
|
|
99
|
+
if self.security_integration_spec_name and not self.oauth_security_integration_name:
|
|
100
|
+
raise ValueError("oauth_security_integration_name is required when security_integration_spec_name is provided")
|
|
101
|
+
return self
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _esc_sql(s):
|
|
105
|
+
return str(s).replace("'", "''")
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _call_proc(session, sql, params):
|
|
109
|
+
"""Run `CALL PLUGIN.X(...)` and unwrap the {success, data|error} envelope.
|
|
110
|
+
Raises RuntimeError when success is false; returns `data` on success."""
|
|
111
|
+
rows = session.sql(sql, params=params).collect()
|
|
112
|
+
if not rows:
|
|
113
|
+
raise RuntimeError(f"delegate proc returned no rows: {sql}")
|
|
114
|
+
val = rows[0][0]
|
|
115
|
+
if isinstance(val, str):
|
|
116
|
+
try:
|
|
117
|
+
val = json.loads(val)
|
|
118
|
+
except json.JSONDecodeError:
|
|
119
|
+
pass
|
|
120
|
+
if isinstance(val, dict) and val.get('success') is False:
|
|
121
|
+
raise RuntimeError(val.get('error') or f"delegate proc failed: {sql}")
|
|
122
|
+
return val.get('data') if isinstance(val, dict) else val
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def run(session, parameters):
|
|
126
|
+
try:
|
|
127
|
+
params = SetConnectionParameters.model_validate(parameters or {})
|
|
128
|
+
|
|
129
|
+
eai_name = params.external_access_integration_name
|
|
130
|
+
network_rule_name = params.network_rule_name
|
|
131
|
+
network_rule_fqn = f"DATA.{network_rule_name}"
|
|
132
|
+
network_addresses = params.network_addresses
|
|
133
|
+
eai_spec_name = params.external_access_integration_spec_name
|
|
134
|
+
connection_slug = params.connection_slug
|
|
135
|
+
|
|
136
|
+
privatelink_rule_name = params.network_rule_name_privatelink
|
|
137
|
+
privatelink_rule_fqn = f"DATA.{privatelink_rule_name}" if privatelink_rule_name else None
|
|
138
|
+
privatelink_addresses = params.network_rule_addresses_privatelink
|
|
139
|
+
|
|
140
|
+
oauth_si_name = params.oauth_security_integration_name
|
|
141
|
+
oauth_parameters = params.oauth_parameters
|
|
142
|
+
oauth_secret_name = params.oauth_secret_name
|
|
143
|
+
oauth_secret_fqn = f"DATA.{oauth_secret_name}" if oauth_secret_name else None
|
|
144
|
+
oauth_secret_config_name = params.oauth_secret_configuration_name
|
|
145
|
+
si_spec_name = params.security_integration_spec_name
|
|
146
|
+
|
|
147
|
+
other_secrets_name = params.other_secrets_name
|
|
148
|
+
other_secrets_fqn = f"DATA.{other_secrets_name}"
|
|
149
|
+
merge_with_secret_name = params.merge_with_secret_name
|
|
150
|
+
connection_secrets = params.connection_secrets
|
|
151
|
+
|
|
152
|
+
logger.info(f"SET_CONNECTION_OBJECTS: provisioning connection '{connection_slug}' (EAI {eai_name})")
|
|
153
|
+
|
|
154
|
+
# (1) Direct network rule — delegate to CREATE_NETWORK_RULE_OBJECT.
|
|
155
|
+
_call_proc(
|
|
156
|
+
session,
|
|
157
|
+
"call PLUGIN.CREATE_NETWORK_RULE_OBJECT(?, PARSE_JSON(?), ?)",
|
|
158
|
+
[network_rule_fqn, json.dumps(network_addresses), 'HOST_PORT'],
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
# (2) Privatelink network rule — optional.
|
|
162
|
+
if privatelink_rule_fqn:
|
|
163
|
+
_call_proc(
|
|
164
|
+
session,
|
|
165
|
+
"call PLUGIN.CREATE_NETWORK_RULE_OBJECT(?, PARSE_JSON(?), ?)",
|
|
166
|
+
[privatelink_rule_fqn, json.dumps(privatelink_addresses), 'PRIVATE_HOST_PORT'],
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
# (3) OAuth security integration — inline DDL (no existing proc).
|
|
170
|
+
if oauth_si_name:
|
|
171
|
+
op = oauth_parameters
|
|
172
|
+
oauth_grant = op.oauth_grant
|
|
173
|
+
oauth_token_ep = op.oauth_token_endpoint or ''
|
|
174
|
+
oauth_auth_ep = op.oauth_authorization_endpoint or ''
|
|
175
|
+
oauth_scopes = op.oauth_allowed_scopes or []
|
|
176
|
+
oauth_client_id = op.oauth_client_id
|
|
177
|
+
oauth_client_secret = op.oauth_client_secret
|
|
178
|
+
|
|
179
|
+
clauses = [
|
|
180
|
+
"TYPE = API_AUTHENTICATION",
|
|
181
|
+
"AUTH_TYPE = OAUTH2",
|
|
182
|
+
"OAUTH_CLIENT_AUTH_METHOD = CLIENT_SECRET_POST",
|
|
183
|
+
f"OAUTH_GRANT = {oauth_grant.upper()}",
|
|
184
|
+
]
|
|
185
|
+
if oauth_grant == 'authorization_code' and oauth_auth_ep:
|
|
186
|
+
clauses.append(f"OAUTH_AUTHORIZATION_ENDPOINT = '{_esc_sql(oauth_auth_ep)}'")
|
|
187
|
+
if oauth_token_ep:
|
|
188
|
+
clauses.append(f"OAUTH_TOKEN_ENDPOINT = '{_esc_sql(oauth_token_ep)}'")
|
|
189
|
+
if oauth_scopes:
|
|
190
|
+
scope_parts = [f"'{_esc_sql(s)}'" for s in oauth_scopes]
|
|
191
|
+
clauses.append(f"OAUTH_ALLOWED_SCOPES = ({', '.join(scope_parts)})")
|
|
192
|
+
if oauth_client_id is not None:
|
|
193
|
+
clauses.append(f"OAUTH_CLIENT_ID = '{_esc_sql(oauth_client_id)}'")
|
|
194
|
+
if oauth_client_secret is not None:
|
|
195
|
+
clauses.append(f"OAUTH_CLIENT_SECRET = '{_esc_sql(oauth_client_secret)}'")
|
|
196
|
+
clauses.append("ENABLED = TRUE")
|
|
197
|
+
|
|
198
|
+
si_sql = "CREATE OR REPLACE SECURITY INTEGRATION IDENTIFIER(?)\n " + "\n ".join(clauses)
|
|
199
|
+
logger.info(f"Executing SQL: {si_sql}")
|
|
200
|
+
session.sql(si_sql, params=[oauth_si_name]).collect()
|
|
201
|
+
# USAGE on the security integration is required for any APPLICATION_ROLES listed in an
|
|
202
|
+
# ALTER APPLICATION SET CONFIGURATION DEFINITION SECRET_AUTHORIZATION (the referenced
|
|
203
|
+
# OAuth secret's API_AUTHENTICATION points to this integration).
|
|
204
|
+
session.sql(
|
|
205
|
+
"grant usage on integration IDENTIFIER(?) to application role OMNATA_MANAGEMENT",
|
|
206
|
+
params=[oauth_si_name],
|
|
207
|
+
).collect()
|
|
208
|
+
|
|
209
|
+
# (4) OAuth secret — delegate to CREATE_OAUTH_SECRET_OBJECT.
|
|
210
|
+
if oauth_secret_fqn:
|
|
211
|
+
_call_proc(
|
|
212
|
+
session,
|
|
213
|
+
"call PLUGIN.CREATE_OAUTH_SECRET_OBJECT(?, ?)",
|
|
214
|
+
[oauth_secret_fqn, oauth_si_name],
|
|
215
|
+
)
|
|
216
|
+
# CREATE_OAUTH_SECRET_OBJECT grants USAGE only. MODIFY is required on the secret for
|
|
217
|
+
# any APPLICATION_ROLES listed in an ALTER APPLICATION SET CONFIGURATION DEFINITION
|
|
218
|
+
# SECRET_AUTHORIZATION, otherwise the configuration fails with
|
|
219
|
+
# "role ... is missing 'MODIFY' privilege on Secret".
|
|
220
|
+
session.sql(
|
|
221
|
+
"grant modify, usage on secret IDENTIFIER(?) to application role OMNATA_MANAGEMENT",
|
|
222
|
+
params=[oauth_secret_fqn],
|
|
223
|
+
).collect()
|
|
224
|
+
|
|
225
|
+
# (5) Secret authorization configuration.
|
|
226
|
+
# https://docs.snowflake.com/en/developer-guide/native-apps/app-configuration-secret-authorization#step-5-create-the-secret-authorization-configuration
|
|
227
|
+
if oauth_secret_config_name:
|
|
228
|
+
# The secret name alternates its trailing __A / __B suffix on each edit; include that
|
|
229
|
+
# letter in the label so the edit-flow configuration is distinct from the prior one
|
|
230
|
+
# (configuration definition labels must be unique).
|
|
231
|
+
secret_alt = oauth_secret_name[-1]
|
|
232
|
+
cfg_label = f"Authorize OAuth connection: {connection_slug} ({secret_alt})"
|
|
233
|
+
cfg_description = (
|
|
234
|
+
f"Complete the OAuth flow so this application can access the external service "
|
|
235
|
+
f"for connection '{connection_slug}'"
|
|
236
|
+
)
|
|
237
|
+
cfg_sql = (
|
|
238
|
+
f"ALTER APPLICATION SET CONFIGURATION DEFINITION {oauth_secret_config_name}\n"
|
|
239
|
+
f" TYPE = SECRET_AUTHORIZATION\n"
|
|
240
|
+
f" SECRET = {oauth_secret_fqn}\n"
|
|
241
|
+
f" LABEL = '{_esc_sql(cfg_label)}'\n"
|
|
242
|
+
f" DESCRIPTION = '{_esc_sql(cfg_description)}'\n"
|
|
243
|
+
f" APPLICATION_ROLES = (OMNATA_MANAGEMENT)"
|
|
244
|
+
)
|
|
245
|
+
logger.info(f"Executing SQL: {cfg_sql}")
|
|
246
|
+
session.sql(cfg_sql).collect()
|
|
247
|
+
|
|
248
|
+
# (6) Other secrets — read the seed directly via _snowflake.get_generic_secret_string
|
|
249
|
+
# rather than going through RETRIEVE_SECRETS / CREATE_GENERIC_SECRET_OBJECT_FROM_EXISTING.
|
|
250
|
+
# The cross-proc VARCHAR bind path was observed to mangle backslash escapes in JSON
|
|
251
|
+
# payloads; reading the secret in-process avoids any extra hop. The secret must be
|
|
252
|
+
# bound to this proc by CONFIGURE_APIS — the alias is the unqualified name.
|
|
253
|
+
seed_secrets = {}
|
|
254
|
+
if merge_with_secret_name:
|
|
255
|
+
raw = _snowflake.get_generic_secret_string(merge_with_secret_name)
|
|
256
|
+
if raw and len(raw) > 2:
|
|
257
|
+
decoded = json.loads(raw)
|
|
258
|
+
if isinstance(decoded, dict):
|
|
259
|
+
seed_secrets = decoded
|
|
260
|
+
if connection_secrets:
|
|
261
|
+
seed_secrets.update(connection_secrets)
|
|
262
|
+
_call_proc(
|
|
263
|
+
session,
|
|
264
|
+
"call PLUGIN.CREATE_GENERIC_SECRET_OBJECT(?, ?)",
|
|
265
|
+
[other_secrets_fqn, json.dumps(seed_secrets)],
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
# (7) External Access Integration.
|
|
269
|
+
eai_rules = [network_rule_fqn]
|
|
270
|
+
if privatelink_rule_fqn:
|
|
271
|
+
eai_rules.append(privatelink_rule_fqn)
|
|
272
|
+
eai_secrets = [other_secrets_fqn]
|
|
273
|
+
if oauth_secret_fqn:
|
|
274
|
+
eai_secrets.append(oauth_secret_fqn)
|
|
275
|
+
|
|
276
|
+
eai_rules_sql = ', '.join(eai_rules)
|
|
277
|
+
eai_secrets_clause = ''
|
|
278
|
+
if eai_secrets:
|
|
279
|
+
eai_secrets_clause = f"\n ALLOWED_AUTHENTICATION_SECRETS = ({', '.join(eai_secrets)})"
|
|
280
|
+
eai_sql = (
|
|
281
|
+
f"CREATE OR REPLACE EXTERNAL ACCESS INTEGRATION IDENTIFIER(?)\n"
|
|
282
|
+
f" ALLOWED_NETWORK_RULES = ({eai_rules_sql})"
|
|
283
|
+
f"{eai_secrets_clause}\n"
|
|
284
|
+
f" ENABLED = FALSE"
|
|
285
|
+
)
|
|
286
|
+
logger.info(f"Executing SQL: {eai_sql}")
|
|
287
|
+
session.sql(eai_sql, params=[eai_name]).collect()
|
|
288
|
+
# Grant USAGE so the sync engine (via OMNATA_MANAGEMENT) can see/use the EAI.
|
|
289
|
+
session.sql(
|
|
290
|
+
"grant usage on integration IDENTIFIER(?) to application role OMNATA_MANAGEMENT",
|
|
291
|
+
params=[eai_name],
|
|
292
|
+
).collect()
|
|
293
|
+
|
|
294
|
+
# (8) EAI application specification — delegate to SET_EAI_SPECIFICATION.
|
|
295
|
+
# Unlike for security integrations/configurations, this specification does not need to alternate when editing as it's not bound to any objects
|
|
296
|
+
eai_spec_props = {
|
|
297
|
+
"LABEL": f"External access for connection: {connection_slug}",
|
|
298
|
+
"DESCRIPTION": f"Allows this app to communicate with external services for connection '{connection_slug}'",
|
|
299
|
+
"HOST_PORTS": network_addresses,
|
|
300
|
+
}
|
|
301
|
+
if privatelink_addresses:
|
|
302
|
+
eai_spec_props["PRIVATE_HOST_PORTS"] = privatelink_addresses
|
|
303
|
+
_call_proc(
|
|
304
|
+
session,
|
|
305
|
+
"call PLUGIN.SET_EAI_SPECIFICATION(?, PARSE_JSON(?))",
|
|
306
|
+
[eai_spec_name, json.dumps(eai_spec_props)],
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
# (9) SI application specification — delegate to SET_SI_SPECIFICATION (OAuth only).
|
|
310
|
+
if si_spec_name:
|
|
311
|
+
op2 = oauth_parameters
|
|
312
|
+
grant_uc = op2.oauth_grant.upper()
|
|
313
|
+
# Include the alternating suffix letter so the edit-flow spec gets a distinct label.
|
|
314
|
+
si_spec_alt = si_spec_name[-1]
|
|
315
|
+
si_spec_props = {
|
|
316
|
+
"LABEL": f"OAuth integration for connection: {connection_slug} ({si_spec_alt})",
|
|
317
|
+
"DESCRIPTION": f"Allows this app to use an OAuth security integration for connection '{connection_slug}'",
|
|
318
|
+
"OAUTH_TYPE": grant_uc,
|
|
319
|
+
}
|
|
320
|
+
if op2.oauth_token_endpoint:
|
|
321
|
+
si_spec_props["OAUTH_TOKEN_ENDPOINT"] = op2.oauth_token_endpoint
|
|
322
|
+
if grant_uc == 'AUTHORIZATION_CODE' and op2.oauth_authorization_endpoint:
|
|
323
|
+
si_spec_props["OAUTH_AUTHORIZATION_ENDPOINT"] = op2.oauth_authorization_endpoint
|
|
324
|
+
if op2.oauth_allowed_scopes:
|
|
325
|
+
si_spec_props["OAUTH_ALLOWED_SCOPES"] = op2.oauth_allowed_scopes
|
|
326
|
+
_call_proc(
|
|
327
|
+
session,
|
|
328
|
+
"call PLUGIN.SET_SI_SPECIFICATION(?, PARSE_JSON(?))",
|
|
329
|
+
[si_spec_name, json.dumps(si_spec_props)],
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
return {
|
|
333
|
+
"success": True,
|
|
334
|
+
"data": {
|
|
335
|
+
"connection_slug": connection_slug,
|
|
336
|
+
"external_access_integration_name": eai_name,
|
|
337
|
+
"network_rule_name": network_rule_fqn,
|
|
338
|
+
"network_rule_name_privatelink": privatelink_rule_fqn,
|
|
339
|
+
"oauth_security_integration_name": oauth_si_name,
|
|
340
|
+
"oauth_secret_name": oauth_secret_fqn,
|
|
341
|
+
"oauth_secret_configuration_name": oauth_secret_config_name,
|
|
342
|
+
"other_secrets_name": other_secrets_fqn,
|
|
343
|
+
"external_access_integration_spec_name": eai_spec_name,
|
|
344
|
+
"security_integration_spec_name": si_spec_name,
|
|
345
|
+
},
|
|
346
|
+
}
|
|
347
|
+
except Exception as exception:
|
|
348
|
+
log_exception(exception, logger)
|
|
349
|
+
return {
|
|
350
|
+
"success": False,
|
|
351
|
+
"error": f"SET_CONNECTION_OBJECTS: {str(exception)}",
|
|
352
|
+
}
|
|
353
|
+
$$
|
|
354
|
+
;
|
|
355
|
+
|
|
356
|
+
grant usage on procedure PLUGIN.SET_CONNECTION_OBJECTS(OBJECT)
|
|
357
|
+
to application role OMNATA_MANAGEMENT;
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
create or replace procedure PLUGIN.CREATE_GENERIC_SECRET_OBJECT(SECRET_NAME VARCHAR,SECRET_CONTENTS VARCHAR)
|
|
2
|
-
returns object
|
|
3
|
-
language javascript
|
|
4
|
-
COMMENT = $$
|
|
5
|
-
Creates a generic secret object.
|
|
6
|
-
$$
|
|
7
|
-
execute as owner
|
|
8
|
-
as
|
|
9
|
-
$$
|
|
10
|
-
try{
|
|
11
|
-
snowflake.createStatement( {
|
|
12
|
-
sqlText: `CREATE OR REPLACE SECRET IDENTIFIER(?) TYPE = GENERIC_STRING SECRET_STRING = ?`,
|
|
13
|
-
binds:[SECRET_NAME,SECRET_CONTENTS]
|
|
14
|
-
} ).execute();
|
|
15
|
-
snowflake.createStatement( {
|
|
16
|
-
sqlText: `grant usage on secret IDENTIFIER(?) to application role OMNATA_MANAGEMENT`,
|
|
17
|
-
binds:[SECRET_NAME]
|
|
18
|
-
} ).execute();
|
|
19
|
-
return {
|
|
20
|
-
"success": true,
|
|
21
|
-
"data": null
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
catch(e){
|
|
25
|
-
return {
|
|
26
|
-
"success": false,
|
|
27
|
-
"error": `CREATE_GENERIC_SECRET_OBJECT: ${String(e)}`
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
$$
|
|
31
|
-
;
|
|
32
|
-
|
|
33
|
-
grant usage on procedure PLUGIN.CREATE_GENERIC_SECRET_OBJECT(VARCHAR,VARCHAR)
|
|
34
|
-
to application role OMNATA_MANAGEMENT;
|
|
35
|
-
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
create or replace procedure PLUGIN.CREATE_GENERIC_SECRET_OBJECT_FROM_EXISTING(SECRET_NAME VARCHAR,SECRET_TO_CLONE VARCHAR,KEYS_TO_INCLUDE ARRAY)
|
|
2
|
-
returns object
|
|
3
|
-
language javascript
|
|
4
|
-
COMMENT = $$
|
|
5
|
-
Creates a generic secret object, using an existing object as a starting point.
|
|
6
|
-
$$
|
|
7
|
-
execute as owner
|
|
8
|
-
as
|
|
9
|
-
$$
|
|
10
|
-
try{
|
|
11
|
-
// retrieve the existing secrets
|
|
12
|
-
var results = snowflake.createStatement( {
|
|
13
|
-
sqlText: `call RETRIEVE_SECRETS(null,?)`,
|
|
14
|
-
binds:[SECRET_TO_CLONE]
|
|
15
|
-
} ).execute();
|
|
16
|
-
results.next();
|
|
17
|
-
var procResult = results.getColumnValue(1);
|
|
18
|
-
if (procResult.success===false){
|
|
19
|
-
throw procResult.error;
|
|
20
|
-
}
|
|
21
|
-
var existingSecrets = procResult.data;
|
|
22
|
-
if (KEYS_TO_INCLUDE != null){
|
|
23
|
-
// pick out the specific keys from the KEYS_TO_INCLUDE array
|
|
24
|
-
existingSecrets = KEYS_TO_INCLUDE.reduce((acc,key) => {
|
|
25
|
-
acc[key] = existingSecrets[key];
|
|
26
|
-
return acc;
|
|
27
|
-
}, {})
|
|
28
|
-
}
|
|
29
|
-
// create the secret object by calling the regular proc
|
|
30
|
-
var results = snowflake.createStatement( {
|
|
31
|
-
sqlText: `call CREATE_GENERIC_SECRET_OBJECT(?,?)`,
|
|
32
|
-
binds:[SECRET_NAME,JSON.stringify(existingSecrets)]
|
|
33
|
-
} ).execute();
|
|
34
|
-
results.next();
|
|
35
|
-
var procResult = results.getColumnValue(1);
|
|
36
|
-
if (procResult.success===false){
|
|
37
|
-
throw procResult.error;
|
|
38
|
-
}
|
|
39
|
-
return {
|
|
40
|
-
"success": true,
|
|
41
|
-
"data": null
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
catch(e){
|
|
45
|
-
return {
|
|
46
|
-
"success": false,
|
|
47
|
-
"error": `CREATE_GENERIC_SECRET_OBJECT_FROM_EXISTING: ${String(e)}`
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
$$
|
|
51
|
-
;
|
|
52
|
-
|
|
53
|
-
grant usage on procedure PLUGIN.CREATE_GENERIC_SECRET_OBJECT_FROM_EXISTING(VARCHAR,VARCHAR,ARRAY)
|
|
54
|
-
to application role OMNATA_MANAGEMENT;
|
|
55
|
-
|