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.
Files changed (86) hide show
  1. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/PKG-INFO +1 -1
  2. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/pyproject.toml +1 -1
  3. omnata_plugin_devkit-0.13.1/src/omnata_plugin_devkit/jinja_templates/CHECK_CONNECTION_PROGRESS.sql.jinja +112 -0
  4. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/CONFIGURE_APIS.sql.jinja +5 -0
  5. omnata_plugin_devkit-0.13.1/src/omnata_plugin_devkit/jinja_templates/GET_MISSING_APP_PRIVILEGES.sql.jinja +35 -0
  6. omnata_plugin_devkit-0.13.1/src/omnata_plugin_devkit/jinja_templates/LIST_APP_SPECIFICATIONS.sql.jinja +40 -0
  7. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/NETWORK_ADDRESSES.sql.jinja +2 -1
  8. omnata_plugin_devkit-0.13.1/src/omnata_plugin_devkit/jinja_templates/SET_CONNECTION_OBJECTS.sql.jinja +357 -0
  9. omnata_plugin_devkit-0.13.1/src/omnata_plugin_devkit/jinja_templates/SET_EAI_ENABLED.sql.jinja +43 -0
  10. omnata_plugin_devkit-0.13.1/src/omnata_plugin_devkit/jinja_templates/SET_EAI_SPECIFICATION.sql.jinja +61 -0
  11. omnata_plugin_devkit-0.13.1/src/omnata_plugin_devkit/jinja_templates/SET_SI_SPECIFICATION.sql.jinja +69 -0
  12. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/manifest.yml.jinja +5 -0
  13. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/native_app_packaging.py +0 -2
  14. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/plugin_uploader.py +10 -1
  15. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/LICENSE +0 -0
  16. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/README.md +0 -0
  17. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/__init__.py +0 -0
  18. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/airbyte_wrapper.py +0 -0
  19. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/cli/__init__.py +0 -0
  20. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/development.ipynb +0 -0
  21. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/development_session.py +0 -0
  22. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/initialiser.py +0 -0
  23. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/API_LIMITS.sql.jinja +0 -0
  24. {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
  25. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/CONFIGURATION_FORM.sql.jinja +0 -0
  26. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/CONNECTION_FORM.sql.jinja +0 -0
  27. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/CONNECTION_TEST.sql.jinja +0 -0
  28. {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
  29. {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
  30. {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
  31. {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
  32. {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
  33. {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
  34. {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
  35. {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
  36. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/DROP_SECRETS.sql.jinja +0 -0
  37. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/FETCH_CONNECTIONS.sql.jinja +0 -0
  38. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/FETCH_SYNCS.sql.jinja +0 -0
  39. {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
  40. {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
  41. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/LIST_STAGES.sql.jinja +0 -0
  42. {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
  43. {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
  44. {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
  45. {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
  46. {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
  47. {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
  48. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/RETRIEVE_SECRETS.sql.jinja +0 -0
  49. {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
  50. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/SYNC.sql.jinja +0 -0
  51. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/TEST_CALLBACK.sql.jinja +0 -0
  52. {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
  53. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/jinja_templates/TUNNEL_TEST.sql.jinja +0 -0
  54. {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
  55. {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
  56. {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
  57. {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
  58. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/plugin_registration.py +0 -0
  59. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/plugin_template/icon.svg +0 -0
  60. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/plugin_template/plugin.py +0 -0
  61. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/plugin_template/plugin_development.ipynb +0 -0
  62. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/plugin_template/requirements.txt +0 -0
  63. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/snowcli/__init__.py +0 -0
  64. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/snowcli/cli/__init__.py +0 -0
  65. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/snowcli/cli/api/__init__.py +0 -0
  66. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/snowcli/cli/api/config.py +0 -0
  67. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/snowcli/cli/api/constants.py +0 -0
  68. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/snowcli/cli/api/exceptions.py +0 -0
  69. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/snowcli/cli/api/secure_path.py +0 -0
  70. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/snowcli/cli/api/secure_utils.py +0 -0
  71. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/snowcli/cli/api/utils/__init__.py +0 -0
  72. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/snowcli/cli/api/utils/types.py +0 -0
  73. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/snowcli/cli/app/__init__.py +0 -0
  74. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/snowcli/cli/app/snow_connector.py +0 -0
  75. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/snowcli/cli/plugins/__init__.py +0 -0
  76. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/snowcli/cli/plugins/snowpark/__init__.py +0 -0
  77. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/snowcli/cli/plugins/snowpark/models.py +0 -0
  78. {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
  79. {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
  80. {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
  81. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/snowcli/cli/plugins/snowpark/venv.py +0 -0
  82. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/snowcli/cli/plugins/snowpark/zipper.py +0 -0
  83. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/snowcli/cli/templates/environment.yml.jinja +0 -0
  84. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/streamlit/plugin_configuration.py +0 -0
  85. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/test_step_definitions.py +0 -0
  86. {omnata_plugin_devkit-0.13.0a159 → omnata_plugin_devkit-0.13.1}/src/omnata_plugin_devkit/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: omnata-plugin-devkit
3
- Version: 0.13.0a159
3
+ Version: 0.13.1
4
4
  Summary:
5
5
  License-File: LICENSE
6
6
  Author: James Weakley
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "omnata-plugin-devkit"
3
- version = "0.13.0a159"
3
+ version = "0.13.1"
4
4
  description = ""
5
5
  authors = ["James Weakley <james.weakley@omnata.com>"]
6
6
  readme = "README.md"
@@ -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;
@@ -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;
@@ -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;
@@ -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({