ligoj-cli 1.0.0__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.
- ligoj_cli-1.0.0.dist-info/METADATA +2350 -0
- ligoj_cli-1.0.0.dist-info/RECORD +20 -0
- ligoj_cli-1.0.0.dist-info/WHEEL +5 -0
- ligoj_cli-1.0.0.dist-info/entry_points.txt +2 -0
- ligoj_cli-1.0.0.dist-info/licenses/LICENSE +21 -0
- ligoj_cli-1.0.0.dist-info/top_level.txt +1 -0
- ligojcli/__init__.py +0 -0
- ligojcli/ligoj.py +126 -0
- ligojcli/plugins/__init__.py +3 -0
- ligojcli/plugins/alfresco.py +246 -0
- ligojcli/plugins/argocd.py +205 -0
- ligojcli/plugins/bootstrap.py +672 -0
- ligojcli/plugins/gitlab.py +221 -0
- ligojcli/plugins/harbor.py +170 -0
- ligojcli/plugins/jenkins.py +606 -0
- ligojcli/plugins/ligoj.py +1665 -0
- ligojcli/plugins/nexus.py +245 -0
- ligojcli/plugins/sonarqube.py +295 -0
- ligojcli/plugins/ssl.py +143 -0
- ligojcli/plugins/utils.py +401 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
ligoj_cli-1.0.0.dist-info/licenses/LICENSE,sha256=L2YkZq7C9_D6o9V3rYPyP1voevszDMzu8Y4DMgXuVL8,1062
|
|
2
|
+
ligojcli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
ligojcli/ligoj.py,sha256=BSKbLBHw_miqKrsRRMxFUA1vkqdZUCBpxonjrIjk6i8,3836
|
|
4
|
+
ligojcli/plugins/__init__.py,sha256=UMUcOsjtTJ-xapSGPQDYgKDKeNzoZkYxbfyIW2pQ9Bs,78
|
|
5
|
+
ligojcli/plugins/alfresco.py,sha256=DbWhIViZfHyo6942pRX88OzLtg1RLfxpEOoMfmubyaA,12405
|
|
6
|
+
ligojcli/plugins/argocd.py,sha256=S_PH2KKqhfJmWaanijkLaLbFe0HL-FZ4cj2Cq9Sx92w,9218
|
|
7
|
+
ligojcli/plugins/bootstrap.py,sha256=F-1n-vME_A4fePG1uGaGMz9C_G8KRhfKArEFriZVKmk,40374
|
|
8
|
+
ligojcli/plugins/gitlab.py,sha256=V3rbL4WlO6PykYwXlFUarVOPylcgE3Q443_RiwY98cE,12447
|
|
9
|
+
ligojcli/plugins/harbor.py,sha256=tW8DqF7WGZCeaLJHw5YJtHtcqDp2hyqmqrvoE1Ex1_E,7384
|
|
10
|
+
ligojcli/plugins/jenkins.py,sha256=PPz4atF7-Jx4bG-uuuvBYuEvq3fbQauOYFWiNuoHQtc,28164
|
|
11
|
+
ligojcli/plugins/ligoj.py,sha256=9Vj4BAzsTQ-nzTGDundfMMDsZ1ID-DpmR9sb2YWUl64,90090
|
|
12
|
+
ligojcli/plugins/nexus.py,sha256=EwHbDEtrl_hgoshqwXN8LDjfB0xEDdPCGTcRK4fCKUo,11309
|
|
13
|
+
ligojcli/plugins/sonarqube.py,sha256=vRjJV0GI-t-kUEVUIvE2cbBR5qWJRTS2pr_M51qVmH8,14818
|
|
14
|
+
ligojcli/plugins/ssl.py,sha256=JQma6-T4kHk__FMp6Jq0Dd0-Y1zYQoUtzy_M8x44DpY,5193
|
|
15
|
+
ligojcli/plugins/utils.py,sha256=v-n5sR0WODytOJMev3ahV3NP_WgoBkodWzV7p039aak,16690
|
|
16
|
+
ligoj_cli-1.0.0.dist-info/METADATA,sha256=ubEo2zrpf-Aet_-q93Zo8-G6kudvN5qpETKPElv8Kig,84209
|
|
17
|
+
ligoj_cli-1.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
18
|
+
ligoj_cli-1.0.0.dist-info/entry_points.txt,sha256=su1lFQ2Dk1a2S-3FhgOIpwnAQek0Q9RYv57TODt1EyM,47
|
|
19
|
+
ligoj_cli-1.0.0.dist-info/top_level.txt,sha256=sufJCWe9-z4wUZlj6vJu5kcwwWv3y3yjd0Ag3LHcUKk,9
|
|
20
|
+
ligoj_cli-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ligoj
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ligojcli
|
ligojcli/__init__.py
ADDED
|
File without changes
|
ligojcli/ligoj.py
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
#
|
|
3
|
+
# Licensed under MIT (https://github.com/ligoj/ligoj/blob/master/LICENSE)
|
|
4
|
+
#
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
from ligojcli.plugins import (
|
|
10
|
+
alfresco,
|
|
11
|
+
argocd,
|
|
12
|
+
bootstrap,
|
|
13
|
+
gitlab,
|
|
14
|
+
harbor,
|
|
15
|
+
jenkins,
|
|
16
|
+
nexus,
|
|
17
|
+
sonarqube,
|
|
18
|
+
utils,
|
|
19
|
+
)
|
|
20
|
+
from ligojcli.plugins import ligoj as ligoj_plugin
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def main():
|
|
24
|
+
(parser, subparser_service) = utils.init()
|
|
25
|
+
argocd.configure(subparser_service)
|
|
26
|
+
alfresco.configure(subparser_service)
|
|
27
|
+
bootstrap.configure(subparser_service)
|
|
28
|
+
gitlab.configure(subparser_service)
|
|
29
|
+
harbor.configure(subparser_service)
|
|
30
|
+
jenkins.configure(subparser_service)
|
|
31
|
+
ligoj_plugin.configure(subparser_service)
|
|
32
|
+
nexus.configure(subparser_service)
|
|
33
|
+
sonarqube.configure(subparser_service)
|
|
34
|
+
|
|
35
|
+
(args, output) = utils.configure(parser)
|
|
36
|
+
|
|
37
|
+
if args["version"] is True:
|
|
38
|
+
service = "info"
|
|
39
|
+
action = "version"
|
|
40
|
+
operation = None
|
|
41
|
+
else:
|
|
42
|
+
service = args.get("service")
|
|
43
|
+
action = args.get("action")
|
|
44
|
+
operation = args.get("operation")
|
|
45
|
+
if action is None or service is None:
|
|
46
|
+
parser.print_usage()
|
|
47
|
+
return
|
|
48
|
+
|
|
49
|
+
ligoj_plugin.parse_remote_args(args)
|
|
50
|
+
|
|
51
|
+
result = execute_action(service, action, operation, args)
|
|
52
|
+
if result is False:
|
|
53
|
+
# Ignore output
|
|
54
|
+
return
|
|
55
|
+
|
|
56
|
+
if isinstance(result, str):
|
|
57
|
+
result = {"id": result}
|
|
58
|
+
elif result is not None:
|
|
59
|
+
try:
|
|
60
|
+
result = result.json()
|
|
61
|
+
except BaseException:
|
|
62
|
+
try:
|
|
63
|
+
result = {"id": result.text}
|
|
64
|
+
except BaseException:
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
if output == "json":
|
|
68
|
+
try:
|
|
69
|
+
print(json.dumps(result))
|
|
70
|
+
except Exception:
|
|
71
|
+
print("Unable to display result as JSON", result)
|
|
72
|
+
|
|
73
|
+
elif output == "text":
|
|
74
|
+
if isinstance(result, str):
|
|
75
|
+
print(result)
|
|
76
|
+
elif result is None or (isinstance(result, dict) and len(result.keys()) == 0):
|
|
77
|
+
print("")
|
|
78
|
+
elif isinstance(result, dict) and len(result.keys()) == 1:
|
|
79
|
+
print(result[list(result.keys())[0]])
|
|
80
|
+
elif isinstance(result, dict):
|
|
81
|
+
for key in result.keys():
|
|
82
|
+
print(result[key])
|
|
83
|
+
else:
|
|
84
|
+
print(result)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def execute_action(service, action, operation, args):
|
|
88
|
+
utils.check_endpoint(utils.not_none(ligoj_plugin.ligoj_endpoint, "endpoint"), "ligoj")
|
|
89
|
+
utils.debug(
|
|
90
|
+
f"[ligoj] Ligoj CLI '{service}/{action}' profile '{utils.ini_profile}', user '{ligoj_plugin.ligoj_api_user}'{'' if ligoj_plugin.ligoj_api_run_as_user is None else (' as ' + ligoj_plugin.ligoj_api_run_as_user)} on endpoint '{ligoj_plugin.ligoj_endpoint}'"
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
ligoj_plugin.execute_action(service, action, operation, args)
|
|
95
|
+
or alfresco.execute_action(service, action, operation, args)
|
|
96
|
+
or argocd.execute_action(service, action, operation, args)
|
|
97
|
+
or jenkins.execute_action(service, action, operation, args)
|
|
98
|
+
or bootstrap.execute_action(service, action, operation, args)
|
|
99
|
+
or harbor.execute_action(service, action, operation, args)
|
|
100
|
+
or nexus.execute_action(service, action, operation, args)
|
|
101
|
+
or sonarqube.execute_action(service, action, operation, args)
|
|
102
|
+
or gitlab.execute_action(service, action, operation, args)
|
|
103
|
+
or False
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def ligoj() -> int:
|
|
108
|
+
"""Entry point for the ligoj CLI."""
|
|
109
|
+
try:
|
|
110
|
+
main()
|
|
111
|
+
except ValueError as e:
|
|
112
|
+
utils.error(str(e))
|
|
113
|
+
sys.exit(os.EX_SOFTWARE)
|
|
114
|
+
return os.EX_SOFTWARE
|
|
115
|
+
except SystemExit as e:
|
|
116
|
+
raise e
|
|
117
|
+
except BaseException as e:
|
|
118
|
+
template = "An exception of type {0} occurred. Arguments:\n{1!r}"
|
|
119
|
+
error_message = template.format(type(e).__name__, e.args)
|
|
120
|
+
utils.error(error_message)
|
|
121
|
+
raise e
|
|
122
|
+
return 0
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
if __name__ == "__main__":
|
|
126
|
+
ligoj()
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Licensed under MIT (https://github.com/ligoj/ligoj/blob/master/LICENSE)
|
|
3
|
+
#
|
|
4
|
+
import base64
|
|
5
|
+
|
|
6
|
+
from unidecode import unidecode
|
|
7
|
+
|
|
8
|
+
from ligojcli.plugins import ligoj, utils
|
|
9
|
+
|
|
10
|
+
PLUGIN_NAME = "alfresco"
|
|
11
|
+
alfresco_endpoint: str | None = None
|
|
12
|
+
alfresco_ticket: str | None = None
|
|
13
|
+
alfresco_user: str | None = None
|
|
14
|
+
alfresco_password: str | None = None
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def configure(subparser_service):
|
|
18
|
+
# alfresco
|
|
19
|
+
subparser_action = subparser_service.add_parser("alfresco", help="Alfresco related operations").add_subparsers(title="action", help="Action", dest="action")
|
|
20
|
+
subparser_service2 = subparser_action.add_parser("site", help="Site operations").add_subparsers(title="action", help="Action", dest="operation")
|
|
21
|
+
subparser_service2.add_parser("list", help="List sites")
|
|
22
|
+
parser_action = subparser_service2.add_parser("create", help="Create site")
|
|
23
|
+
parser_action.add_argument("--id", "-i", help="Site name")
|
|
24
|
+
parser_action.add_argument("--title", help="Site title")
|
|
25
|
+
parser_action.add_argument("--visibility", help="Visibility", choices=["PUBLIC", "PRIVATE"])
|
|
26
|
+
parser_action.add_argument("--description", help="Description")
|
|
27
|
+
parser_action = subparser_service2.add_parser("get", help="Get site")
|
|
28
|
+
parser_action.add_argument("--id", help="Site name")
|
|
29
|
+
parser_action = subparser_service2.add_parser("add-group", help="Add group to site with a role")
|
|
30
|
+
parser_action.add_argument("--id", help="Site name")
|
|
31
|
+
parser_action.add_argument("--group", help="Group name")
|
|
32
|
+
parser_action.add_argument("--role", help="Role name", choices=["SiteCollaborator", "SiteConsumer", "SiteManager"])
|
|
33
|
+
parser_action = subparser_service2.add_parser("remove-group", help="Remove group from site")
|
|
34
|
+
parser_action.add_argument("--id", help="Site name")
|
|
35
|
+
parser_action.add_argument("--group", help="Group name")
|
|
36
|
+
subparser_service2 = subparser_action.add_parser("group", help="Group operations").add_subparsers(title="action", help="Action", dest="operation")
|
|
37
|
+
parser_action = subparser_service2.add_parser("create", help="Create group")
|
|
38
|
+
parser_action.add_argument("--id", "-i", help="Group name")
|
|
39
|
+
parser_action.add_argument("--display-name", help="Group display name")
|
|
40
|
+
parser_action.add_argument("--parents", help="Parent group identifiers", nargs="*", default=["admin"])
|
|
41
|
+
parser_action = subparser_service2.add_parser("delete", help="Delete group")
|
|
42
|
+
parser_action.add_argument("--id", "-i", help="Group name")
|
|
43
|
+
parser_action = subparser_service2.add_parser("get", help="Get group")
|
|
44
|
+
parser_action.add_argument("--id", "-i", help="Group name")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def execute_action(service, action, operation, args):
|
|
48
|
+
if service == "alfresco":
|
|
49
|
+
parse_remote_args(args)
|
|
50
|
+
|
|
51
|
+
if action == "site" and operation == "create":
|
|
52
|
+
return alfresco_create_site(args.get("id"), args.get("title"), args.get("description"), args.get("visibility"))
|
|
53
|
+
if action == "site" and operation == "list":
|
|
54
|
+
return alfresco_list_sites()
|
|
55
|
+
if action == "site" and operation == "get":
|
|
56
|
+
return alfresco_get_site(args.get("id"))
|
|
57
|
+
if action == "site" and operation == "add-group":
|
|
58
|
+
return alfresco_set_permissions(args.get("group"), [args.get("role")], args.get("id"))
|
|
59
|
+
if action == "site" and operation == "remove-group":
|
|
60
|
+
return alfresco_remove_permissions(args.get("group"), args.get("id"))
|
|
61
|
+
if action == "group" and operation == "create":
|
|
62
|
+
return alfresco_create_group(args.get("id"), args.get("display_name"), args.get("parents"))
|
|
63
|
+
if action == "group" and operation == "get":
|
|
64
|
+
return alfresco_get_group(args.get("id"))
|
|
65
|
+
if action == "group" and operation == "get":
|
|
66
|
+
return alfresco_delete_group(args.get("id"))
|
|
67
|
+
return None
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# Extract from args the parameters related to remote access API of Alfresco
|
|
71
|
+
def parse_remote_args(args):
|
|
72
|
+
global alfresco_endpoint
|
|
73
|
+
global alfresco_ticket
|
|
74
|
+
global alfresco_user
|
|
75
|
+
global alfresco_password
|
|
76
|
+
alfresco_endpoint = utils.get_config(args, "alfresco_endpoint", "ALFRESCO_ENDPOINT", None)
|
|
77
|
+
alfresco_ticket = utils.get_secret(args, "alfresco_ticket", "ALFRESCO_TICKET", None)
|
|
78
|
+
alfresco_user = utils.get_secret(args, "alfresco_user", "ALFRESCO_USER", None)
|
|
79
|
+
alfresco_password = utils.get_secret(args, "alfresco_password", "ALFRESCO_PASSWORD", None)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def call_alfresco_api(method, definition, url, **kwargs):
|
|
83
|
+
global alfresco_ticket
|
|
84
|
+
if not alfresco_ticket:
|
|
85
|
+
if not alfresco_user or not alfresco_password:
|
|
86
|
+
raise ValueError("[alfresco] No ticket and no user/password provided")
|
|
87
|
+
alfresco_ticket = alfresco_login(alfresco_user, alfresco_password)
|
|
88
|
+
return utils.call_rest_api(
|
|
89
|
+
method, "alfresco", f"{alfresco_endpoint}/{definition}/versions/1/", url, None, kwargs | {"headers": {"Authorization": f"Basic {base64.b64encode(alfresco_ticket.encode('ascii')).decode()}"}}
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def alfresco_login(user, password):
|
|
94
|
+
utils.info(f"[alfresco] Login of user '{user}' ...")
|
|
95
|
+
return utils.call_rest_api("POST", "alfresco", f"{alfresco_endpoint}/authentication/versions/1/", "tickets", None, {"data": {"userId": user, "password": password}}).json()["entry"]["id"]
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def alfresco_internal_group_name(name):
|
|
99
|
+
return name if name.startswith("GROUP_") else f"GROUP_{name}"
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def alfresco_get_group(name):
|
|
103
|
+
utils.info(f"[alfresco] Get group {name} ...")
|
|
104
|
+
name = alfresco_internal_group_name(name)
|
|
105
|
+
response = call_alfresco_api("GET", "alfresco", f"groups/{name}", ignore_error=True)
|
|
106
|
+
return response and response.json() or None
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def alfresco_delete_group(name):
|
|
110
|
+
utils.info(f"[alfresco] Delete group {name} ...")
|
|
111
|
+
name = alfresco_internal_group_name(name)
|
|
112
|
+
response = call_alfresco_api("DELETE", "alfresco", f"groups/{name}", ignore_error=True)
|
|
113
|
+
return response and response.json() or None
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def alfresco_list_groups():
|
|
117
|
+
utils.info("[alfresco] List groups ...")
|
|
118
|
+
return call_alfresco_api("GET", "alfresco", "groups")
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def alfresco_create_group(name: str, display_name: str, parents=None):
|
|
122
|
+
utils.info(f"[alfresco] Create group '{name}' ...")
|
|
123
|
+
if alfresco_get_group(name):
|
|
124
|
+
utils.debug(f"[alfresco] Group {name} already exists ...")
|
|
125
|
+
else:
|
|
126
|
+
call_alfresco_api("POST", "alfresco", "groups", data={"id": name, "displayName": display_name, "parentIds": parents or []})
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def alfresco_list_sites():
|
|
130
|
+
utils.info("[alfresco] List sites ...")
|
|
131
|
+
return call_alfresco_api("GET", "alfresco", "sites")
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def alfresco_get_site(name):
|
|
135
|
+
utils.info(f"[alfresco] Get site {name} ...")
|
|
136
|
+
response = call_alfresco_api("GET", "alfresco", f"sites/{name}", ignore_error=True)
|
|
137
|
+
return response and response.json()["entry"] or None
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def alfresco_create_site(name, title, description, visibility="PRIVATE"):
|
|
141
|
+
if name != unidecode(name):
|
|
142
|
+
raise ValueError("[alfresco] Site identifier can contain only ASCII chars")
|
|
143
|
+
|
|
144
|
+
utils.info(f"[alfresco] Create site '{name}', '{title}' ({visibility}) ...")
|
|
145
|
+
site_details = alfresco_get_site(name)
|
|
146
|
+
if not title:
|
|
147
|
+
title = name
|
|
148
|
+
if site_details is None:
|
|
149
|
+
call_alfresco_api("POST", "alfresco", "sites", data={"id": name, "title": title, "description": description, "visibility": visibility})
|
|
150
|
+
return
|
|
151
|
+
|
|
152
|
+
utils.info(f"[alfresco] Site {name} already exists")
|
|
153
|
+
current_title = site_details.get("title", "")
|
|
154
|
+
current_description = site_details.get("description", "")
|
|
155
|
+
current_visibility = site_details["visibility"]
|
|
156
|
+
if current_title != (title or "") or current_description != (description or "") or current_visibility != visibility:
|
|
157
|
+
# Update to the new key
|
|
158
|
+
utils.debug(f"[alfresco] Update site '{name}' title, description or visibility ...")
|
|
159
|
+
call_alfresco_api("PUT", "alfresco", f"sites/{name}", data={"title": title, "description": description, "visibility": visibility})
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def alfresco_delete_site(site_id, site_details, remove_from_trash):
|
|
163
|
+
utils.info(f"[alfresco] Delete site {site_id} ...")
|
|
164
|
+
if site_details is None:
|
|
165
|
+
utils.info(f"[alfresco] Site {site_id} does not exist, ignore")
|
|
166
|
+
else:
|
|
167
|
+
call_alfresco_api("DELETE", "alfresco", f"sites/{site_id}")
|
|
168
|
+
|
|
169
|
+
if remove_from_trash:
|
|
170
|
+
nodes = call_alfresco_api("GET", "alfresco", "deleted-nodes", data={"maxItems": 100000}).json()["list"]["entries"]
|
|
171
|
+
node_entry = next(filter(lambda x: x["entry"]["nodeType"] == "st:site" and x["entry"]["name"] == site_id, nodes), None)
|
|
172
|
+
if node_entry:
|
|
173
|
+
# Also remote site from trash
|
|
174
|
+
node_id = node_entry["entry"]["id"]
|
|
175
|
+
utils.info(f"[alfresco] Delete site {site_id} from trash, node_id={node_id} ...")
|
|
176
|
+
call_alfresco_api("DELETE", "alfresco", f"deleted-nodes/{node_id}")
|
|
177
|
+
elif site_details:
|
|
178
|
+
utils.warn(f"[alfresco] Site {site_id} has been deleted, however it is not found in trash")
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def alfresco_create_site_roles(groups_by_name: dict[str, str], definition):
|
|
182
|
+
utils.info("[alfresco] Create Alfresco roles for sites ...")
|
|
183
|
+
|
|
184
|
+
for alfresco_site in definition.get("sites", []):
|
|
185
|
+
alfresco_site_id = alfresco_site.get("id", "")
|
|
186
|
+
alfresco_site_title = alfresco_site.get("title", "")
|
|
187
|
+
alfresco_site_description = alfresco_site.get("description", "")
|
|
188
|
+
alfresco_site_visibility = alfresco_site.get("visibility", "public").upper()
|
|
189
|
+
if alfresco_site_id == "":
|
|
190
|
+
raise ValueError("[alfresco] Missing Alfresco site id")
|
|
191
|
+
if alfresco_site_title == "":
|
|
192
|
+
alfresco_site_title = alfresco_site_id
|
|
193
|
+
if alfresco_site_visibility not in ["PUBLIC", "PRIVATE"]:
|
|
194
|
+
raise ValueError("[alfresco] Invalid Alfresco visibility in site '{alfresco_site_id}'")
|
|
195
|
+
alfresco_create_site(alfresco_site_id, alfresco_site_title, alfresco_site_description, alfresco_site_visibility)
|
|
196
|
+
alfresco_create_roles_scope(groups_by_name, alfresco_site, alfresco_site_id)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def alfresco_delete_site_roles(groups_by_name: dict[str, str], definition, with_data: bool):
|
|
200
|
+
utils.info("[alfresco] Delete Alfresco roles for sites ...")
|
|
201
|
+
|
|
202
|
+
for alfresco_site in definition.get("sites", []):
|
|
203
|
+
alfresco_site_id = alfresco_site.get("id", "")
|
|
204
|
+
if alfresco_site_id == "":
|
|
205
|
+
raise ValueError("[alfresco] Missing Alfresco site id")
|
|
206
|
+
site_details = alfresco_get_site(alfresco_site_id)
|
|
207
|
+
|
|
208
|
+
if site_details:
|
|
209
|
+
alfresco_delete_roles_scope(groups_by_name, alfresco_site, alfresco_site_id)
|
|
210
|
+
if with_data:
|
|
211
|
+
alfresco_delete_site(alfresco_site_id, site_details, True)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def alfresco_create_roles_scope(groups_by_name: dict[str, str], definition, site_id):
|
|
215
|
+
for alfresco_group in definition.get("roles", {}).keys():
|
|
216
|
+
ldap_group = ligoj.get_ldap_group("alfresco", groups_by_name, alfresco_group)
|
|
217
|
+
role_definition = definition["roles"][alfresco_group]
|
|
218
|
+
permissions = role_definition.get("permissions", [])
|
|
219
|
+
if len(permissions) == 0:
|
|
220
|
+
raise ValueError(f"[alfresco] Missing Alfresco permissions in role '{alfresco_group}'")
|
|
221
|
+
alfresco_create_group(ldap_group, ldap_group)
|
|
222
|
+
alfresco_set_permissions(ldap_group, permissions, site_id)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def alfresco_delete_roles_scope(groups_by_name: dict[str, str], definition, site_id):
|
|
226
|
+
for alfresco_group in definition.get("roles", {}).keys():
|
|
227
|
+
ldap_group = ligoj.get_ldap_group("alfresco", groups_by_name, alfresco_group)
|
|
228
|
+
alfresco_remove_permissions(ldap_group, site_id)
|
|
229
|
+
alfresco_delete_group(ldap_group)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def alfresco_set_permissions(group, permissions, site_id):
|
|
233
|
+
for permission in permissions:
|
|
234
|
+
utils.info(f"[alfresco] Set permission '{permission}' to group '{group}' on site '{site_id}' ...")
|
|
235
|
+
group = alfresco_internal_group_name(group)
|
|
236
|
+
member = call_alfresco_api("GET", "alfresco", f"sites/{site_id}/group-members/{group}", ignore_400=True)
|
|
237
|
+
if member:
|
|
238
|
+
call_alfresco_api("PUT", "alfresco", f"sites/{site_id}/group-members/{group}", data={"role": permission})
|
|
239
|
+
else:
|
|
240
|
+
call_alfresco_api("POST", "alfresco", f"sites/{site_id}/group-members", data={"id": group, "role": permission})
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def alfresco_remove_permissions(group, site_id):
|
|
244
|
+
utils.info(f"[alfresco] Remove group '{group}' from site '{site_id}' ...")
|
|
245
|
+
group = alfresco_internal_group_name(group)
|
|
246
|
+
call_alfresco_api("DELETE", "alfresco", f"sites/{site_id}/group-members/{group}", ignore_409=True)
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Licensed under MIT (https://github.com/ligoj/ligoj/blob/master/LICENSE)
|
|
3
|
+
#
|
|
4
|
+
import json
|
|
5
|
+
|
|
6
|
+
from unidecode import unidecode
|
|
7
|
+
|
|
8
|
+
from ligojcli.plugins import ligoj, utils
|
|
9
|
+
|
|
10
|
+
PLUGIN_NAME = "argocd"
|
|
11
|
+
ARGOCD_ALLOWED_ACTIONS = ["get", "create", "sync", "override", "delete", "*"]
|
|
12
|
+
argocd_endpoint: str | None = None
|
|
13
|
+
argocd_token: str | None = None
|
|
14
|
+
argocd_user: str | None = None
|
|
15
|
+
argocd_password: str | None = None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def configure(subparser_service):
|
|
19
|
+
|
|
20
|
+
# argocd
|
|
21
|
+
subparser_action = subparser_service.add_parser("argocd", help="Argo CD related operations").add_subparsers(title="action", help="Action", dest="action")
|
|
22
|
+
subparser_service2 = subparser_action.add_parser("project", help="Project operations").add_subparsers(title="action", help="Action", dest="operation")
|
|
23
|
+
subparser_service2.add_parser("list", help="List projects")
|
|
24
|
+
parser_action = subparser_service2.add_parser("upsert", help="Create or update a project")
|
|
25
|
+
parser_action.add_argument("--name", "-i", help="Project name")
|
|
26
|
+
parser_action.add_argument("--description", help="Description")
|
|
27
|
+
parser_action = subparser_service2.add_parser("get", help="Get project")
|
|
28
|
+
parser_action.add_argument("--name", help="Project name")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def execute_action(service, action, operation, args):
|
|
32
|
+
|
|
33
|
+
# argocd
|
|
34
|
+
if service == "argocd":
|
|
35
|
+
parse_remote_args(args)
|
|
36
|
+
if action == "project" and operation == "upsert":
|
|
37
|
+
return argocd_upsert_project(args.get("name"), args.get("description"))
|
|
38
|
+
if action == "project" and operation == "list":
|
|
39
|
+
return argocd_list_projects()
|
|
40
|
+
if action == "project" and operation == "get":
|
|
41
|
+
return argocd_get_project(args.get("name"))
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# Extract from args the parameters related to remote access API of ArgoCD
|
|
46
|
+
def parse_remote_args(args):
|
|
47
|
+
global argocd_endpoint
|
|
48
|
+
global argocd_token
|
|
49
|
+
global argocd_user
|
|
50
|
+
global argocd_password
|
|
51
|
+
argocd_endpoint = utils.get_config(args, "argocd_endpoint", "ARGOCD_ENDPOINT", None)
|
|
52
|
+
argocd_token = utils.get_secret(args, "argocd_token", "ARGOCD_TOKEN", None)
|
|
53
|
+
argocd_user = utils.get_secret(args, "argocd_user", "ARGOCD_API_TOKEN", None)
|
|
54
|
+
argocd_password = utils.get_secret(args, "argocd_password", "ARGOCD_PASSWORD", None)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def argocd_list_projects():
|
|
58
|
+
utils.info("[argocd] List projects ...")
|
|
59
|
+
return call_argocd_api("GET", "projects").json()["items"]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def argocd_get_project(name):
|
|
63
|
+
utils.info(f"[argocd] Get project {name} ...")
|
|
64
|
+
response = call_argocd_api("GET", f"projects/{name}/detailed", ignore_error=True)
|
|
65
|
+
return response and response.json() or None
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def argocd_upsert_project(name, description: str | None = None):
|
|
69
|
+
if name != unidecode(name):
|
|
70
|
+
raise ValueError("[argocd] Project identifier can contain only ASCII chars")
|
|
71
|
+
|
|
72
|
+
utils.info(f"[argocd] Create project '{name}' ...")
|
|
73
|
+
project_details = argocd_get_project(name)
|
|
74
|
+
if not description:
|
|
75
|
+
description = name
|
|
76
|
+
if project_details is None:
|
|
77
|
+
call_argocd_api("POST", "projects", data={"project": {"metadata": {"name": name}, "spec": {"description": description}}})
|
|
78
|
+
return argocd_get_project(name)
|
|
79
|
+
|
|
80
|
+
utils.info(f"[argocd] Project '{name}' already exists")
|
|
81
|
+
spec = project_details["project"].get("spec")
|
|
82
|
+
if not spec:
|
|
83
|
+
spec = {}
|
|
84
|
+
project_details["spec"] = spec
|
|
85
|
+
current_description = spec.get("description")
|
|
86
|
+
if current_description != (description or ""):
|
|
87
|
+
# Update to the new key
|
|
88
|
+
utils.debug(f"[argocd] Update project '{name}' description ...")
|
|
89
|
+
spec["description"] = description
|
|
90
|
+
call_argocd_api("PUT", f"projects/{name}", data=project_details)
|
|
91
|
+
return project_details
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def argocd_login(user, password):
|
|
95
|
+
utils.info(f"[argocd] Login of user '{user}' ...")
|
|
96
|
+
return utils.call_rest_api("POST", "argocd", f"{argocd_endpoint}/api/v1", "session", None, {"username": user, "password": password}).json()["token"]
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def call_argocd_api(method, url, **kwargs):
|
|
100
|
+
global argocd_token
|
|
101
|
+
if not argocd_token:
|
|
102
|
+
if not argocd_user or not argocd_password:
|
|
103
|
+
raise ValueError("[argocd] No token and no user/password provided")
|
|
104
|
+
argocd_token = argocd_login(argocd_user, argocd_password)
|
|
105
|
+
return utils.call_rest_api(method, "argocd", f"{argocd_endpoint}/api/v1", url, None, kwargs | {"headers": {"Content-Type": "application/json"}, "cookies": {"argocd.token": argocd_token}})
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def argocd_create_project_roles(groups_by_name: dict[str, str], definition):
|
|
109
|
+
utils.info("[argocd] Create ArgoCD roles for projects ...")
|
|
110
|
+
|
|
111
|
+
for argocd_project in definition.get("projects", []):
|
|
112
|
+
argocd_project_id = argocd_project.get("name", "")
|
|
113
|
+
argocd_project_description = argocd_project.get("description", "")
|
|
114
|
+
if argocd_project_id == "":
|
|
115
|
+
raise ValueError("[argocd] Missing ArgoCD project name")
|
|
116
|
+
if argocd_project_description == "":
|
|
117
|
+
argocd_project_description = argocd_project_id
|
|
118
|
+
project_details = argocd_upsert_project(argocd_project_id, argocd_project_description)
|
|
119
|
+
argocd_create_roles_scope(groups_by_name, argocd_project, argocd_project_id, project_details)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def argocd_delete_project_roles(groups_by_name: dict[str, str], definition, with_data):
|
|
123
|
+
utils.info("[argocd] Delete ArgoCD roles for projects ...")
|
|
124
|
+
|
|
125
|
+
for argocd_project in definition.get("projects", []):
|
|
126
|
+
argocd_project_id = argocd_project.get("name", "")
|
|
127
|
+
if argocd_project_id == "":
|
|
128
|
+
raise ValueError("[argocd] Missing ArgoCD project name")
|
|
129
|
+
project_details = argocd_get_project(argocd_project_id)
|
|
130
|
+
if project_details:
|
|
131
|
+
argocd_delete_roles_scope(groups_by_name, argocd_project, argocd_project_id, project_details)
|
|
132
|
+
|
|
133
|
+
if with_data:
|
|
134
|
+
call_argocd_api("DELETE", f"projects/{argocd_project_id}")
|
|
135
|
+
else:
|
|
136
|
+
utils.info(f"[argocd] Project {argocd_project_id} does not exist, ignore")
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def argocd_create_roles_scope(groups_by_name: dict[str, str], definition, project_id, project_details):
|
|
140
|
+
current_project_json = json.dumps(project_details)
|
|
141
|
+
for argocd_group in definition.get("roles", {}).keys():
|
|
142
|
+
ldap_group = ligoj.get_ldap_group("argocd", groups_by_name, argocd_group)
|
|
143
|
+
role_definition = definition["roles"][argocd_group]
|
|
144
|
+
permissions = role_definition.get("permissions", [])
|
|
145
|
+
if len(permissions) == 0:
|
|
146
|
+
raise ValueError(f"[argocd] Missing ArgoCD permissions in role '{argocd_group}'")
|
|
147
|
+
argocd_role = argocd_complete_role(ldap_group, project_details)
|
|
148
|
+
argocd_complete_permissions(ldap_group, permissions, project_id, argocd_role)
|
|
149
|
+
|
|
150
|
+
new_project_json = json.dumps(project_details)
|
|
151
|
+
if new_project_json != current_project_json:
|
|
152
|
+
# at least one change in this project
|
|
153
|
+
utils.debug(f"[argocd] Update project '{project_id}' permissions ...")
|
|
154
|
+
call_argocd_api("PUT", f"projects/{project_id}", data=project_details)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def argocd_delete_roles_scope(groups_by_name: dict[str, str], definition, project_id, project_details):
|
|
158
|
+
current_project_json = json.dumps(project_details)
|
|
159
|
+
for argocd_group in definition.get("roles", {}).keys():
|
|
160
|
+
ldap_group = ligoj.get_ldap_group("argocd", groups_by_name, argocd_group)
|
|
161
|
+
argocd_complete_role_delete(ldap_group, project_details)
|
|
162
|
+
|
|
163
|
+
new_project_json = json.dumps(project_details)
|
|
164
|
+
if new_project_json != current_project_json:
|
|
165
|
+
# at least one change in this project
|
|
166
|
+
utils.debug(f"[argocd] Update project '{project_id}', delete permissions ...")
|
|
167
|
+
call_argocd_api("PUT", f"projects/{project_id}", data=project_details)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def argocd_complete_role(group, project_details):
|
|
171
|
+
roles = project_details["project"]["spec"].get("roles")
|
|
172
|
+
if not roles:
|
|
173
|
+
roles = []
|
|
174
|
+
project_details["project"]["spec"]["roles"] = roles
|
|
175
|
+
|
|
176
|
+
role = next(filter(lambda r: r["name"] == group, roles), None)
|
|
177
|
+
if not role:
|
|
178
|
+
# Add a new role
|
|
179
|
+
role = {"name": group}
|
|
180
|
+
roles.append(role)
|
|
181
|
+
return role
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def argocd_complete_role_delete(group, project_details):
|
|
185
|
+
roles = project_details["project"]["spec"].get("roles", [])
|
|
186
|
+
project_details["project"]["spec"]["roles"] = list(filter(lambda r: r["name"] != group, roles))
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def argocd_complete_permissions(group, permissions, project_id, argocd_role):
|
|
190
|
+
policies = []
|
|
191
|
+
for permission in permissions:
|
|
192
|
+
if "action" not in permission:
|
|
193
|
+
raise ValueError("[argocd] Missing ArgoCD permission action")
|
|
194
|
+
action = permission["action"]
|
|
195
|
+
if action not in ARGOCD_ALLOWED_ACTIONS:
|
|
196
|
+
raise ValueError(f"[argocd] Invalid ArgoCD permission action, allowed: {ARGOCD_ALLOWED_ACTIONS}")
|
|
197
|
+
action_permission = permission.get("permission", "allow")
|
|
198
|
+
if action_permission != "allow" and action_permission != "deny":
|
|
199
|
+
raise ValueError("[argocd] Invalid ArgoCD permission type, allowed: deny/allow")
|
|
200
|
+
application = permission.get("application", "*")
|
|
201
|
+
policies.append(f"p, proj:{project_id}:{group}, applications, {action}, {project_id}/{application}, {action_permission}")
|
|
202
|
+
|
|
203
|
+
# Replace previous groups and policies
|
|
204
|
+
argocd_role["groups"] = [group.lower()]
|
|
205
|
+
argocd_role["policies"] = policies
|