omnata-plugin-devkit 0.13.0a159__tar.gz → 0.13.1__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.0a159 → omnata_plugin_devkit-0.13.1}/PKG-INFO +1 -1
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/pyproject.toml +1 -1
- omnata_plugin_devkit-0.13.1/src/omnata_plugin_devkit/jinja_templates/CHECK_CONNECTION_PROGRESS.sql.jinja +112 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/CONFIGURE_APIS.sql.jinja +5 -0
- omnata_plugin_devkit-0.13.1/src/omnata_plugin_devkit/jinja_templates/GET_MISSING_APP_PRIVILEGES.sql.jinja +35 -0
- omnata_plugin_devkit-0.13.1/src/omnata_plugin_devkit/jinja_templates/LIST_APP_SPECIFICATIONS.sql.jinja +40 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/NETWORK_ADDRESSES.sql.jinja +2 -1
- omnata_plugin_devkit-0.13.1/src/omnata_plugin_devkit/jinja_templates/SET_CONNECTION_OBJECTS.sql.jinja +357 -0
- omnata_plugin_devkit-0.13.1/src/omnata_plugin_devkit/jinja_templates/SET_EAI_ENABLED.sql.jinja +43 -0
- omnata_plugin_devkit-0.13.1/src/omnata_plugin_devkit/jinja_templates/SET_EAI_SPECIFICATION.sql.jinja +61 -0
- omnata_plugin_devkit-0.13.1/src/omnata_plugin_devkit/jinja_templates/SET_SI_SPECIFICATION.sql.jinja +69 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/manifest.yml.jinja +5 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/native_app_packaging.py +0 -2
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/plugin_uploader.py +10 -1
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/LICENSE +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/README.md +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/__init__.py +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/airbyte_wrapper.py +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/cli/__init__.py +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/development.ipynb +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/development_session.py +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/initialiser.py +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/API_LIMITS.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/ASSIGN_OUTBOUND_TARGET_TYPE.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/CONFIGURATION_FORM.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/CONNECTION_FORM.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/CONNECTION_TEST.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/CONSTRUCT_FORM_OPTION.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/CREATE_BILLING_EVENTS.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/CREATE_GENERIC_SECRET_OBJECT.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/CREATE_GENERIC_SECRET_OBJECT_FROM_EXISTING.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/CREATE_NETWORK_RULE_OBJECT.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/CREATE_NETWORK_RULE_OBJECT_FROM_EXISTING.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/CREATE_OAUTH_SECRET_OBJECT.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/DROP_NETWORK_RULES.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/DROP_SECRETS.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/FETCH_CONNECTIONS.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/FETCH_SYNCS.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/FETCH_SYNC_BRANCHES.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/INBOUND_LIST_STREAMS.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/LIST_STAGES.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/NGROK_POST_TUNNEL_FIELDS.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/OUTBOUND_RECORD_VALIDATOR.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/PENDING_API_CONFIGURATION.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/POST_INSTALL_ACTIONS.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/RENAME_CONNECTION_METHODS.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/RETRIEVE_NETWORK_RULE_OBJECT.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/RETRIEVE_SECRETS.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/RETRIEVE_SECRETS_UDF.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/SYNC.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/TEST_CALLBACK.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/TEST_OAUTH_TOKEN_EXISTS.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/TUNNEL_TEST.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/UPDATE_API_CONFIGURATION.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/UPDATE_GENERIC_SECRET_OBJECT.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/UPDATE_GENERIC_SECRET_OBJECT_OLD.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/UPDATE_NETWORK_RULE_OBJECT.sql.jinja +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/plugin_registration.py +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/plugin_template/icon.svg +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/plugin_template/plugin.py +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/plugin_template/plugin_development.ipynb +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/plugin_template/requirements.txt +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/snowcli/__init__.py +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/snowcli/cli/__init__.py +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/snowcli/cli/api/__init__.py +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/snowcli/cli/api/config.py +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/snowcli/cli/api/constants.py +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/snowcli/cli/api/exceptions.py +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/snowcli/cli/api/secure_path.py +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/snowcli/cli/api/secure_utils.py +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/snowcli/cli/api/utils/__init__.py +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/snowcli/cli/api/utils/types.py +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/snowcli/cli/app/__init__.py +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/snowcli/cli/app/snow_connector.py +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/snowcli/cli/plugins/__init__.py +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/snowcli/cli/plugins/snowpark/__init__.py +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/snowcli/cli/plugins/snowpark/models.py +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/snowcli/cli/plugins/snowpark/package/__init__.py +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/snowcli/cli/plugins/snowpark/package_utils.py +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/snowcli/cli/plugins/snowpark/snowpark_shared.py +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/snowcli/cli/plugins/snowpark/venv.py +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/snowcli/cli/plugins/snowpark/zipper.py +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/snowcli/cli/templates/environment.yml.jinja +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/streamlit/plugin_configuration.py +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/test_step_definitions.py +0 -0
- {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/utils.py +0 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
create or replace procedure PLUGIN.CHECK_CONNECTION_PROGRESS(PARAMETERS OBJECT)
|
|
2
|
+
returns object
|
|
3
|
+
language javascript
|
|
4
|
+
COMMENT = $$
|
|
5
|
+
Reports the consumer-approval status of the application specifications and configuration
|
|
6
|
+
definitions associated with a connection. Input keys (all optional except the first):
|
|
7
|
+
external_access_integration_spec_name (required)
|
|
8
|
+
security_integration_spec_name (optional; omitted => null in the response)
|
|
9
|
+
oauth_secret_configuration_name (optional; omitted => null in the response)
|
|
10
|
+
Returns:
|
|
11
|
+
{
|
|
12
|
+
latest_eai_spec_revision_approved: <bool>,
|
|
13
|
+
latest_si_spec_revision_approved: <bool|null>,
|
|
14
|
+
oauth_secret_configuration_done: <bool|null>
|
|
15
|
+
}
|
|
16
|
+
A field is false (not null) when the name was provided but the object does not exist yet
|
|
17
|
+
or is not in APPROVED state.
|
|
18
|
+
$$
|
|
19
|
+
execute as owner
|
|
20
|
+
as
|
|
21
|
+
$$
|
|
22
|
+
try{
|
|
23
|
+
var p = PARAMETERS || {};
|
|
24
|
+
var eaiSpecName = p.external_access_integration_spec_name || null;
|
|
25
|
+
var siSpecName = p.security_integration_spec_name || null;
|
|
26
|
+
var cfgName = p.oauth_secret_configuration_name || null;
|
|
27
|
+
|
|
28
|
+
if (!eaiSpecName){
|
|
29
|
+
throw `external_access_integration_spec_name is required`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Collect the status of every specification in this application.
|
|
33
|
+
var specStatus = {};
|
|
34
|
+
try {
|
|
35
|
+
var rs = snowflake.createStatement({
|
|
36
|
+
sqlText: `SHOW SPECIFICATIONS
|
|
37
|
+
->> select * from $1
|
|
38
|
+
qualify row_number() over (partition by "name" order by "sequence_number" desc) = 1`,
|
|
39
|
+
binds: []
|
|
40
|
+
}).execute();
|
|
41
|
+
var colCount = rs.getColumnCount();
|
|
42
|
+
var colIdx = {};
|
|
43
|
+
for (var i = 1; i <= colCount; i++){
|
|
44
|
+
colIdx[rs.getColumnName(i).toLowerCase()] = i;
|
|
45
|
+
}
|
|
46
|
+
while (rs.next()){
|
|
47
|
+
var nm = rs.getColumnValue(colIdx['name']);
|
|
48
|
+
var st = rs.getColumnValue(colIdx['status']);
|
|
49
|
+
specStatus[String(nm).toUpperCase()] = st;
|
|
50
|
+
}
|
|
51
|
+
} catch(e) {
|
|
52
|
+
snowflake.log("warn", `CHECK_CONNECTION_PROGRESS: could not list specifications: ${String(e)}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function specApproved(name){
|
|
56
|
+
if (!name) return null;
|
|
57
|
+
var s = specStatus[String(name).toUpperCase()];
|
|
58
|
+
if (s === undefined) return false;
|
|
59
|
+
return String(s).toUpperCase() === 'APPROVED';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
var latestEaiApproved = specApproved(eaiSpecName);
|
|
63
|
+
var latestSiApproved = specApproved(siSpecName);
|
|
64
|
+
|
|
65
|
+
// Secret authorization configuration status.
|
|
66
|
+
// https://docs.snowflake.com/en/developer-guide/native-apps/app-configuration-secret-authorization
|
|
67
|
+
var cfgDone = null;
|
|
68
|
+
if (cfgName){
|
|
69
|
+
cfgDone = false;
|
|
70
|
+
try {
|
|
71
|
+
var cfgRs = snowflake.createStatement({
|
|
72
|
+
sqlText: `SHOW CONFIGURATIONS`,
|
|
73
|
+
binds: []
|
|
74
|
+
}).execute();
|
|
75
|
+
var cfgColCount = cfgRs.getColumnCount();
|
|
76
|
+
var cfgColIdx = {};
|
|
77
|
+
for (var j = 1; j <= cfgColCount; j++){
|
|
78
|
+
cfgColIdx[cfgRs.getColumnName(j).toLowerCase()] = j;
|
|
79
|
+
}
|
|
80
|
+
while (cfgRs.next()){
|
|
81
|
+
var cfgRowName = cfgRs.getColumnValue(cfgColIdx['name']);
|
|
82
|
+
if (String(cfgRowName).toUpperCase() === String(cfgName).toUpperCase()){
|
|
83
|
+
var cfgRowStatus = cfgRs.getColumnValue(cfgColIdx['status']);
|
|
84
|
+
cfgDone = String(cfgRowStatus).toUpperCase() === 'DONE';
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
} catch(e) {
|
|
89
|
+
snowflake.log("warn", `CHECK_CONNECTION_PROGRESS: could not list configuration definitions: ${String(e)}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
"success": true,
|
|
95
|
+
"data": {
|
|
96
|
+
"latest_eai_spec_revision_approved": latestEaiApproved,
|
|
97
|
+
"latest_si_spec_revision_approved": latestSiApproved,
|
|
98
|
+
"oauth_secret_configuration_done": cfgDone
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
catch(e){
|
|
103
|
+
return {
|
|
104
|
+
"success": false,
|
|
105
|
+
"error": `CHECK_CONNECTION_PROGRESS: ${String(e)}`
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
$$
|
|
109
|
+
;
|
|
110
|
+
|
|
111
|
+
grant usage on procedure PLUGIN.CHECK_CONNECTION_PROGRESS(OBJECT)
|
|
112
|
+
to application role OMNATA_MANAGEMENT;
|
|
@@ -142,6 +142,11 @@ try{
|
|
|
142
142
|
var candidateEais = [];
|
|
143
143
|
while (showEaiResults.next()) {
|
|
144
144
|
var eaiName = showEaiResults.getColumnValue(1);
|
|
145
|
+
var eaiEnabled = showEaiResults.getColumnValue(4);
|
|
146
|
+
if (eaiEnabled === false || String(eaiEnabled).toLowerCase() === 'false'){
|
|
147
|
+
snowflake.log("warn", `Excluding EAI ${eaiName} because it is disabled (enabled=false)`);
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
145
150
|
if (eaiName.includes('__') || registeredV1EaiNames.has(eaiName)) {
|
|
146
151
|
if (registeredV1EaiNames.has(eaiName)){
|
|
147
152
|
snowflake.log("warn",`Legacy EAI still in use: ${eaiName}`)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
create or replace procedure PLUGIN.GET_MISSING_APP_PRIVILEGES()
|
|
2
|
+
returns object
|
|
3
|
+
language python
|
|
4
|
+
RUNTIME_VERSION = '3.10'
|
|
5
|
+
PACKAGES = ('snowflake-snowpark-python','snowflake-native-apps-permission')
|
|
6
|
+
HANDLER = 'run'
|
|
7
|
+
COMMENT = $$
|
|
8
|
+
Returns the list of account-level privileges this application has requested but the consumer
|
|
9
|
+
has not yet granted. Wraps snowflake.permissions.get_missing_account_privileges.
|
|
10
|
+
$$
|
|
11
|
+
execute as owner
|
|
12
|
+
as
|
|
13
|
+
$$
|
|
14
|
+
from snowflake.permissions import get_missing_account_privileges
|
|
15
|
+
|
|
16
|
+
def run(session):
|
|
17
|
+
try:
|
|
18
|
+
missing = get_missing_account_privileges([
|
|
19
|
+
'CREATE EXTERNAL ACCESS INTEGRATION',
|
|
20
|
+
'CREATE SECURITY INTEGRATION'
|
|
21
|
+
])
|
|
22
|
+
return {
|
|
23
|
+
"success": True,
|
|
24
|
+
"data": list(missing) if missing is not None else []
|
|
25
|
+
}
|
|
26
|
+
except Exception as exception:
|
|
27
|
+
return {
|
|
28
|
+
"success": False,
|
|
29
|
+
"error": f"GET_MISSING_APP_PRIVILEGES: {str(exception)}"
|
|
30
|
+
}
|
|
31
|
+
$$
|
|
32
|
+
;
|
|
33
|
+
|
|
34
|
+
grant usage on procedure PLUGIN.GET_MISSING_APP_PRIVILEGES()
|
|
35
|
+
to application role OMNATA_MANAGEMENT;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
create or replace procedure PLUGIN.LIST_APP_SPECIFICATIONS()
|
|
2
|
+
returns object
|
|
3
|
+
language javascript
|
|
4
|
+
COMMENT = $$
|
|
5
|
+
Runs SHOW SPECIFICATIONS and returns the rows as an array of objects keyed by column name.
|
|
6
|
+
$$
|
|
7
|
+
execute as owner
|
|
8
|
+
as
|
|
9
|
+
$$
|
|
10
|
+
try{
|
|
11
|
+
var rs = snowflake.createStatement({ sqlText: `show specifications`, binds: [] }).execute();
|
|
12
|
+
var colCount = rs.getColumnCount();
|
|
13
|
+
var colNames = [];
|
|
14
|
+
for (var i = 1; i <= colCount; i++) {
|
|
15
|
+
colNames.push(rs.getColumnName(i));
|
|
16
|
+
}
|
|
17
|
+
var results = [];
|
|
18
|
+
while (rs.next()) {
|
|
19
|
+
var row = {};
|
|
20
|
+
for (var i = 1; i <= colCount; i++) {
|
|
21
|
+
row[colNames[i - 1]] = rs.getColumnValue(i);
|
|
22
|
+
}
|
|
23
|
+
results.push(row);
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
"success": true,
|
|
27
|
+
"data": results
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
catch(e){
|
|
31
|
+
return {
|
|
32
|
+
"success": false,
|
|
33
|
+
"error": `LIST_APP_SPECIFICATIONS: ${String(e)}`
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
$$
|
|
37
|
+
;
|
|
38
|
+
|
|
39
|
+
grant usage on procedure PLUGIN.LIST_APP_SPECIFICATIONS()
|
|
40
|
+
to application role OMNATA_MANAGEMENT;
|
|
@@ -16,6 +16,7 @@ execute as owner
|
|
|
16
16
|
as
|
|
17
17
|
$$
|
|
18
18
|
from logging import getLogger
|
|
19
|
+
from pydantic_core import to_jsonable_python
|
|
19
20
|
from omnata_plugin_runtime.logging import log_exception
|
|
20
21
|
import json
|
|
21
22
|
logger = getLogger(__name__)
|
|
@@ -33,7 +34,7 @@ def run(session,connectivity_option,method,connection_parameters):
|
|
|
33
34
|
logger.info(f'result from plugin: {result}')
|
|
34
35
|
return {
|
|
35
36
|
"success": True,
|
|
36
|
-
"data": result
|
|
37
|
+
"data": to_jsonable_python(result)
|
|
37
38
|
}
|
|
38
39
|
except Exception as exception:
|
|
39
40
|
log_exception(exception,logger)
|
|
@@ -0,0 +1,357 @@
|
|
|
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
|
+
function callProc(sql, binds){
|
|
81
|
+
var rs = snowflake.createStatement({sqlText: sql, binds: binds}).execute();
|
|
82
|
+
rs.next();
|
|
83
|
+
var r = rs.getColumnValue(1);
|
|
84
|
+
if (r && r.success === false){ throw r.error || `delegate proc failed: ${sql}`; }
|
|
85
|
+
return r;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Required parameters.
|
|
89
|
+
var eaiName = requireIdent('external_access_integration_name');
|
|
90
|
+
var networkRuleName = requireBareIdent('network_rule_name');
|
|
91
|
+
var networkRuleFqn = `DATA.${networkRuleName}`;
|
|
92
|
+
var networkAddresses = requireArrayOfStrings('network_addresses');
|
|
93
|
+
var eaiSpecName = requireIdent('external_access_integration_spec_name');
|
|
94
|
+
var connectionSlug = p.connection_slug;
|
|
95
|
+
if (typeof connectionSlug !== 'string' || connectionSlug.length === 0 || connectionSlug.length > 255){
|
|
96
|
+
throw `parameter 'connection_slug' is required and must be a non-empty string`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Optional privatelink network rule.
|
|
100
|
+
var privatelinkRuleName = optBareIdent('network_rule_name_privatelink');
|
|
101
|
+
var privatelinkRuleFqn = privatelinkRuleName ? `DATA.${privatelinkRuleName}` : null;
|
|
102
|
+
var privatelinkAddresses = optArrayOfStrings('network_rule_addresses_privatelink');
|
|
103
|
+
if (privatelinkRuleName && privatelinkAddresses === null){
|
|
104
|
+
throw `network_rule_addresses_privatelink is required when network_rule_name_privatelink is provided`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Optional OAuth bundle.
|
|
108
|
+
var oauthSiName = optIdent('oauth_security_integration_name');
|
|
109
|
+
var oauthSecretName = optBareIdent('oauth_secret_name');
|
|
110
|
+
var oauthSecretFqn = oauthSecretName ? `DATA.${oauthSecretName}` : null;
|
|
111
|
+
var oauthSecretConfigName = optIdent('oauth_secret_configuration_name');
|
|
112
|
+
var siSpecName = optIdent('security_integration_spec_name');
|
|
113
|
+
var oauthParameters = (typeof p.oauth_parameters === 'object' && p.oauth_parameters !== null) ? p.oauth_parameters : null;
|
|
114
|
+
if (oauthSiName && !oauthParameters){
|
|
115
|
+
throw `oauth_parameters (object) is required when oauth_security_integration_name is provided`;
|
|
116
|
+
}
|
|
117
|
+
if (oauthSecretName && !oauthSiName){
|
|
118
|
+
throw `oauth_security_integration_name is required when oauth_secret_name is provided`;
|
|
119
|
+
}
|
|
120
|
+
if (oauthSecretConfigName && !oauthSecretName){
|
|
121
|
+
throw `oauth_secret_name is required when oauth_secret_configuration_name is provided`;
|
|
122
|
+
}
|
|
123
|
+
if (siSpecName && !oauthSiName){
|
|
124
|
+
throw `oauth_security_integration_name is required when security_integration_spec_name is provided`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Other-secrets (mandatory) plus optional seed/merge inputs.
|
|
128
|
+
var otherSecretsName = requireBareIdent('other_secrets_name');
|
|
129
|
+
var otherSecretsFqn = `DATA.${otherSecretsName}`;
|
|
130
|
+
var mergeWithSecretName = optBareIdent('merge_with_secret_name');
|
|
131
|
+
var connectionSecrets = (typeof p.connection_secrets === 'object' && p.connection_secrets !== null && !Array.isArray(p.connection_secrets))
|
|
132
|
+
? p.connection_secrets
|
|
133
|
+
: null;
|
|
134
|
+
if (p.connection_secrets != null && connectionSecrets === null){
|
|
135
|
+
throw `connection_secrets must be an object`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
snowflake.log("info", `SET_CONNECTION_OBJECTS: provisioning connection '${connectionSlug}' (EAI ${eaiName})`);
|
|
139
|
+
|
|
140
|
+
// (1) Direct network rule — delegate to CREATE_NETWORK_RULE_OBJECT.
|
|
141
|
+
callProc(
|
|
142
|
+
`call PLUGIN.CREATE_NETWORK_RULE_OBJECT(?, PARSE_JSON(?), ?)`,
|
|
143
|
+
[networkRuleFqn, JSON.stringify(networkAddresses), 'HOST_PORT']
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
// (2) Privatelink network rule — optional.
|
|
147
|
+
if (privatelinkRuleFqn){
|
|
148
|
+
callProc(
|
|
149
|
+
`call PLUGIN.CREATE_NETWORK_RULE_OBJECT(?, PARSE_JSON(?), ?)`,
|
|
150
|
+
[privatelinkRuleFqn, JSON.stringify(privatelinkAddresses), 'PRIVATE_HOST_PORT']
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// (3) OAuth security integration — inline DDL (no existing proc).
|
|
155
|
+
if (oauthSiName){
|
|
156
|
+
var op = oauthParameters;
|
|
157
|
+
var oauthGrant = String(op.oauth_grant || 'authorization_code').toLowerCase();
|
|
158
|
+
var oauthTokenEp = op.oauth_token_endpoint || '';
|
|
159
|
+
var oauthAuthEp = op.oauth_authorization_endpoint || '';
|
|
160
|
+
var oauthScopes = Array.isArray(op.oauth_allowed_scopes) ? op.oauth_allowed_scopes : [];
|
|
161
|
+
var oauthClientId = op.oauth_client_id;
|
|
162
|
+
var oauthClientSecret = op.oauth_client_secret;
|
|
163
|
+
|
|
164
|
+
if (oauthGrant !== 'authorization_code' && oauthGrant !== 'client_credentials'){
|
|
165
|
+
throw `oauth_parameters.oauth_grant must be 'authorization_code' or 'client_credentials'`;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
var clauses = [
|
|
169
|
+
"TYPE = API_AUTHENTICATION",
|
|
170
|
+
"AUTH_TYPE = OAUTH2",
|
|
171
|
+
"OAUTH_CLIENT_AUTH_METHOD = CLIENT_SECRET_POST",
|
|
172
|
+
`OAUTH_GRANT = ${oauthGrant.toUpperCase()}`
|
|
173
|
+
];
|
|
174
|
+
if (oauthGrant === 'authorization_code' && oauthAuthEp){
|
|
175
|
+
clauses.push(`OAUTH_AUTHORIZATION_ENDPOINT = '${esc(oauthAuthEp)}'`);
|
|
176
|
+
}
|
|
177
|
+
if (oauthTokenEp){
|
|
178
|
+
clauses.push(`OAUTH_TOKEN_ENDPOINT = '${esc(oauthTokenEp)}'`);
|
|
179
|
+
}
|
|
180
|
+
if (oauthScopes.length > 0){
|
|
181
|
+
var scopesList = oauthScopes.map(function(s){
|
|
182
|
+
if (typeof s !== 'string'){ throw `oauth_parameters.oauth_allowed_scopes entries must be strings`; }
|
|
183
|
+
return `'${esc(s)}'`;
|
|
184
|
+
}).join(', ');
|
|
185
|
+
clauses.push(`OAUTH_ALLOWED_SCOPES = (${scopesList})`);
|
|
186
|
+
}
|
|
187
|
+
if (oauthClientId != null){
|
|
188
|
+
if (typeof oauthClientId !== 'string'){ throw `oauth_parameters.oauth_client_id must be a string`; }
|
|
189
|
+
clauses.push(`OAUTH_CLIENT_ID = '${esc(oauthClientId)}'`);
|
|
190
|
+
}
|
|
191
|
+
if (oauthClientSecret != null){
|
|
192
|
+
if (typeof oauthClientSecret !== 'string'){ throw `oauth_parameters.oauth_client_secret must be a string`; }
|
|
193
|
+
clauses.push(`OAUTH_CLIENT_SECRET = '${esc(oauthClientSecret)}'`);
|
|
194
|
+
}
|
|
195
|
+
clauses.push("ENABLED = TRUE");
|
|
196
|
+
|
|
197
|
+
var siSql = `CREATE OR REPLACE SECURITY INTEGRATION IDENTIFIER(?)\n ` + clauses.join('\n ');
|
|
198
|
+
snowflake.log("info", `Executing SQL: ${siSql}`);
|
|
199
|
+
snowflake.createStatement({sqlText: siSql, binds: [oauthSiName]}).execute();
|
|
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
|
+
snowflake.createStatement({
|
|
204
|
+
sqlText: `grant usage on integration IDENTIFIER(?) to application role OMNATA_MANAGEMENT`,
|
|
205
|
+
binds: [oauthSiName]
|
|
206
|
+
}).execute();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// (4) OAuth secret — delegate to CREATE_OAUTH_SECRET_OBJECT.
|
|
210
|
+
if (oauthSecretFqn){
|
|
211
|
+
callProc(`call PLUGIN.CREATE_OAUTH_SECRET_OBJECT(?, ?)`, [oauthSecretFqn, oauthSiName]);
|
|
212
|
+
// CREATE_OAUTH_SECRET_OBJECT grants USAGE only. MODIFY is required on the secret for
|
|
213
|
+
// any APPLICATION_ROLES listed in an ALTER APPLICATION SET CONFIGURATION DEFINITION
|
|
214
|
+
// SECRET_AUTHORIZATION, otherwise the configuration fails with
|
|
215
|
+
// "role ... is missing 'MODIFY' privilege on Secret".
|
|
216
|
+
snowflake.createStatement({
|
|
217
|
+
sqlText: `grant modify, usage on secret IDENTIFIER(?) to application role OMNATA_MANAGEMENT`,
|
|
218
|
+
binds: [oauthSecretFqn]
|
|
219
|
+
}).execute();
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// (5) Secret authorization configuration.
|
|
223
|
+
// TODO: verify exact DDL and keywords against Snowflake docs; WebFetch was unavailable during authoring.
|
|
224
|
+
// https://docs.snowflake.com/en/developer-guide/native-apps/app-configuration-secret-authorization#step-5-create-the-secret-authorization-configuration
|
|
225
|
+
if (oauthSecretConfigName){
|
|
226
|
+
// The secret name alternates its trailing __A / __B suffix on each edit; include that
|
|
227
|
+
// letter in the label so the edit-flow configuration is distinct from the prior one
|
|
228
|
+
// (configuration definition labels must be unique).
|
|
229
|
+
var secretAlt = oauthSecretName.slice(-1);
|
|
230
|
+
var cfgLabel = `Authorize OAuth connection: ${connectionSlug} (${secretAlt})`;
|
|
231
|
+
var cfgDescription = `Complete the OAuth flow so this application can access the external service for connection '${connectionSlug}'`;
|
|
232
|
+
var cfgSql = `ALTER APPLICATION SET CONFIGURATION DEFINITION ${oauthSecretConfigName}\n` +
|
|
233
|
+
` TYPE = SECRET_AUTHORIZATION\n` +
|
|
234
|
+
` SECRET = ${oauthSecretFqn}\n` +
|
|
235
|
+
` LABEL = '${esc(cfgLabel)}'\n` +
|
|
236
|
+
` DESCRIPTION = '${esc(cfgDescription)}'\n` +
|
|
237
|
+
` APPLICATION_ROLES = (OMNATA_MANAGEMENT)`;
|
|
238
|
+
snowflake.log("info", `Executing SQL: ${cfgSql}`);
|
|
239
|
+
snowflake.createStatement({sqlText: cfgSql, binds: []}).execute();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// (6) Other secrets — seeded from an existing secret and/or caller-supplied values.
|
|
243
|
+
var seedSecrets = {};
|
|
244
|
+
if (mergeWithSecretName){
|
|
245
|
+
// RETRIEVE_SECRETS_UDF reads the secret contents via _snowflake.get_generic_secret_string,
|
|
246
|
+
// which takes the binding alias (the unqualified name) — not the FQN. The source secret
|
|
247
|
+
// must already be bound to RETRIEVE_SECRETS_UDF via a prior CONFIGURE_APIS run.
|
|
248
|
+
// Passing NULL for the OAuth secret name keeps the access_token key out of the returned object.
|
|
249
|
+
var seedRs = snowflake.createStatement({
|
|
250
|
+
sqlText: `select PLUGIN.RETRIEVE_SECRETS_UDF(NULL, ?)`,
|
|
251
|
+
binds: [mergeWithSecretName]
|
|
252
|
+
}).execute();
|
|
253
|
+
seedRs.next();
|
|
254
|
+
var seedValue = seedRs.getColumnValue(1);
|
|
255
|
+
if (seedValue && typeof seedValue === 'object'){
|
|
256
|
+
seedSecrets = seedValue;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (connectionSecrets){
|
|
260
|
+
Object.keys(connectionSecrets).forEach(function(k){
|
|
261
|
+
seedSecrets[k] = connectionSecrets[k];
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
callProc(`call PLUGIN.CREATE_GENERIC_SECRET_OBJECT(?, ?)`, [otherSecretsFqn, JSON.stringify(seedSecrets)]);
|
|
265
|
+
|
|
266
|
+
// (7) External Access Integration.
|
|
267
|
+
var eaiRules = [networkRuleFqn];
|
|
268
|
+
if (privatelinkRuleFqn){ eaiRules.push(privatelinkRuleFqn); }
|
|
269
|
+
var eaiSecrets = [otherSecretsFqn];
|
|
270
|
+
if (oauthSecretFqn){ eaiSecrets.push(oauthSecretFqn); }
|
|
271
|
+
|
|
272
|
+
var eaiRulesSql = eaiRules.join(', ');
|
|
273
|
+
var eaiSecretsClause = '';
|
|
274
|
+
if (eaiSecrets.length > 0){
|
|
275
|
+
eaiSecretsClause = `\n ALLOWED_AUTHENTICATION_SECRETS = (${eaiSecrets.join(', ')})`;
|
|
276
|
+
}
|
|
277
|
+
var eaiSql = `CREATE OR REPLACE EXTERNAL ACCESS INTEGRATION IDENTIFIER(?)\n` +
|
|
278
|
+
` ALLOWED_NETWORK_RULES = (${eaiRulesSql})` +
|
|
279
|
+
eaiSecretsClause + `\n` +
|
|
280
|
+
` ENABLED = FALSE`;
|
|
281
|
+
snowflake.log("info", `Executing SQL: ${eaiSql}`);
|
|
282
|
+
snowflake.createStatement({sqlText: eaiSql, binds: [eaiName]}).execute();
|
|
283
|
+
// Grant USAGE so the sync engine (via OMNATA_MANAGEMENT) can see/use the EAI.
|
|
284
|
+
snowflake.createStatement({
|
|
285
|
+
sqlText: `grant usage on integration IDENTIFIER(?) to application role OMNATA_MANAGEMENT`,
|
|
286
|
+
binds: [eaiName]
|
|
287
|
+
}).execute();
|
|
288
|
+
|
|
289
|
+
// (8) EAI application specification — delegate to SET_EAI_SPECIFICATION.
|
|
290
|
+
// Unlike for security integrations/configurations, this specification does not need to alternate when editing as it's not bound to any objects
|
|
291
|
+
var eaiSpecProps = {
|
|
292
|
+
LABEL: `External access for connection: ${connectionSlug}`,
|
|
293
|
+
DESCRIPTION: `Allows this app to communicate with external services for connection '${connectionSlug}'`,
|
|
294
|
+
HOST_PORTS: networkAddresses
|
|
295
|
+
};
|
|
296
|
+
if (privatelinkAddresses){
|
|
297
|
+
eaiSpecProps.PRIVATE_HOST_PORTS = privatelinkAddresses;
|
|
298
|
+
}
|
|
299
|
+
callProc(
|
|
300
|
+
`call PLUGIN.SET_EAI_SPECIFICATION(?, PARSE_JSON(?))`,
|
|
301
|
+
[eaiSpecName, JSON.stringify(eaiSpecProps)]
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
// (9) SI application specification — delegate to SET_SI_SPECIFICATION (OAuth only).
|
|
305
|
+
if (siSpecName){
|
|
306
|
+
var op2 = oauthParameters;
|
|
307
|
+
var grantUc = String(op2.oauth_grant || 'authorization_code').toUpperCase();
|
|
308
|
+
// Include the alternating suffix letter so the edit-flow spec gets a distinct label.
|
|
309
|
+
var siSpecAlt = siSpecName.slice(-1);
|
|
310
|
+
var siSpecProps = {
|
|
311
|
+
LABEL: `OAuth integration for connection: ${connectionSlug} (${siSpecAlt})`,
|
|
312
|
+
DESCRIPTION: `Allows this app to use an OAuth security integration for connection '${connectionSlug}'`,
|
|
313
|
+
OAUTH_TYPE: grantUc
|
|
314
|
+
};
|
|
315
|
+
if (op2.oauth_token_endpoint){
|
|
316
|
+
siSpecProps.OAUTH_TOKEN_ENDPOINT = op2.oauth_token_endpoint;
|
|
317
|
+
}
|
|
318
|
+
if (grantUc === 'AUTHORIZATION_CODE' && op2.oauth_authorization_endpoint){
|
|
319
|
+
siSpecProps.OAUTH_AUTHORIZATION_ENDPOINT = op2.oauth_authorization_endpoint;
|
|
320
|
+
}
|
|
321
|
+
if (Array.isArray(op2.oauth_allowed_scopes) && op2.oauth_allowed_scopes.length > 0){
|
|
322
|
+
siSpecProps.OAUTH_ALLOWED_SCOPES = op2.oauth_allowed_scopes;
|
|
323
|
+
}
|
|
324
|
+
callProc(
|
|
325
|
+
`call PLUGIN.SET_SI_SPECIFICATION(?, PARSE_JSON(?))`,
|
|
326
|
+
[siSpecName, JSON.stringify(siSpecProps)]
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return {
|
|
331
|
+
"success": true,
|
|
332
|
+
"data": {
|
|
333
|
+
"connection_slug": connectionSlug,
|
|
334
|
+
"external_access_integration_name": eaiName,
|
|
335
|
+
"network_rule_name": networkRuleFqn,
|
|
336
|
+
"network_rule_name_privatelink": privatelinkRuleFqn,
|
|
337
|
+
"oauth_security_integration_name": oauthSiName,
|
|
338
|
+
"oauth_secret_name": oauthSecretFqn,
|
|
339
|
+
"oauth_secret_configuration_name": oauthSecretConfigName,
|
|
340
|
+
"other_secrets_name": otherSecretsFqn,
|
|
341
|
+
"external_access_integration_spec_name": eaiSpecName,
|
|
342
|
+
"security_integration_spec_name": siSpecName
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
catch(e){
|
|
347
|
+
snowflake.log("error", e);
|
|
348
|
+
return {
|
|
349
|
+
"success": false,
|
|
350
|
+
"error": `SET_CONNECTION_OBJECTS: ${String(e)}`
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
$$
|
|
354
|
+
;
|
|
355
|
+
|
|
356
|
+
grant usage on procedure PLUGIN.SET_CONNECTION_OBJECTS(OBJECT)
|
|
357
|
+
to application role OMNATA_MANAGEMENT;
|
omnata_plugin_devkit-0.13.1/src/omnata_plugin_devkit/jinja_templates/SET_EAI_ENABLED.sql.jinja
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
create or replace procedure PLUGIN.SET_EAI_ENABLED(EAI_NAME VARCHAR, ENABLED BOOLEAN)
|
|
2
|
+
returns object
|
|
3
|
+
language javascript
|
|
4
|
+
COMMENT = $$
|
|
5
|
+
Enables or disables an External Access Integration owned by this application via
|
|
6
|
+
ALTER EXTERNAL ACCESS INTEGRATION <name> SET ENABLED = TRUE|FALSE.
|
|
7
|
+
$$
|
|
8
|
+
execute as owner
|
|
9
|
+
as
|
|
10
|
+
$$
|
|
11
|
+
try{
|
|
12
|
+
if (typeof EAI_NAME !== 'string' || EAI_NAME.length === 0 || EAI_NAME.length > 255){
|
|
13
|
+
throw `SET_EAI_ENABLED: EAI_NAME must be a non-empty string`;
|
|
14
|
+
}
|
|
15
|
+
/*
|
|
16
|
+
From docs:
|
|
17
|
+
The values true and false are represented by 1 and 0 respectively.
|
|
18
|
+
Note that this behavior may change in future releases, so you should rely
|
|
19
|
+
on JavaScript truthiness rather than direct value comparisons.
|
|
20
|
+
*/
|
|
21
|
+
var enabledSql = ENABLED ? 'TRUE' : 'FALSE';
|
|
22
|
+
var sql = `ALTER EXTERNAL ACCESS INTEGRATION IDENTIFIER(?) SET ENABLED = ${enabledSql}`;
|
|
23
|
+
snowflake.log("info", `Executing SQL: ${sql}`);
|
|
24
|
+
snowflake.createStatement({sqlText: sql, binds: [EAI_NAME]}).execute();
|
|
25
|
+
return {
|
|
26
|
+
"success": true,
|
|
27
|
+
"data": {
|
|
28
|
+
"name": EAI_NAME,
|
|
29
|
+
"enabled": ENABLED
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
catch(e){
|
|
34
|
+
return {
|
|
35
|
+
"success": false,
|
|
36
|
+
"error": `SET_EAI_ENABLED: ${String(e)}`
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
$$
|
|
40
|
+
;
|
|
41
|
+
|
|
42
|
+
grant usage on procedure PLUGIN.SET_EAI_ENABLED(VARCHAR, BOOLEAN)
|
|
43
|
+
to application role OMNATA_MANAGEMENT;
|
omnata_plugin_devkit-0.13.1/src/omnata_plugin_devkit/jinja_templates/SET_EAI_SPECIFICATION.sql.jinja
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
create or replace procedure PLUGIN.SET_EAI_SPECIFICATION(SPEC_NAME VARCHAR, PROPERTIES OBJECT)
|
|
2
|
+
returns object
|
|
3
|
+
language javascript
|
|
4
|
+
COMMENT = $$
|
|
5
|
+
Sets an EXTERNAL_ACCESS application specification via ALTER APPLICATION SET SPECIFICATION.
|
|
6
|
+
TYPE is hard-coded to EXTERNAL_ACCESS. Permitted properties:
|
|
7
|
+
LABEL, DESCRIPTION, HOST_PORTS, PRIVATE_HOST_PORTS.
|
|
8
|
+
Raises an error if any input is invalid or the ALTER fails.
|
|
9
|
+
$$
|
|
10
|
+
execute as owner
|
|
11
|
+
as
|
|
12
|
+
$$
|
|
13
|
+
// Validate spec name as a simple identifier so it can be safely interpolated
|
|
14
|
+
// into ALTER APPLICATION SET SPECIFICATION <name>.
|
|
15
|
+
if (typeof SPEC_NAME !== 'string' || !/^[A-Za-z_][A-Za-z0-9_$]*$/.test(SPEC_NAME) || SPEC_NAME.length > 255) {
|
|
16
|
+
throw `SET_EAI_SPECIFICATION: invalid specification name`;
|
|
17
|
+
}
|
|
18
|
+
var props = PROPERTIES || {};
|
|
19
|
+
var allowed = ['LABEL', 'DESCRIPTION', 'HOST_PORTS', 'PRIVATE_HOST_PORTS'];
|
|
20
|
+
function escSql(s) {
|
|
21
|
+
return String(s).replace(/'/g, "''");
|
|
22
|
+
}
|
|
23
|
+
var clauses = [`TYPE = EXTERNAL_ACCESS`];
|
|
24
|
+
Object.keys(props).forEach(function(key){
|
|
25
|
+
var upperKey = key.toUpperCase();
|
|
26
|
+
if (allowed.indexOf(upperKey) === -1) {
|
|
27
|
+
throw `SET_EAI_SPECIFICATION: property ${key} is not permitted`;
|
|
28
|
+
}
|
|
29
|
+
var value = props[key];
|
|
30
|
+
if (upperKey === 'LABEL' || upperKey === 'DESCRIPTION') {
|
|
31
|
+
if (typeof value !== 'string') {
|
|
32
|
+
throw `SET_EAI_SPECIFICATION: ${upperKey} must be a string`;
|
|
33
|
+
}
|
|
34
|
+
clauses.push(`${upperKey} = '${escSql(value)}'`);
|
|
35
|
+
} else if (upperKey === 'HOST_PORTS' || upperKey === 'PRIVATE_HOST_PORTS') {
|
|
36
|
+
if (!Array.isArray(value)) {
|
|
37
|
+
throw `SET_EAI_SPECIFICATION: ${upperKey} must be an array of strings`;
|
|
38
|
+
}
|
|
39
|
+
var listSql = value.map(function(v){
|
|
40
|
+
if (typeof v !== 'string') {
|
|
41
|
+
throw `SET_EAI_SPECIFICATION: ${upperKey} entries must be strings`;
|
|
42
|
+
}
|
|
43
|
+
return `'${escSql(v)}'`;
|
|
44
|
+
}).join(', ');
|
|
45
|
+
clauses.push(`${upperKey} = (${listSql})`);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
var sql = `ALTER APPLICATION SET SPECIFICATION ${SPEC_NAME}\n ` + clauses.join('\n ');
|
|
49
|
+
snowflake.log("info", `Executing SQL: ${sql}`);
|
|
50
|
+
snowflake.createStatement({ sqlText: sql, binds: [] }).execute();
|
|
51
|
+
return {
|
|
52
|
+
"success": true,
|
|
53
|
+
"data": {
|
|
54
|
+
"name": SPEC_NAME
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
$$
|
|
58
|
+
;
|
|
59
|
+
|
|
60
|
+
grant usage on procedure PLUGIN.SET_EAI_SPECIFICATION(VARCHAR, OBJECT)
|
|
61
|
+
to application role OMNATA_MANAGEMENT;
|
omnata_plugin_devkit-0.13.1/src/omnata_plugin_devkit/jinja_templates/SET_SI_SPECIFICATION.sql.jinja
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
create or replace procedure PLUGIN.SET_SI_SPECIFICATION(SPEC_NAME VARCHAR, PROPERTIES OBJECT)
|
|
2
|
+
returns object
|
|
3
|
+
language javascript
|
|
4
|
+
COMMENT = $$
|
|
5
|
+
Sets a SECURITY_INTEGRATION application specification via ALTER APPLICATION SET SPECIFICATION.
|
|
6
|
+
TYPE is hard-coded to SECURITY_INTEGRATION. Permitted properties:
|
|
7
|
+
LABEL, DESCRIPTION, OAUTH_TYPE (AUTHORIZATION_CODE or CLIENT_CREDENTIALS),
|
|
8
|
+
OAUTH_TOKEN_ENDPOINT, OAUTH_ALLOWED_SCOPES, OAUTH_AUTHORIZATION_ENDPOINT.
|
|
9
|
+
Raises an error if any input is invalid or the ALTER fails.
|
|
10
|
+
$$
|
|
11
|
+
execute as owner
|
|
12
|
+
as
|
|
13
|
+
$$
|
|
14
|
+
// Validate spec name as a simple identifier so it can be safely interpolated
|
|
15
|
+
// into ALTER APPLICATION SET SPECIFICATION <name>.
|
|
16
|
+
if (typeof SPEC_NAME !== 'string' || !/^[A-Za-z_][A-Za-z0-9_$]*$/.test(SPEC_NAME) || SPEC_NAME.length > 255) {
|
|
17
|
+
throw `SET_SI_SPECIFICATION: invalid specification name`;
|
|
18
|
+
}
|
|
19
|
+
var props = PROPERTIES || {};
|
|
20
|
+
var stringProps = ['LABEL', 'DESCRIPTION', 'OAUTH_TOKEN_ENDPOINT', 'OAUTH_AUTHORIZATION_ENDPOINT'];
|
|
21
|
+
var allowedOauthTypes = ['AUTHORIZATION_CODE', 'CLIENT_CREDENTIALS'];
|
|
22
|
+
var allowed = stringProps.concat(['OAUTH_TYPE', 'OAUTH_ALLOWED_SCOPES']);
|
|
23
|
+
function escSql(s) {
|
|
24
|
+
return String(s).replace(/'/g, "''");
|
|
25
|
+
}
|
|
26
|
+
var clauses = [`TYPE = SECURITY_INTEGRATION`];
|
|
27
|
+
Object.keys(props).forEach(function(key){
|
|
28
|
+
var upperKey = key.toUpperCase();
|
|
29
|
+
if (allowed.indexOf(upperKey) === -1) {
|
|
30
|
+
throw `SET_SI_SPECIFICATION: property ${key} is not permitted`;
|
|
31
|
+
}
|
|
32
|
+
var value = props[key];
|
|
33
|
+
if (stringProps.indexOf(upperKey) !== -1) {
|
|
34
|
+
if (typeof value !== 'string') {
|
|
35
|
+
throw `SET_SI_SPECIFICATION: ${upperKey} must be a string`;
|
|
36
|
+
}
|
|
37
|
+
clauses.push(`${upperKey} = '${escSql(value)}'`);
|
|
38
|
+
} else if (upperKey === 'OAUTH_TYPE') {
|
|
39
|
+
if (typeof value !== 'string' || allowedOauthTypes.indexOf(value.toUpperCase()) === -1) {
|
|
40
|
+
throw `SET_SI_SPECIFICATION: OAUTH_TYPE must be one of: ${allowedOauthTypes.join(', ')}`;
|
|
41
|
+
}
|
|
42
|
+
clauses.push(`OAUTH_TYPE = ${value.toUpperCase()}`);
|
|
43
|
+
} else if (upperKey === 'OAUTH_ALLOWED_SCOPES') {
|
|
44
|
+
if (!Array.isArray(value)) {
|
|
45
|
+
throw `SET_SI_SPECIFICATION: OAUTH_ALLOWED_SCOPES must be an array of strings`;
|
|
46
|
+
}
|
|
47
|
+
var listSql = value.map(function(v){
|
|
48
|
+
if (typeof v !== 'string') {
|
|
49
|
+
throw `SET_SI_SPECIFICATION: OAUTH_ALLOWED_SCOPES entries must be strings`;
|
|
50
|
+
}
|
|
51
|
+
return `'${escSql(v)}'`;
|
|
52
|
+
}).join(', ');
|
|
53
|
+
clauses.push(`OAUTH_ALLOWED_SCOPES = (${listSql})`);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
var sql = `ALTER APPLICATION SET SPECIFICATION ${SPEC_NAME}\n ` + clauses.join('\n ');
|
|
57
|
+
snowflake.log("info", `Executing SQL: ${sql}`);
|
|
58
|
+
snowflake.createStatement({ sqlText: sql, binds: [] }).execute();
|
|
59
|
+
return {
|
|
60
|
+
"success": true,
|
|
61
|
+
"data": {
|
|
62
|
+
"name": SPEC_NAME
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
$$
|
|
66
|
+
;
|
|
67
|
+
|
|
68
|
+
grant usage on procedure PLUGIN.SET_SI_SPECIFICATION(VARCHAR, OBJECT)
|
|
69
|
+
to application role OMNATA_MANAGEMENT;
|
|
@@ -28,3 +28,8 @@ configuration:
|
|
|
28
28
|
- type: METRICS
|
|
29
29
|
sharing: OPTIONAL
|
|
30
30
|
|
|
31
|
+
privileges:
|
|
32
|
+
- CREATE EXTERNAL ACCESS INTEGRATION:
|
|
33
|
+
description: "To create external access integrations for Omnata to communicate with external services"
|
|
34
|
+
- CREATE SECURITY INTEGRATION:
|
|
35
|
+
description: "To create security integrations for OAuth"
|
|
@@ -81,7 +81,6 @@ class NativeAppPackaging:
|
|
|
81
81
|
application_name: str,
|
|
82
82
|
version_name: str,
|
|
83
83
|
patch_number: Optional[int] = None,
|
|
84
|
-
debug_mode: bool = True,
|
|
85
84
|
authorize_telemetry_event_sharing: bool = True,
|
|
86
85
|
) -> bool:
|
|
87
86
|
"""
|
|
@@ -109,7 +108,6 @@ class NativeAppPackaging:
|
|
|
109
108
|
f"""CREATE APPLICATION {application_name}
|
|
110
109
|
FROM APPLICATION PACKAGE {self.package_name}
|
|
111
110
|
USING VERSION {version_name} patch {patch_number}
|
|
112
|
-
DEBUG_MODE={str(debug_mode).upper()}
|
|
113
111
|
AUTHORIZE_TELEMETRY_EVENT_SHARING={str(authorize_telemetry_event_sharing).upper()}"""
|
|
114
112
|
).collect()
|
|
115
113
|
return True
|
|
@@ -485,7 +485,16 @@ class PluginUploader:
|
|
|
485
485
|
"RETRIEVE_NETWORK_RULE_OBJECT",
|
|
486
486
|
"POST_INSTALL_ACTIONS",
|
|
487
487
|
"RENAME_CONNECTION_METHODS",
|
|
488
|
-
"RETRIEVE_SECRETS_UDF"
|
|
488
|
+
"RETRIEVE_SECRETS_UDF",
|
|
489
|
+
"SET_EAI_SPECIFICATION",
|
|
490
|
+
"SET_SI_SPECIFICATION",
|
|
491
|
+
"LIST_APP_SPECIFICATIONS",
|
|
492
|
+
"SET_CONNECTION_OBJECTS",
|
|
493
|
+
"CHECK_CONNECTION_PROGRESS",
|
|
494
|
+
"DROP_SECRETS",
|
|
495
|
+
"DROP_NETWORK_RULES",
|
|
496
|
+
"SET_EAI_ENABLED",
|
|
497
|
+
"GET_MISSING_APP_PRIVILEGES"
|
|
489
498
|
]:
|
|
490
499
|
template = environment.get_template(f"{proc_template}.sql.jinja")
|
|
491
500
|
content = template.render({
|
|
File without changes
|
|
File without changes
|
{omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/utils.py
RENAMED
|
File without changes
|