kubernetes-watch 0.1.5__py3-none-any.whl → 0.1.9__py3-none-any.whl
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.
- kube_watch/enums/kube.py +5 -5
- kube_watch/enums/logic.py +8 -8
- kube_watch/enums/providers.py +12 -12
- kube_watch/enums/workflow.py +17 -17
- kube_watch/models/common.py +16 -16
- kube_watch/models/workflow.py +60 -60
- kube_watch/modules/clusters/kube.py +185 -185
- kube_watch/modules/database/__init__.py +0 -0
- kube_watch/modules/database/model.py +12 -0
- kube_watch/modules/database/postgre.py +271 -0
- kube_watch/modules/logic/actions.py +55 -55
- kube_watch/modules/logic/checks.py +7 -7
- kube_watch/modules/logic/load.py +23 -23
- kube_watch/modules/logic/merge.py +31 -31
- kube_watch/modules/logic/scheduler.py +74 -74
- kube_watch/modules/mock/mock_generator.py +53 -53
- kube_watch/modules/providers/aws.py +210 -210
- kube_watch/modules/providers/git.py +120 -32
- kube_watch/modules/providers/github.py +126 -126
- kube_watch/modules/providers/vault.py +188 -188
- kube_watch/standalone/metarecogen/ckan_to_gn.py +132 -132
- kube_watch/watch/__init__.py +1 -1
- kube_watch/watch/helpers.py +170 -170
- kube_watch/watch/workflow.py +232 -100
- {kubernetes_watch-0.1.5.dist-info → kubernetes_watch-0.1.9.dist-info}/LICENSE +21 -21
- {kubernetes_watch-0.1.5.dist-info → kubernetes_watch-0.1.9.dist-info}/METADATA +5 -3
- kubernetes_watch-0.1.9.dist-info/RECORD +36 -0
- kubernetes_watch-0.1.5.dist-info/RECORD +0 -33
- {kubernetes_watch-0.1.5.dist-info → kubernetes_watch-0.1.9.dist-info}/WHEEL +0 -0
|
@@ -1,188 +1,188 @@
|
|
|
1
|
-
import hvac
|
|
2
|
-
import os
|
|
3
|
-
from prefect import get_run_logger
|
|
4
|
-
|
|
5
|
-
from kube_watch.enums.providers import Providers
|
|
6
|
-
|
|
7
|
-
logger = get_run_logger()
|
|
8
|
-
|
|
9
|
-
def login(url, app_role_id, secret_id, path):
|
|
10
|
-
"""
|
|
11
|
-
Login to Vault, using an existing token if available, or via AppRole otherwise.
|
|
12
|
-
|
|
13
|
-
Parameters:
|
|
14
|
-
url (str): Vault server URL.
|
|
15
|
-
app_role_id (str): AppRole ID.
|
|
16
|
-
secret_id (str): AppRole Secret ID.
|
|
17
|
-
path (str): Path where the AppRole is enabled.
|
|
18
|
-
|
|
19
|
-
Returns:
|
|
20
|
-
dict: Dictionary containing the initialized vault_client.
|
|
21
|
-
"""
|
|
22
|
-
vault_client = hvac.Client(url=url)
|
|
23
|
-
|
|
24
|
-
# Attempt to use an existing token from environment variables
|
|
25
|
-
vault_token = os.getenv('VAULT_TOKEN', None)
|
|
26
|
-
if vault_token:
|
|
27
|
-
vault_client.token = vault_token
|
|
28
|
-
# Verify if the current token is still valid
|
|
29
|
-
try:
|
|
30
|
-
if vault_client.is_authenticated():
|
|
31
|
-
logger.info("Authenticated with existing token.")
|
|
32
|
-
return vault_client
|
|
33
|
-
except hvac.exceptions.InvalidRequest as e:
|
|
34
|
-
logger.warning(f"Failed to authenticate with the existing token: {str(e)}")
|
|
35
|
-
|
|
36
|
-
# If token is not valid or not present, authenticate with AppRole
|
|
37
|
-
try:
|
|
38
|
-
vault_client.auth.approle.login(
|
|
39
|
-
role_id=app_role_id,
|
|
40
|
-
secret_id=secret_id,
|
|
41
|
-
mount_point=f'approle/{path}'
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
# Store the new token in environment variables for subsequent use
|
|
45
|
-
os.environ['VAULT_TOKEN'] = vault_client.token
|
|
46
|
-
logger.info("Authenticated with new token and stored in environment variable.")
|
|
47
|
-
|
|
48
|
-
return vault_client
|
|
49
|
-
except hvac.exceptions.InvalidRequest as e:
|
|
50
|
-
logger.error(f"Authentication failed with provided secret_id: {str(e)}")
|
|
51
|
-
raise RuntimeError("Authentication failed: unable to log in with the provided credentials.") from e
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def get_secret(vault_client, secret_path, vault_mount_point):
|
|
56
|
-
"""
|
|
57
|
-
Retrieve a secret from Vault
|
|
58
|
-
"""
|
|
59
|
-
res = vault_client.secrets.kv.v2.read_secret_version(
|
|
60
|
-
path=secret_path,
|
|
61
|
-
mount_point=vault_mount_point,
|
|
62
|
-
raise_on_deleted_version=True
|
|
63
|
-
)
|
|
64
|
-
return res.get('data', {}).get('data')
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
def update_secret(vault_client, secret_path, secret_data, vault_mount_point):
|
|
68
|
-
"""
|
|
69
|
-
Update or create a secret in Vault at the specified path.
|
|
70
|
-
|
|
71
|
-
Args:
|
|
72
|
-
vault_client: The authenticated Vault client instance.
|
|
73
|
-
secret_path (str): The path where the secret will be stored or updated in Vault.
|
|
74
|
-
secret_data (dict): The secret data to store as a dictionary.
|
|
75
|
-
vault_mount_point (str): The mount point for the KV store.
|
|
76
|
-
|
|
77
|
-
Returns:
|
|
78
|
-
bool: True if the operation was successful, False otherwise.
|
|
79
|
-
"""
|
|
80
|
-
try:
|
|
81
|
-
# Writing the secret data to Vault at the specified path
|
|
82
|
-
vault_client.secrets.kv.v2.create_or_update_secret(
|
|
83
|
-
path=secret_path,
|
|
84
|
-
secret=secret_data,
|
|
85
|
-
mount_point=vault_mount_point
|
|
86
|
-
)
|
|
87
|
-
print("Secret updated successfully.")
|
|
88
|
-
return True
|
|
89
|
-
except Exception as e:
|
|
90
|
-
print(f"Failed to update secret: {e}")
|
|
91
|
-
return False
|
|
92
|
-
|
|
93
|
-
def generate_provider_creds(vault_client, provider, backend_path, role_name):
|
|
94
|
-
"""
|
|
95
|
-
Generate credentials for a specified provider
|
|
96
|
-
"""
|
|
97
|
-
if provider == Providers.AWS:
|
|
98
|
-
backend_path = backend_path
|
|
99
|
-
role_name = role_name
|
|
100
|
-
creds_path = f"{backend_path}/creds/{role_name}"
|
|
101
|
-
return vault_client.read(creds_path)
|
|
102
|
-
|
|
103
|
-
raise ValueError("Unknown provider")
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
def generate_new_secret_id(vault_client, role_name, vault_path, env_var_name):
|
|
108
|
-
"""
|
|
109
|
-
Generates new secret_id. Note an admin role is required for this.
|
|
110
|
-
"""
|
|
111
|
-
try:
|
|
112
|
-
# Write directly to the Vault endpoint to create the secret ID with num_uses
|
|
113
|
-
# response = vault_client.write(
|
|
114
|
-
# f"auth/approle/{vault_path}/role/{role_name}/secret-id",
|
|
115
|
-
# )
|
|
116
|
-
response = vault_client.auth.approle.generate_secret_id(
|
|
117
|
-
role_name=role_name,
|
|
118
|
-
mount_point=f'approle/{vault_path}'
|
|
119
|
-
)
|
|
120
|
-
# Check if the response contains the secret ID
|
|
121
|
-
if response and 'data' in response:
|
|
122
|
-
secret_id = response['data']['secret_id']
|
|
123
|
-
secret_id_accessor = response['data']['secret_id_accessor']
|
|
124
|
-
logger.info("Generated a new secret ID with usage buffer.")
|
|
125
|
-
return {env_var_name: secret_id, f"{env_var_name}_ACCESSOR": secret_id_accessor}
|
|
126
|
-
else:
|
|
127
|
-
logger.error("No secret ID returned in the response.")
|
|
128
|
-
raise RuntimeError("Failed to generate new secret ID: No content returned.")
|
|
129
|
-
except hvac.exceptions.InvalidRequest as e:
|
|
130
|
-
logger.error("Error generating new secret ID: %s", str(e))
|
|
131
|
-
raise RuntimeError("Failed to generate new secret ID.") from e
|
|
132
|
-
# new_secret_response = vault_client.auth.approle.generate_secret_id(
|
|
133
|
-
# role_name=role_name,
|
|
134
|
-
# mount_point=f'approle/{vault_path}'
|
|
135
|
-
# )
|
|
136
|
-
|
|
137
|
-
# return { env_var_name : new_secret_response['data']['secret_id'] }
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
def delete_secret_id(vault_client, role_name, secret_id, vault_path):
|
|
142
|
-
"""
|
|
143
|
-
Delete (revoke) a secret ID associated with a role in Vault.
|
|
144
|
-
|
|
145
|
-
Parameters:
|
|
146
|
-
vault_client (hvac.Client): An authenticated Vault client.
|
|
147
|
-
role_name (str): The name of the role the secret ID is associated with.
|
|
148
|
-
secret_id (str): The secret ID to be deleted.
|
|
149
|
-
vault_path (str): The path where the AppRole is enabled.
|
|
150
|
-
"""
|
|
151
|
-
try:
|
|
152
|
-
vault_client.auth.approle.destroy_secret_id(
|
|
153
|
-
mount_point=f"approle/{vault_path}",
|
|
154
|
-
role_name=role_name,
|
|
155
|
-
secret_id=secret_id
|
|
156
|
-
)
|
|
157
|
-
|
|
158
|
-
logger.info("Secret ID successfully revoked.")
|
|
159
|
-
except hvac.exceptions.InvalidRequest as e:
|
|
160
|
-
logger.error("Failed to revoke the secret ID: %s", str(e))
|
|
161
|
-
raise RuntimeError("Failed to delete the secret ID.") from e
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
def clean_secret_ids(vault_client, role_name, secret_id_env, vault_path, has_kube_secret_updated):
|
|
165
|
-
"""
|
|
166
|
-
This function removes all idle secret-ids from `role_name`, except the
|
|
167
|
-
inputted `secret_id_env`.
|
|
168
|
-
|
|
169
|
-
Note: secret_id_env is a dictionary. The key, VAULT_SECRET_ID, has the secret_id value.
|
|
170
|
-
"""
|
|
171
|
-
secret_id = secret_id_env.get("VAULT_SECRET_ID_ACCESSOR")
|
|
172
|
-
|
|
173
|
-
if has_kube_secret_updated:
|
|
174
|
-
secret_ids_path = f'auth/approle/{vault_path}/role/{role_name}/secret-id'
|
|
175
|
-
try:
|
|
176
|
-
response = vault_client.list(secret_ids_path)
|
|
177
|
-
if 'data' in response:
|
|
178
|
-
secret_ids = response['data']['keys']
|
|
179
|
-
for idx in secret_ids:
|
|
180
|
-
if idx != secret_id:
|
|
181
|
-
delete_secret_id(vault_client, role_name, secret_id, vault_path)
|
|
182
|
-
logger.info(f"Revoking idle secret id for role: {role_name}")
|
|
183
|
-
else:
|
|
184
|
-
logger.info("No secrets found at this path.")
|
|
185
|
-
except hvac.exceptions.Forbidden:
|
|
186
|
-
logger.error("Access denied. Ensure your token has the correct policies to read this path.")
|
|
187
|
-
except Exception as e:
|
|
188
|
-
logger.error(f"An error occurred: {e}")
|
|
1
|
+
import hvac
|
|
2
|
+
import os
|
|
3
|
+
from prefect import get_run_logger
|
|
4
|
+
|
|
5
|
+
from kube_watch.enums.providers import Providers
|
|
6
|
+
|
|
7
|
+
logger = get_run_logger()
|
|
8
|
+
|
|
9
|
+
def login(url, app_role_id, secret_id, path):
|
|
10
|
+
"""
|
|
11
|
+
Login to Vault, using an existing token if available, or via AppRole otherwise.
|
|
12
|
+
|
|
13
|
+
Parameters:
|
|
14
|
+
url (str): Vault server URL.
|
|
15
|
+
app_role_id (str): AppRole ID.
|
|
16
|
+
secret_id (str): AppRole Secret ID.
|
|
17
|
+
path (str): Path where the AppRole is enabled.
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
dict: Dictionary containing the initialized vault_client.
|
|
21
|
+
"""
|
|
22
|
+
vault_client = hvac.Client(url=url)
|
|
23
|
+
|
|
24
|
+
# Attempt to use an existing token from environment variables
|
|
25
|
+
vault_token = os.getenv('VAULT_TOKEN', None)
|
|
26
|
+
if vault_token:
|
|
27
|
+
vault_client.token = vault_token
|
|
28
|
+
# Verify if the current token is still valid
|
|
29
|
+
try:
|
|
30
|
+
if vault_client.is_authenticated():
|
|
31
|
+
logger.info("Authenticated with existing token.")
|
|
32
|
+
return vault_client
|
|
33
|
+
except hvac.exceptions.InvalidRequest as e:
|
|
34
|
+
logger.warning(f"Failed to authenticate with the existing token: {str(e)}")
|
|
35
|
+
|
|
36
|
+
# If token is not valid or not present, authenticate with AppRole
|
|
37
|
+
try:
|
|
38
|
+
vault_client.auth.approle.login(
|
|
39
|
+
role_id=app_role_id,
|
|
40
|
+
secret_id=secret_id,
|
|
41
|
+
mount_point=f'approle/{path}'
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# Store the new token in environment variables for subsequent use
|
|
45
|
+
os.environ['VAULT_TOKEN'] = vault_client.token
|
|
46
|
+
logger.info("Authenticated with new token and stored in environment variable.")
|
|
47
|
+
|
|
48
|
+
return vault_client
|
|
49
|
+
except hvac.exceptions.InvalidRequest as e:
|
|
50
|
+
logger.error(f"Authentication failed with provided secret_id: {str(e)}")
|
|
51
|
+
raise RuntimeError("Authentication failed: unable to log in with the provided credentials.") from e
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def get_secret(vault_client, secret_path, vault_mount_point):
|
|
56
|
+
"""
|
|
57
|
+
Retrieve a secret from Vault
|
|
58
|
+
"""
|
|
59
|
+
res = vault_client.secrets.kv.v2.read_secret_version(
|
|
60
|
+
path=secret_path,
|
|
61
|
+
mount_point=vault_mount_point,
|
|
62
|
+
raise_on_deleted_version=True
|
|
63
|
+
)
|
|
64
|
+
return res.get('data', {}).get('data')
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def update_secret(vault_client, secret_path, secret_data, vault_mount_point):
|
|
68
|
+
"""
|
|
69
|
+
Update or create a secret in Vault at the specified path.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
vault_client: The authenticated Vault client instance.
|
|
73
|
+
secret_path (str): The path where the secret will be stored or updated in Vault.
|
|
74
|
+
secret_data (dict): The secret data to store as a dictionary.
|
|
75
|
+
vault_mount_point (str): The mount point for the KV store.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
bool: True if the operation was successful, False otherwise.
|
|
79
|
+
"""
|
|
80
|
+
try:
|
|
81
|
+
# Writing the secret data to Vault at the specified path
|
|
82
|
+
vault_client.secrets.kv.v2.create_or_update_secret(
|
|
83
|
+
path=secret_path,
|
|
84
|
+
secret=secret_data,
|
|
85
|
+
mount_point=vault_mount_point
|
|
86
|
+
)
|
|
87
|
+
print("Secret updated successfully.")
|
|
88
|
+
return True
|
|
89
|
+
except Exception as e:
|
|
90
|
+
print(f"Failed to update secret: {e}")
|
|
91
|
+
return False
|
|
92
|
+
|
|
93
|
+
def generate_provider_creds(vault_client, provider, backend_path, role_name):
|
|
94
|
+
"""
|
|
95
|
+
Generate credentials for a specified provider
|
|
96
|
+
"""
|
|
97
|
+
if provider == Providers.AWS:
|
|
98
|
+
backend_path = backend_path
|
|
99
|
+
role_name = role_name
|
|
100
|
+
creds_path = f"{backend_path}/creds/{role_name}"
|
|
101
|
+
return vault_client.read(creds_path)
|
|
102
|
+
|
|
103
|
+
raise ValueError("Unknown provider")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def generate_new_secret_id(vault_client, role_name, vault_path, env_var_name):
|
|
108
|
+
"""
|
|
109
|
+
Generates new secret_id. Note an admin role is required for this.
|
|
110
|
+
"""
|
|
111
|
+
try:
|
|
112
|
+
# Write directly to the Vault endpoint to create the secret ID with num_uses
|
|
113
|
+
# response = vault_client.write(
|
|
114
|
+
# f"auth/approle/{vault_path}/role/{role_name}/secret-id",
|
|
115
|
+
# )
|
|
116
|
+
response = vault_client.auth.approle.generate_secret_id(
|
|
117
|
+
role_name=role_name,
|
|
118
|
+
mount_point=f'approle/{vault_path}'
|
|
119
|
+
)
|
|
120
|
+
# Check if the response contains the secret ID
|
|
121
|
+
if response and 'data' in response:
|
|
122
|
+
secret_id = response['data']['secret_id']
|
|
123
|
+
secret_id_accessor = response['data']['secret_id_accessor']
|
|
124
|
+
logger.info("Generated a new secret ID with usage buffer.")
|
|
125
|
+
return {env_var_name: secret_id, f"{env_var_name}_ACCESSOR": secret_id_accessor}
|
|
126
|
+
else:
|
|
127
|
+
logger.error("No secret ID returned in the response.")
|
|
128
|
+
raise RuntimeError("Failed to generate new secret ID: No content returned.")
|
|
129
|
+
except hvac.exceptions.InvalidRequest as e:
|
|
130
|
+
logger.error("Error generating new secret ID: %s", str(e))
|
|
131
|
+
raise RuntimeError("Failed to generate new secret ID.") from e
|
|
132
|
+
# new_secret_response = vault_client.auth.approle.generate_secret_id(
|
|
133
|
+
# role_name=role_name,
|
|
134
|
+
# mount_point=f'approle/{vault_path}'
|
|
135
|
+
# )
|
|
136
|
+
|
|
137
|
+
# return { env_var_name : new_secret_response['data']['secret_id'] }
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def delete_secret_id(vault_client, role_name, secret_id, vault_path):
|
|
142
|
+
"""
|
|
143
|
+
Delete (revoke) a secret ID associated with a role in Vault.
|
|
144
|
+
|
|
145
|
+
Parameters:
|
|
146
|
+
vault_client (hvac.Client): An authenticated Vault client.
|
|
147
|
+
role_name (str): The name of the role the secret ID is associated with.
|
|
148
|
+
secret_id (str): The secret ID to be deleted.
|
|
149
|
+
vault_path (str): The path where the AppRole is enabled.
|
|
150
|
+
"""
|
|
151
|
+
try:
|
|
152
|
+
vault_client.auth.approle.destroy_secret_id(
|
|
153
|
+
mount_point=f"approle/{vault_path}",
|
|
154
|
+
role_name=role_name,
|
|
155
|
+
secret_id=secret_id
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
logger.info("Secret ID successfully revoked.")
|
|
159
|
+
except hvac.exceptions.InvalidRequest as e:
|
|
160
|
+
logger.error("Failed to revoke the secret ID: %s", str(e))
|
|
161
|
+
raise RuntimeError("Failed to delete the secret ID.") from e
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def clean_secret_ids(vault_client, role_name, secret_id_env, vault_path, has_kube_secret_updated):
|
|
165
|
+
"""
|
|
166
|
+
This function removes all idle secret-ids from `role_name`, except the
|
|
167
|
+
inputted `secret_id_env`.
|
|
168
|
+
|
|
169
|
+
Note: secret_id_env is a dictionary. The key, VAULT_SECRET_ID, has the secret_id value.
|
|
170
|
+
"""
|
|
171
|
+
secret_id = secret_id_env.get("VAULT_SECRET_ID_ACCESSOR")
|
|
172
|
+
|
|
173
|
+
if has_kube_secret_updated:
|
|
174
|
+
secret_ids_path = f'auth/approle/{vault_path}/role/{role_name}/secret-id'
|
|
175
|
+
try:
|
|
176
|
+
response = vault_client.list(secret_ids_path)
|
|
177
|
+
if 'data' in response:
|
|
178
|
+
secret_ids = response['data']['keys']
|
|
179
|
+
for idx in secret_ids:
|
|
180
|
+
if idx != secret_id:
|
|
181
|
+
delete_secret_id(vault_client, role_name, secret_id, vault_path)
|
|
182
|
+
logger.info(f"Revoking idle secret id for role: {role_name}")
|
|
183
|
+
else:
|
|
184
|
+
logger.info("No secrets found at this path.")
|
|
185
|
+
except hvac.exceptions.Forbidden:
|
|
186
|
+
logger.error("Access denied. Ensure your token has the correct policies to read this path.")
|
|
187
|
+
except Exception as e:
|
|
188
|
+
logger.error(f"An error occurred: {e}")
|
|
@@ -1,132 +1,132 @@
|
|
|
1
|
-
import requests
|
|
2
|
-
# from pathlib import Path
|
|
3
|
-
import os
|
|
4
|
-
import sys
|
|
5
|
-
|
|
6
|
-
"""
|
|
7
|
-
A simple script to:
|
|
8
|
-
1. Retrieve all public records from a CKAN service
|
|
9
|
-
2. Insert CKAN records into a Geonetwork service
|
|
10
|
-
|
|
11
|
-
This requires the 'iso19115' extension to be installed in CKAN and the following env. vars:
|
|
12
|
-
1. 'CKAN2GN_GN_USERNAME' geonetwork username for an account that can create records
|
|
13
|
-
2. 'CKAN2GN_GN_PASSWORD' geonetwork password for an account that can create records
|
|
14
|
-
3. 'CKAN2GN_GN_URL' geonetork service URL
|
|
15
|
-
4. 'CKAN2GN_CKAN_URL' CKAN service URL
|
|
16
|
-
"""
|
|
17
|
-
|
|
18
|
-
# Geonetwork username and password:
|
|
19
|
-
GN_USERNAME = os.environ.get('CKAN2GN_GN_USERNAME')
|
|
20
|
-
GN_PASSWORD = os.environ.get('CKAN2GN_GN_PASSWORD')
|
|
21
|
-
|
|
22
|
-
# Geonetwork and CKAN server URLs
|
|
23
|
-
GN_URL = os.environ.get('CKAN2GN_GN_URL')
|
|
24
|
-
CKAN_URL = os.environ.get('CKAN2GN_CKAN_URL')
|
|
25
|
-
|
|
26
|
-
def get_gn_xsrf_token(session):
|
|
27
|
-
""" Retrieves XSRF token from Geonetwork
|
|
28
|
-
|
|
29
|
-
:param session: requests Session object
|
|
30
|
-
:returns: XSRF as string or None upon error
|
|
31
|
-
"""
|
|
32
|
-
authenticate_url = GN_URL + '/geonetwork/srv/eng/info?type=me'
|
|
33
|
-
response = session.post(authenticate_url)
|
|
34
|
-
|
|
35
|
-
# Extract XRSF token
|
|
36
|
-
xsrf_token = response.cookies.get("XSRF-TOKEN")
|
|
37
|
-
if xsrf_token:
|
|
38
|
-
return xsrf_token
|
|
39
|
-
return None
|
|
40
|
-
|
|
41
|
-
def list_ckan_records():
|
|
42
|
-
""" Contacts CKAN and retrieves a list of package ids for all public records
|
|
43
|
-
|
|
44
|
-
:returns: list of package id strings or None upon error
|
|
45
|
-
"""
|
|
46
|
-
session = requests.Session()
|
|
47
|
-
url_path = 'api/3/action/package_list' # Path('api') / '3' / 'action' / 'package_list'
|
|
48
|
-
url = f'{CKAN_URL}/{url_path}'
|
|
49
|
-
r = session.get(url)
|
|
50
|
-
resp = r.json()
|
|
51
|
-
if resp['success'] is False:
|
|
52
|
-
return None
|
|
53
|
-
return resp['result']
|
|
54
|
-
|
|
55
|
-
def get_ckan_record(package_id):
|
|
56
|
-
""" Given a package id retrieves its record metadata
|
|
57
|
-
|
|
58
|
-
:param package_id: CKAN package_id string
|
|
59
|
-
:returns: package metadata as a dict or None upon error
|
|
60
|
-
"""
|
|
61
|
-
session = requests.Session()
|
|
62
|
-
# Set up CKAN URL
|
|
63
|
-
url_path = 'api/3/action/iso19115_package_show' # Path('api') / '3' / 'action' / 'iso19115_package_show'
|
|
64
|
-
url = f'{CKAN_URL}/{url_path}'
|
|
65
|
-
r = session.get(url, params={'format':'xml', 'id':package_id})
|
|
66
|
-
resp = r.json()
|
|
67
|
-
if resp['success'] is False:
|
|
68
|
-
return None
|
|
69
|
-
return resp['result']
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
def insert_gn_record(session, xsrf_token, xml_string):
|
|
73
|
-
""" Inserts a record into Geonetwork
|
|
74
|
-
|
|
75
|
-
:param session: requests Session object
|
|
76
|
-
:param xsrf_token: Geonetwork's XSRF token as a string
|
|
77
|
-
:param xml_string: XML to be inserted as a string
|
|
78
|
-
:returns: True or False if insert succeeded
|
|
79
|
-
"""
|
|
80
|
-
# Set header for connection
|
|
81
|
-
headers = {'Accept': 'application/json',
|
|
82
|
-
'Content-Type': 'application/xml',
|
|
83
|
-
'X-XSRF-TOKEN': xsrf_token
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
# Set the parameters
|
|
87
|
-
# Currently 'uuidProcessing' is set to 'NOTHING' so that records that
|
|
88
|
-
# already exist are rejected by Geonetwork as duplicates
|
|
89
|
-
params = {'metadataType': 'METADATA',
|
|
90
|
-
'publishToAll': 'true',
|
|
91
|
-
'uuidProcessing': 'NOTHING', # Available values : GENERATEUUID, NOTHING, OVERWRITE
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
# Send a put request to the endpoint to create record
|
|
95
|
-
response = session.put(GN_URL + '/geonetwork/srv/api/0.1/records',
|
|
96
|
-
data=xml_string,
|
|
97
|
-
params=params,
|
|
98
|
-
auth=(GN_USERNAME, GN_PASSWORD),
|
|
99
|
-
headers=headers
|
|
100
|
-
)
|
|
101
|
-
resp = response.json()
|
|
102
|
-
|
|
103
|
-
# Check if record was created in Geonetwork
|
|
104
|
-
if response.status_code == requests.codes['created'] and resp['numberOfRecordsProcessed'] == 1 and \
|
|
105
|
-
resp['numberOfRecordsWithErrors'] == 0:
|
|
106
|
-
print("Inserted")
|
|
107
|
-
return True
|
|
108
|
-
print(f"Insert failed: status code: {response.status_code}\n{resp}")
|
|
109
|
-
return False
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
if __name__ == "__main__":
|
|
113
|
-
# Check env. vars
|
|
114
|
-
if GN_USERNAME is None or GN_PASSWORD is None or GN_URL is None or CKAN_URL is None:
|
|
115
|
-
print("Please define the following env. vars:")
|
|
116
|
-
print(" 'CKAN2GN_GN_USERNAME' 'CKAN2GN_GN_PASSWORD' 'CKAN2GN_GN_URL' 'CKAN2GN_CKAN_URL'")
|
|
117
|
-
sys.exit(1)
|
|
118
|
-
# Connect to server
|
|
119
|
-
session = requests.Session()
|
|
120
|
-
xsrf = get_gn_xsrf_token(session)
|
|
121
|
-
if xsrf is not None:
|
|
122
|
-
# Get records from CKAN
|
|
123
|
-
for id in list_ckan_records():
|
|
124
|
-
print(f"Inserting '{id}'")
|
|
125
|
-
xml_string = get_ckan_record(id)
|
|
126
|
-
if xml_string is not None:
|
|
127
|
-
# Insert GN record
|
|
128
|
-
insert_gn_record(session, xsrf, xml_string)
|
|
129
|
-
else:
|
|
130
|
-
print(f"Could not get record id {id} from CKAN")
|
|
131
|
-
|
|
132
|
-
|
|
1
|
+
import requests
|
|
2
|
+
# from pathlib import Path
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
A simple script to:
|
|
8
|
+
1. Retrieve all public records from a CKAN service
|
|
9
|
+
2. Insert CKAN records into a Geonetwork service
|
|
10
|
+
|
|
11
|
+
This requires the 'iso19115' extension to be installed in CKAN and the following env. vars:
|
|
12
|
+
1. 'CKAN2GN_GN_USERNAME' geonetwork username for an account that can create records
|
|
13
|
+
2. 'CKAN2GN_GN_PASSWORD' geonetwork password for an account that can create records
|
|
14
|
+
3. 'CKAN2GN_GN_URL' geonetork service URL
|
|
15
|
+
4. 'CKAN2GN_CKAN_URL' CKAN service URL
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
# Geonetwork username and password:
|
|
19
|
+
GN_USERNAME = os.environ.get('CKAN2GN_GN_USERNAME')
|
|
20
|
+
GN_PASSWORD = os.environ.get('CKAN2GN_GN_PASSWORD')
|
|
21
|
+
|
|
22
|
+
# Geonetwork and CKAN server URLs
|
|
23
|
+
GN_URL = os.environ.get('CKAN2GN_GN_URL')
|
|
24
|
+
CKAN_URL = os.environ.get('CKAN2GN_CKAN_URL')
|
|
25
|
+
|
|
26
|
+
def get_gn_xsrf_token(session):
|
|
27
|
+
""" Retrieves XSRF token from Geonetwork
|
|
28
|
+
|
|
29
|
+
:param session: requests Session object
|
|
30
|
+
:returns: XSRF as string or None upon error
|
|
31
|
+
"""
|
|
32
|
+
authenticate_url = GN_URL + '/geonetwork/srv/eng/info?type=me'
|
|
33
|
+
response = session.post(authenticate_url)
|
|
34
|
+
|
|
35
|
+
# Extract XRSF token
|
|
36
|
+
xsrf_token = response.cookies.get("XSRF-TOKEN")
|
|
37
|
+
if xsrf_token:
|
|
38
|
+
return xsrf_token
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
def list_ckan_records():
|
|
42
|
+
""" Contacts CKAN and retrieves a list of package ids for all public records
|
|
43
|
+
|
|
44
|
+
:returns: list of package id strings or None upon error
|
|
45
|
+
"""
|
|
46
|
+
session = requests.Session()
|
|
47
|
+
url_path = 'api/3/action/package_list' # Path('api') / '3' / 'action' / 'package_list'
|
|
48
|
+
url = f'{CKAN_URL}/{url_path}'
|
|
49
|
+
r = session.get(url)
|
|
50
|
+
resp = r.json()
|
|
51
|
+
if resp['success'] is False:
|
|
52
|
+
return None
|
|
53
|
+
return resp['result']
|
|
54
|
+
|
|
55
|
+
def get_ckan_record(package_id):
|
|
56
|
+
""" Given a package id retrieves its record metadata
|
|
57
|
+
|
|
58
|
+
:param package_id: CKAN package_id string
|
|
59
|
+
:returns: package metadata as a dict or None upon error
|
|
60
|
+
"""
|
|
61
|
+
session = requests.Session()
|
|
62
|
+
# Set up CKAN URL
|
|
63
|
+
url_path = 'api/3/action/iso19115_package_show' # Path('api') / '3' / 'action' / 'iso19115_package_show'
|
|
64
|
+
url = f'{CKAN_URL}/{url_path}'
|
|
65
|
+
r = session.get(url, params={'format':'xml', 'id':package_id})
|
|
66
|
+
resp = r.json()
|
|
67
|
+
if resp['success'] is False:
|
|
68
|
+
return None
|
|
69
|
+
return resp['result']
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def insert_gn_record(session, xsrf_token, xml_string):
|
|
73
|
+
""" Inserts a record into Geonetwork
|
|
74
|
+
|
|
75
|
+
:param session: requests Session object
|
|
76
|
+
:param xsrf_token: Geonetwork's XSRF token as a string
|
|
77
|
+
:param xml_string: XML to be inserted as a string
|
|
78
|
+
:returns: True or False if insert succeeded
|
|
79
|
+
"""
|
|
80
|
+
# Set header for connection
|
|
81
|
+
headers = {'Accept': 'application/json',
|
|
82
|
+
'Content-Type': 'application/xml',
|
|
83
|
+
'X-XSRF-TOKEN': xsrf_token
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
# Set the parameters
|
|
87
|
+
# Currently 'uuidProcessing' is set to 'NOTHING' so that records that
|
|
88
|
+
# already exist are rejected by Geonetwork as duplicates
|
|
89
|
+
params = {'metadataType': 'METADATA',
|
|
90
|
+
'publishToAll': 'true',
|
|
91
|
+
'uuidProcessing': 'NOTHING', # Available values : GENERATEUUID, NOTHING, OVERWRITE
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
# Send a put request to the endpoint to create record
|
|
95
|
+
response = session.put(GN_URL + '/geonetwork/srv/api/0.1/records',
|
|
96
|
+
data=xml_string,
|
|
97
|
+
params=params,
|
|
98
|
+
auth=(GN_USERNAME, GN_PASSWORD),
|
|
99
|
+
headers=headers
|
|
100
|
+
)
|
|
101
|
+
resp = response.json()
|
|
102
|
+
|
|
103
|
+
# Check if record was created in Geonetwork
|
|
104
|
+
if response.status_code == requests.codes['created'] and resp['numberOfRecordsProcessed'] == 1 and \
|
|
105
|
+
resp['numberOfRecordsWithErrors'] == 0:
|
|
106
|
+
print("Inserted")
|
|
107
|
+
return True
|
|
108
|
+
print(f"Insert failed: status code: {response.status_code}\n{resp}")
|
|
109
|
+
return False
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
if __name__ == "__main__":
|
|
113
|
+
# Check env. vars
|
|
114
|
+
if GN_USERNAME is None or GN_PASSWORD is None or GN_URL is None or CKAN_URL is None:
|
|
115
|
+
print("Please define the following env. vars:")
|
|
116
|
+
print(" 'CKAN2GN_GN_USERNAME' 'CKAN2GN_GN_PASSWORD' 'CKAN2GN_GN_URL' 'CKAN2GN_CKAN_URL'")
|
|
117
|
+
sys.exit(1)
|
|
118
|
+
# Connect to server
|
|
119
|
+
session = requests.Session()
|
|
120
|
+
xsrf = get_gn_xsrf_token(session)
|
|
121
|
+
if xsrf is not None:
|
|
122
|
+
# Get records from CKAN
|
|
123
|
+
for id in list_ckan_records():
|
|
124
|
+
print(f"Inserting '{id}'")
|
|
125
|
+
xml_string = get_ckan_record(id)
|
|
126
|
+
if xml_string is not None:
|
|
127
|
+
# Insert GN record
|
|
128
|
+
insert_gn_record(session, xsrf, xml_string)
|
|
129
|
+
else:
|
|
130
|
+
print(f"Could not get record id {id} from CKAN")
|
|
131
|
+
|
|
132
|
+
|
kube_watch/watch/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
from .workflow import
|
|
1
|
+
from .workflow import single_run_workflow_async, batch_run_workflow, single_run_workflow
|