orchestrator-shell 0.1.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.
- orchestrator_shell-0.1.0.dist-info/LICENSE +202 -0
- orchestrator_shell-0.1.0.dist-info/METADATA +36 -0
- orchestrator_shell-0.1.0.dist-info/RECORD +11 -0
- orchestrator_shell-0.1.0.dist-info/WHEEL +4 -0
- wfoshell/__init__.py +15 -0
- wfoshell/__main__.py +313 -0
- wfoshell/product_block.py +131 -0
- wfoshell/resource_type.py +82 -0
- wfoshell/settings.py +28 -0
- wfoshell/state.py +153 -0
- wfoshell/subscripition.py +113 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
Copyright [yyyy] [name of copyright owner]
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright [2024] [Workflow Orchestrator Project]
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: orchestrator-shell
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Shell for interacting with an orchestrator-core database.
|
|
5
|
+
Requires-Python: >=3.11,<3.14
|
|
6
|
+
Classifier: Intended Audience :: Information Technology
|
|
7
|
+
Classifier: Intended Audience :: System Administrators
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python
|
|
11
|
+
Classifier: Topic :: Internet
|
|
12
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
13
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
14
|
+
Classifier: Topic :: Software Development
|
|
15
|
+
Classifier: Typing :: Typed
|
|
16
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
17
|
+
Classifier: Environment :: Console
|
|
18
|
+
Classifier: Intended Audience :: Developers
|
|
19
|
+
Classifier: Intended Audience :: Telecommunications Industry
|
|
20
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
21
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
25
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
|
|
26
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
27
|
+
Requires-Dist: cmd2
|
|
28
|
+
Requires-Dist: orchestrator-core
|
|
29
|
+
Requires-Dist: tabulate
|
|
30
|
+
Requires-Dist: mypy ; extra == "dev"
|
|
31
|
+
Requires-Dist: ruff ; extra == "dev"
|
|
32
|
+
Requires-Dist: types-tabulate ; extra == "dev"
|
|
33
|
+
Requires-Dist: bumpversion ; extra == "dev"
|
|
34
|
+
Project-URL: Documentation, https://workfloworchestrator.org/orchestrator-core/
|
|
35
|
+
Project-URL: Source, https://github.com/workfloworchestrator/wfoshell
|
|
36
|
+
Provides-Extra: dev
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
wfoshell/__init__.py,sha256=jELakGRhZNRoPojr_U7yhSxxnl_IHKDeTjuv4WY2opY,666
|
|
2
|
+
wfoshell/__main__.py,sha256=fiFxm7coqdAmwzPXxSpKhT35yTLVtCtkFpvoLznBb_k,15449
|
|
3
|
+
wfoshell/product_block.py,sha256=YsPb9gPrQj2pmU2rZrYBsG-jKPIQb4rcbCiC69X2sOQ,5455
|
|
4
|
+
wfoshell/resource_type.py,sha256=A6JbrFHRZQRscUKCUFyBCd5YnR9-O_GBKXOXw7Qim5w,3221
|
|
5
|
+
wfoshell/settings.py,sha256=iDnQvOBQtb1SCxnTq052CtZqEPc2nThZnNtt5sLcmhI,999
|
|
6
|
+
wfoshell/state.py,sha256=53A7t9I4NiFkmP1tKbkdXYqWuRv-vlKtWdZE_YmsWrw,6312
|
|
7
|
+
wfoshell/subscripition.py,sha256=0lbcNgqwQPpOv7YNEJJkbxEe5ggEoaQtOGCsb4MD554,4583
|
|
8
|
+
orchestrator_shell-0.1.0.dist-info/LICENSE,sha256=mArBeJXIz9xnP96SRvhrKyjGFnJDg9Eu0SbLyUiKRHk,11408
|
|
9
|
+
orchestrator_shell-0.1.0.dist-info/WHEEL,sha256=CpUCUxeHQbRN5UGRQHYRJorO5Af-Qy_fHMctcQ8DSGI,82
|
|
10
|
+
orchestrator_shell-0.1.0.dist-info/METADATA,sha256=bh0DIURYzfgcEGMk642o5POepMEutOtMIMdY4UAdyX0,1616
|
|
11
|
+
orchestrator_shell-0.1.0.dist-info/RECORD,,
|
wfoshell/__init__.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Copyright 2024 SURF.
|
|
2
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
# you may not use this file except in compliance with the License.
|
|
4
|
+
# You may obtain a copy of the License at
|
|
5
|
+
#
|
|
6
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
#
|
|
8
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
10
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
# See the License for the specific language governing permissions and
|
|
12
|
+
# limitations under the License.
|
|
13
|
+
"""Shell for interacting with an orchestrator-core database."""
|
|
14
|
+
|
|
15
|
+
__version__ = "0.1.0"
|
wfoshell/__main__.py
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
# Copyright 2024 SURF.
|
|
2
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
# you may not use this file except in compliance with the License.
|
|
4
|
+
# You may obtain a copy of the License at
|
|
5
|
+
#
|
|
6
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
#
|
|
8
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
10
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
# See the License for the specific language governing permissions and
|
|
12
|
+
# limitations under the License.
|
|
13
|
+
|
|
14
|
+
from argparse import Namespace
|
|
15
|
+
from datetime import datetime
|
|
16
|
+
|
|
17
|
+
from cmd2 import Cmd, Cmd2ArgumentParser, Statement, with_argparser
|
|
18
|
+
from orchestrator.db import init_database
|
|
19
|
+
|
|
20
|
+
import wfoshell.product_block
|
|
21
|
+
import wfoshell.resource_type
|
|
22
|
+
import wfoshell.state
|
|
23
|
+
import wfoshell.subscripition
|
|
24
|
+
from wfoshell.settings import settings
|
|
25
|
+
from wfoshell.state import state
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class WFOshell(Cmd):
|
|
29
|
+
"""WorkFlow Orchestrator shell."""
|
|
30
|
+
|
|
31
|
+
intro = "Welcome to the WFO shell.\n" "Type help or ? to list commands."
|
|
32
|
+
|
|
33
|
+
def __init__(self) -> None:
|
|
34
|
+
"""WFO shell initialisation."""
|
|
35
|
+
super().__init__(
|
|
36
|
+
persistent_history_file=str(settings.WFOSHELL_HISTFILE),
|
|
37
|
+
persistent_history_length=settings.WFOSHELL_HISTFILE_SIZE,
|
|
38
|
+
)
|
|
39
|
+
self.prompt = "(wfo) "
|
|
40
|
+
self.hidden_commands.extend(["alias", "edit", "macro", "run_pyscript", "run_script", "shell", "shortcuts"])
|
|
41
|
+
init_database(settings) # type: ignore[arg-type]
|
|
42
|
+
|
|
43
|
+
def do_exit(self, line: Statement) -> bool: # noqa: ARG002
|
|
44
|
+
"""Exit the application."""
|
|
45
|
+
return True
|
|
46
|
+
|
|
47
|
+
# subcommand functions for the subscription command
|
|
48
|
+
def subscription_list(self, args: Namespace) -> None: # noqa: ARG002
|
|
49
|
+
"""List subcommand of subscription command."""
|
|
50
|
+
self.poutput(wfoshell.subscripition.subscription_list())
|
|
51
|
+
|
|
52
|
+
def subscription_search(self, args: Namespace) -> None:
|
|
53
|
+
"""Search subcommand of subscription command."""
|
|
54
|
+
self.poutput(wfoshell.subscripition.subscription_search(args.regular_expression))
|
|
55
|
+
|
|
56
|
+
def subscription_select(self, args: Namespace) -> None:
|
|
57
|
+
"""Select subcommand of subscription command."""
|
|
58
|
+
number_of_subscriptions = (
|
|
59
|
+
len(state.filtered_subscriptions) if state.filtered_subscriptions is not None else len(state.subscriptions)
|
|
60
|
+
)
|
|
61
|
+
if not number_of_subscriptions:
|
|
62
|
+
self.pwarning("list or search for subscriptions first")
|
|
63
|
+
elif not 0 <= args.index < number_of_subscriptions:
|
|
64
|
+
self.pwarning(f"selected subscription index not between 0 and {number_of_subscriptions - 1}")
|
|
65
|
+
else:
|
|
66
|
+
self.poutput(wfoshell.subscripition.subscription_select(args.index))
|
|
67
|
+
|
|
68
|
+
def subscription_details(self, args: Namespace) -> None:
|
|
69
|
+
"""Details subcommand of subscription command."""
|
|
70
|
+
if state.subscription_index is None:
|
|
71
|
+
self.pwarning("first select a subscription")
|
|
72
|
+
else:
|
|
73
|
+
self.poutput(
|
|
74
|
+
wfoshell.subscripition.subscription_details(
|
|
75
|
+
subscription_only=args.subscription_only, product_blocks_only=args.product_blocks_only
|
|
76
|
+
)
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
def subscription_update(self, args: Namespace) -> None: # noqa: C901
|
|
80
|
+
"""Update subcommand of subscription command."""
|
|
81
|
+
if state.subscription_index is None:
|
|
82
|
+
self.pwarning("first select a subscription")
|
|
83
|
+
return
|
|
84
|
+
if args.field in ["insync"]:
|
|
85
|
+
if args.new_value.lower() in ["y", "yes", "true"]:
|
|
86
|
+
args.new_value = True
|
|
87
|
+
elif args.new_value.lower() in ["n", "no", "false"]:
|
|
88
|
+
args.new_value = False
|
|
89
|
+
else:
|
|
90
|
+
self.pwarning("expected y, yes, true, n, no or false")
|
|
91
|
+
return
|
|
92
|
+
if args.field in ["start_date", "end_date"]:
|
|
93
|
+
if args.new_value == "":
|
|
94
|
+
args.new_value = None
|
|
95
|
+
else:
|
|
96
|
+
try:
|
|
97
|
+
args.new_value = datetime.fromisoformat(args.new_value)
|
|
98
|
+
except ValueError as value_error:
|
|
99
|
+
self.pwarning(str(value_error))
|
|
100
|
+
return
|
|
101
|
+
if args.new_value.tzinfo is None:
|
|
102
|
+
args.new_value = args.new_value.astimezone()
|
|
103
|
+
wfoshell.subscripition.subscription_update(args.field, args.new_value)
|
|
104
|
+
|
|
105
|
+
# subscription (sub)commands argument parsers
|
|
106
|
+
s_parser = Cmd2ArgumentParser()
|
|
107
|
+
s_subparser = s_parser.add_subparsers(title="subscription subcommands")
|
|
108
|
+
s_list_parser = s_subparser.add_parser("list", help="list all subscriptions from database")
|
|
109
|
+
s_list_parser.set_defaults(func=subscription_list)
|
|
110
|
+
s_search_parser = s_subparser.add_parser("search", help="case insensitive search subscription descriptions")
|
|
111
|
+
s_search_parser.add_argument("regular_expression", type=str, help="match description on regular expression")
|
|
112
|
+
s_search_parser.set_defaults(func=subscription_search)
|
|
113
|
+
s_select_parser = s_subparser.add_parser("select", help="select subscription to work on")
|
|
114
|
+
s_select_parser.add_argument("index", type=int, help="select by index number")
|
|
115
|
+
s_select_parser.set_defaults(func=subscription_select)
|
|
116
|
+
s_details_parser = s_subparser.add_parser("details", help="show subscription details")
|
|
117
|
+
s_details_parser.add_argument("--subscription_only", action="store_true", help="show subscription details only")
|
|
118
|
+
s_details_parser.add_argument("--product_blocks_only", action="store_true", help="show product block details only")
|
|
119
|
+
s_details_parser.set_defaults(func=subscription_details)
|
|
120
|
+
s_update_parser = s_subparser.add_parser("update", help="update subscription field")
|
|
121
|
+
s_update_parser.add_argument(
|
|
122
|
+
"field",
|
|
123
|
+
choices=[
|
|
124
|
+
"description",
|
|
125
|
+
"status",
|
|
126
|
+
"customer_id",
|
|
127
|
+
"insync",
|
|
128
|
+
"start_date",
|
|
129
|
+
"end_date",
|
|
130
|
+
"note",
|
|
131
|
+
],
|
|
132
|
+
help="subscription field",
|
|
133
|
+
)
|
|
134
|
+
s_update_parser.add_argument("new_value", type=str, help="new value for selected subscription field")
|
|
135
|
+
s_update_parser.set_defaults(func=subscription_update)
|
|
136
|
+
|
|
137
|
+
# subscription command
|
|
138
|
+
@with_argparser(s_parser)
|
|
139
|
+
def do_subscription(self, args: Namespace) -> None:
|
|
140
|
+
"""List, search or select subscriptions, update fields, and show details."""
|
|
141
|
+
if func := getattr(args, "func", None):
|
|
142
|
+
func(self, args)
|
|
143
|
+
else:
|
|
144
|
+
self.do_help("subscription")
|
|
145
|
+
|
|
146
|
+
# subcommand functions for the product_block command
|
|
147
|
+
def product_block_list(self, args: Namespace) -> None: # noqa: ARG002
|
|
148
|
+
"""List subcommand of product_block command."""
|
|
149
|
+
if state.subscription_index is None:
|
|
150
|
+
self.pwarning("first select a subscription")
|
|
151
|
+
else:
|
|
152
|
+
self.poutput(wfoshell.product_block.product_block_list())
|
|
153
|
+
|
|
154
|
+
def product_block_select(self, args: Namespace) -> None:
|
|
155
|
+
"""Select subcommand of product_block command."""
|
|
156
|
+
if not (number_of_product_blocks := len(state.selected_product_blocks)):
|
|
157
|
+
self.pwarning("list or search for product_blocks first")
|
|
158
|
+
elif not 0 <= args.index < number_of_product_blocks:
|
|
159
|
+
self.pwarning(f"selected product_block index not between 0 and {number_of_product_blocks - 1}")
|
|
160
|
+
else:
|
|
161
|
+
self.poutput(wfoshell.product_block.product_block_select(args.index))
|
|
162
|
+
|
|
163
|
+
def product_block_details(self, args: Namespace) -> None:
|
|
164
|
+
"""Details subcommand of product_block command."""
|
|
165
|
+
if state.product_block_index is None:
|
|
166
|
+
self.pwarning("first select a product_block")
|
|
167
|
+
else:
|
|
168
|
+
self.poutput(
|
|
169
|
+
wfoshell.product_block.product_block_details(
|
|
170
|
+
product_block_only=args.product_block_only,
|
|
171
|
+
resource_types_only=args.resource_types_only,
|
|
172
|
+
depends_on_only=args.depends_on_only,
|
|
173
|
+
in_use_by_only=args.in_use_by_only,
|
|
174
|
+
)
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
def product_block_depends_on(self, args: Namespace) -> None:
|
|
178
|
+
"""Depends_on subcommand of product_block command."""
|
|
179
|
+
if state.product_block_index is None:
|
|
180
|
+
self.pwarning("first select a product block")
|
|
181
|
+
elif not (number_of_depends_on := len(state.selected_product_block.depends_on)):
|
|
182
|
+
self.pwarning("no depend on product blocks")
|
|
183
|
+
elif not 0 <= args.index < number_of_depends_on:
|
|
184
|
+
self.pwarning(f"selected product_block index not between 0 and {number_of_depends_on - 1}")
|
|
185
|
+
else:
|
|
186
|
+
self.poutput(wfoshell.product_block.product_block_depends_on(args.index))
|
|
187
|
+
|
|
188
|
+
def product_block_in_use_by(self, args: Namespace) -> None:
|
|
189
|
+
"""In_use_by subcommand of product_block command."""
|
|
190
|
+
if state.product_block_index is None:
|
|
191
|
+
self.pwarning("first select a product block")
|
|
192
|
+
elif not (number_of_in_use_by := len(state.selected_product_block.in_use_by)):
|
|
193
|
+
self.pwarning("no in use by product blocks")
|
|
194
|
+
elif not 0 <= args.index < number_of_in_use_by:
|
|
195
|
+
self.pwarning(f"selected product_block index not between 0 and {number_of_in_use_by - 1}")
|
|
196
|
+
else:
|
|
197
|
+
self.poutput(wfoshell.product_block.product_block_in_use_by(args.index))
|
|
198
|
+
|
|
199
|
+
# product_block (sub)commands argument parsers
|
|
200
|
+
pb_parser = Cmd2ArgumentParser()
|
|
201
|
+
pb_subparser = pb_parser.add_subparsers(title="product_block subcommands")
|
|
202
|
+
pb_list_parser = pb_subparser.add_parser("list", help="list product blocks of current selected subscription")
|
|
203
|
+
pb_list_parser.set_defaults(func=product_block_list)
|
|
204
|
+
pb_select_parser = pb_subparser.add_parser("select", help="select product block to work on")
|
|
205
|
+
pb_select_parser.add_argument("index", type=int, help="select by index number")
|
|
206
|
+
pb_select_parser.set_defaults(func=product_block_select)
|
|
207
|
+
pb_details_parser = pb_subparser.add_parser("details", help="show product block details")
|
|
208
|
+
pb_details_parser.add_argument("--product_block_only", action="store_true", help="show product block details only")
|
|
209
|
+
pb_details_parser.add_argument("--resource_types_only", action="store_true", help="show resource type details only")
|
|
210
|
+
pb_details_parser.add_argument("--depends_on_only", action="store_true", help="show depends on details only")
|
|
211
|
+
pb_details_parser.add_argument("--in_use_by_only", action="store_true", help="show in use by details only")
|
|
212
|
+
pb_details_parser.set_defaults(func=product_block_details)
|
|
213
|
+
pb_depends_on_parser = pb_subparser.add_parser("depends_on", help="show depends on product blocks")
|
|
214
|
+
pb_depends_on_parser.add_argument("index", type=int, help="select by index number")
|
|
215
|
+
pb_depends_on_parser.set_defaults(func=product_block_depends_on)
|
|
216
|
+
pb_is_use_by_parser = pb_subparser.add_parser("in_use_by", help="show in use by product blocks")
|
|
217
|
+
pb_is_use_by_parser.add_argument("index", type=int, help="select by index number")
|
|
218
|
+
pb_is_use_by_parser.set_defaults(func=product_block_in_use_by)
|
|
219
|
+
|
|
220
|
+
# product_block command
|
|
221
|
+
@with_argparser(pb_parser)
|
|
222
|
+
def do_product_block(self, args: Namespace) -> None:
|
|
223
|
+
"""List and select product blocks, show details, or follow depends on and in use by product blocks."""
|
|
224
|
+
if func := getattr(args, "func", None):
|
|
225
|
+
func(self, args)
|
|
226
|
+
else:
|
|
227
|
+
self.do_help("product_block")
|
|
228
|
+
|
|
229
|
+
# subcommand functions for the resource_type command
|
|
230
|
+
def resource_type_list(self, args: Namespace) -> None: # noqa: ARG002
|
|
231
|
+
"""List subcommand of resource_type command."""
|
|
232
|
+
if state.product_block_index is None:
|
|
233
|
+
self.pwarning("first select a product block")
|
|
234
|
+
else:
|
|
235
|
+
self.poutput(wfoshell.resource_type.resource_type_list())
|
|
236
|
+
|
|
237
|
+
def resource_type_select(self, args: Namespace) -> None:
|
|
238
|
+
"""Select subcommand of resource_type command."""
|
|
239
|
+
if not (number_of_resource_types := len(state.selected_resource_types)):
|
|
240
|
+
self.pwarning("list or search for resource_types first")
|
|
241
|
+
elif not 0 <= args.index < number_of_resource_types:
|
|
242
|
+
self.pwarning(f"selected resource_type index not between 0 and {number_of_resource_types - 1}")
|
|
243
|
+
else:
|
|
244
|
+
self.poutput(wfoshell.resource_type.resource_type_select(args.index))
|
|
245
|
+
|
|
246
|
+
def resource_type_details(self, args: Namespace) -> None: # noqa: ARG002
|
|
247
|
+
"""Details subcommand of resource_type command."""
|
|
248
|
+
if state.resource_type_index is None:
|
|
249
|
+
self.pwarning("first select a resource_type")
|
|
250
|
+
else:
|
|
251
|
+
self.poutput(wfoshell.resource_type.resource_type_details())
|
|
252
|
+
|
|
253
|
+
def resource_type_update(self, args: Namespace) -> None:
|
|
254
|
+
"""Update subcommand of resource_type command."""
|
|
255
|
+
if state.resource_type_index is None:
|
|
256
|
+
self.pwarning("first select a resource_type")
|
|
257
|
+
else:
|
|
258
|
+
wfoshell.resource_type.resource_type_update(args.new_value)
|
|
259
|
+
|
|
260
|
+
# resource_type (sub)commands argument parsers
|
|
261
|
+
rt_parser = Cmd2ArgumentParser()
|
|
262
|
+
rt_subparser = rt_parser.add_subparsers(title="resource_type subcommands")
|
|
263
|
+
rt_list_parser = rt_subparser.add_parser("list", help="list resource types of current selected product block")
|
|
264
|
+
rt_list_parser.set_defaults(func=resource_type_list)
|
|
265
|
+
rt_select_parser = rt_subparser.add_parser("select", help="select resource type to work on")
|
|
266
|
+
rt_select_parser.add_argument("index", type=int, help="select by index number")
|
|
267
|
+
rt_select_parser.set_defaults(func=resource_type_select)
|
|
268
|
+
rt_details_parser = rt_subparser.add_parser("details", help="show resource type details")
|
|
269
|
+
rt_details_parser.set_defaults(func=resource_type_details)
|
|
270
|
+
rt_update_parser = rt_subparser.add_parser("update", help="update selected resource type")
|
|
271
|
+
rt_update_parser.add_argument("new_value", type=str, help="new value for selected resource type")
|
|
272
|
+
rt_update_parser.set_defaults(func=resource_type_update)
|
|
273
|
+
|
|
274
|
+
# resource_type command
|
|
275
|
+
@with_argparser(rt_parser)
|
|
276
|
+
def do_resource_type(self, args: Namespace) -> None:
|
|
277
|
+
"""List, select and update resource types, and show details."""
|
|
278
|
+
if func := getattr(args, "func", None):
|
|
279
|
+
func(self, args)
|
|
280
|
+
else:
|
|
281
|
+
self.do_help("resource_type")
|
|
282
|
+
|
|
283
|
+
# subcommand functions for the state command
|
|
284
|
+
def state_summary(self, args: Namespace) -> None: # noqa: ARG002
|
|
285
|
+
"""summary subcommand of state command."""
|
|
286
|
+
if summary := state.summary:
|
|
287
|
+
self.poutput(summary)
|
|
288
|
+
|
|
289
|
+
def state_details(self, args: Namespace) -> None: # noqa: ARG002
|
|
290
|
+
"""details subcommand of state command."""
|
|
291
|
+
self.poutput(state.details)
|
|
292
|
+
|
|
293
|
+
# state (sub)commands argument parsers
|
|
294
|
+
state_parser = Cmd2ArgumentParser()
|
|
295
|
+
state_subparser = state_parser.add_subparsers(title="state subcommands")
|
|
296
|
+
state_summary_parser = state_subparser.add_parser("summary", help="show state summary")
|
|
297
|
+
state_summary_parser.set_defaults(func=state_summary)
|
|
298
|
+
state_details_parser = state_subparser.add_parser("details", help="show state details")
|
|
299
|
+
state_details_parser.set_defaults(func=state_details)
|
|
300
|
+
|
|
301
|
+
# resource_type command
|
|
302
|
+
@with_argparser(state_parser)
|
|
303
|
+
def do_state(self, args: Namespace) -> None:
|
|
304
|
+
"""Show state summary or details."""
|
|
305
|
+
if func := getattr(args, "func", None):
|
|
306
|
+
func(self, args)
|
|
307
|
+
else:
|
|
308
|
+
self.do_help("state")
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
if __name__ == "__main__":
|
|
312
|
+
shell = WFOshell()
|
|
313
|
+
shell.cmdloop()
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# Copyright 2024 SURF.
|
|
2
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
# you may not use this file except in compliance with the License.
|
|
4
|
+
# You may obtain a copy of the License at
|
|
5
|
+
#
|
|
6
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
#
|
|
8
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
10
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
# See the License for the specific language governing permissions and
|
|
12
|
+
# limitations under the License.
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
from orchestrator.db import SubscriptionInstanceTable
|
|
16
|
+
from tabulate import tabulate
|
|
17
|
+
|
|
18
|
+
from wfoshell.resource_type import resource_type_table
|
|
19
|
+
from wfoshell.state import all_resource_types, state
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def product_block_table(product_blocks: list[SubscriptionInstanceTable]) -> str:
|
|
23
|
+
"""Return indexed table of product blocks."""
|
|
24
|
+
max_rt_width = max([len(rt.resource_type.resource_type) for pb in product_blocks for rt in all_resource_types(pb)])
|
|
25
|
+
return tabulate(
|
|
26
|
+
[
|
|
27
|
+
[
|
|
28
|
+
tabulate(
|
|
29
|
+
[
|
|
30
|
+
["name", product_block.product_block.name],
|
|
31
|
+
["resource types", resource_type_table(all_resource_types(product_block), max_rt_width)],
|
|
32
|
+
],
|
|
33
|
+
tablefmt="plain",
|
|
34
|
+
)
|
|
35
|
+
]
|
|
36
|
+
for product_block in product_blocks
|
|
37
|
+
],
|
|
38
|
+
tablefmt="plain",
|
|
39
|
+
disable_numparse=True,
|
|
40
|
+
showindex=True,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def details_product_block(product_block: SubscriptionInstanceTable) -> list[tuple[str, str]]:
|
|
45
|
+
"""Return list of tuples with product block details only."""
|
|
46
|
+
return [
|
|
47
|
+
("name", product_block.product_block.name),
|
|
48
|
+
("subscription_instance_id", product_block.subscription_instance_id),
|
|
49
|
+
("subscription_id", product_block.subscription_id),
|
|
50
|
+
("product_block_id", product_block.product_block_id),
|
|
51
|
+
("label", product_block.label),
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def details_resource_types(product_block: SubscriptionInstanceTable) -> list[tuple[str, str]]:
|
|
56
|
+
"""Return list of tuples with resource type details only."""
|
|
57
|
+
return [
|
|
58
|
+
("resource types", resource_type_table(all_resource_types(product_block))),
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def details_depends_on(product_block: SubscriptionInstanceTable) -> list[tuple[str, str]]:
|
|
63
|
+
"""Return list of tuples with depends on details only."""
|
|
64
|
+
return [
|
|
65
|
+
("depends_on", product_block_table(product_block.depends_on) if product_block.depends_on else ""),
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def details_in_use_by(product_block: SubscriptionInstanceTable) -> list[tuple[str, str]]:
|
|
70
|
+
"""Return list of tuples with in use by details only."""
|
|
71
|
+
return [
|
|
72
|
+
("in_use_by", product_block_table(product_block.in_use_by) if product_block.in_use_by else ""),
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def details_all(product_block: SubscriptionInstanceTable) -> list[tuple[str, str]]:
|
|
77
|
+
"""Return list of tuples with all product block details."""
|
|
78
|
+
return (
|
|
79
|
+
details_product_block(product_block)
|
|
80
|
+
+ details_resource_types(product_block)
|
|
81
|
+
+ details_depends_on(product_block)
|
|
82
|
+
+ details_in_use_by(product_block)
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def product_block_list() -> str:
|
|
87
|
+
"""Implementation of the 'product_block list' subcommand."""
|
|
88
|
+
return product_block_table(state.selected_product_blocks)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def product_block_select(index: int) -> str:
|
|
92
|
+
"""Implementation of the 'product_block select' subcommand."""
|
|
93
|
+
state.product_block_index = index
|
|
94
|
+
state.resource_type_index = None
|
|
95
|
+
return state.summary
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def product_block_details(
|
|
99
|
+
product_block_only: bool, resource_types_only: bool, depends_on_only: bool, in_use_by_only: bool
|
|
100
|
+
) -> str:
|
|
101
|
+
"""Implementation of the 'product_block details' subcommand."""
|
|
102
|
+
if product_block_only:
|
|
103
|
+
return tabulate(details_product_block(state.selected_product_block), tablefmt="plain")
|
|
104
|
+
elif resource_types_only: # noqa: RET505
|
|
105
|
+
return tabulate(details_resource_types(state.selected_product_block), tablefmt="plain")
|
|
106
|
+
elif depends_on_only:
|
|
107
|
+
return tabulate(details_depends_on(state.selected_product_block), tablefmt="plain")
|
|
108
|
+
elif in_use_by_only:
|
|
109
|
+
return tabulate(details_in_use_by(state.selected_product_block), tablefmt="plain")
|
|
110
|
+
else:
|
|
111
|
+
return tabulate(details_all(state.selected_product_block), tablefmt="plain")
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def product_block_depends_on(index: int) -> str:
|
|
115
|
+
"""Implementation of the 'product_block depends_on' subcommand."""
|
|
116
|
+
depends_on_product_block = state.selected_product_block.depends_on[index]
|
|
117
|
+
state.subscription_index = state.subscriptions.index(depends_on_product_block.subscription)
|
|
118
|
+
# note that the selected_product_blocks list below is of the subscription selected just above
|
|
119
|
+
state.product_block_index = state.selected_product_blocks.index(depends_on_product_block)
|
|
120
|
+
state.resource_type_index = None
|
|
121
|
+
return state.summary
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def product_block_in_use_by(index: int) -> str:
|
|
125
|
+
"""Implementation of the 'product_block in_use_by' subcommand."""
|
|
126
|
+
in_use_by_product_block = state.selected_product_block.in_use_by[index]
|
|
127
|
+
state.subscription_index = state.subscriptions.index(in_use_by_product_block.subscription)
|
|
128
|
+
# note that the selected_product_blocks list below is of the subscription selected just above
|
|
129
|
+
state.product_block_index = state.selected_product_blocks.index(in_use_by_product_block)
|
|
130
|
+
state.resource_type_index = None
|
|
131
|
+
return state.summary
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Copyright 2024 SURF.
|
|
2
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
# you may not use this file except in compliance with the License.
|
|
4
|
+
# You may obtain a copy of the License at
|
|
5
|
+
#
|
|
6
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
#
|
|
8
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
10
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
# See the License for the specific language governing permissions and
|
|
12
|
+
# limitations under the License.
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
import tabulate
|
|
16
|
+
from orchestrator.db import SubscriptionInstanceValueTable, db, transactional
|
|
17
|
+
from structlog import get_logger
|
|
18
|
+
|
|
19
|
+
from wfoshell.state import sorted_resource_types, state
|
|
20
|
+
|
|
21
|
+
logger = get_logger(__name__)
|
|
22
|
+
tabulate.PRESERVE_WHITESPACE = True
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def resource_type_table(resource_types: list[SubscriptionInstanceValueTable], width: int = 0) -> str:
|
|
26
|
+
"""Return indexed table of resource types, with name optionally aligned on width."""
|
|
27
|
+
return tabulate.tabulate(
|
|
28
|
+
[
|
|
29
|
+
[
|
|
30
|
+
resource_type.resource_type.resource_type.ljust(width),
|
|
31
|
+
resource_type.value if resource_type.value is not None else "<unset or non-scalar>",
|
|
32
|
+
]
|
|
33
|
+
for resource_type in sorted_resource_types(resource_types)
|
|
34
|
+
],
|
|
35
|
+
tablefmt="plain",
|
|
36
|
+
disable_numparse=True,
|
|
37
|
+
showindex=True,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def details(resource_type: SubscriptionInstanceValueTable | None) -> list[tuple[str, str]]:
|
|
42
|
+
"""Return list of tuples with resource type detail information."""
|
|
43
|
+
if resource_type is None:
|
|
44
|
+
return []
|
|
45
|
+
return [
|
|
46
|
+
("resource_type", resource_type.resource_type.resource_type),
|
|
47
|
+
("value", resource_type.value),
|
|
48
|
+
("subscription_instance_value_id", resource_type.subscription_instance_value_id),
|
|
49
|
+
("subscription_instance_id", resource_type.subscription_instance_id),
|
|
50
|
+
("resource_type_id", resource_type.resource_type_id),
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def resource_type_list() -> str:
|
|
55
|
+
"""Implementation of the 'resource_type list' subcommand."""
|
|
56
|
+
return resource_type_table(state.selected_resource_types)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def resource_type_select(index: int) -> str:
|
|
60
|
+
"""Implementation of the 'resource_type select' subcommand."""
|
|
61
|
+
state.resource_type_index = index
|
|
62
|
+
return state.summary
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def resource_type_details() -> str:
|
|
66
|
+
"""Implementation of the 'resource_type details' subcommand."""
|
|
67
|
+
return tabulate.tabulate(details(state.selected_resource_type), tablefmt="plain")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def resource_type_update(new_value: str) -> None:
|
|
71
|
+
"""Implementation of the 'resource_type update' subcommand."""
|
|
72
|
+
with transactional(db, logger):
|
|
73
|
+
if state.selected_resource_type.value is None:
|
|
74
|
+
# add previously unset resource type to list of product block values
|
|
75
|
+
state.selected_product_block.values.append(
|
|
76
|
+
SubscriptionInstanceValueTable(
|
|
77
|
+
resource_type_id=state.selected_resource_type.resource_type.resource_type_id, value=new_value
|
|
78
|
+
)
|
|
79
|
+
)
|
|
80
|
+
else:
|
|
81
|
+
# otherwise just update the existing resource type value
|
|
82
|
+
state.selected_resource_type.value = new_value
|
wfoshell/settings.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Copyright 2024 SURF.
|
|
2
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
# you may not use this file except in compliance with the License.
|
|
4
|
+
# You may obtain a copy of the License at
|
|
5
|
+
#
|
|
6
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
#
|
|
8
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
10
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
# See the License for the specific language governing permissions and
|
|
12
|
+
# limitations under the License.
|
|
13
|
+
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
from pydantic.networks import PostgresDsn
|
|
17
|
+
from pydantic_settings import BaseSettings
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Settings(BaseSettings):
|
|
21
|
+
"""WFO Shell settings."""
|
|
22
|
+
|
|
23
|
+
DATABASE_URI: PostgresDsn = "postgresql://nwa:nwa@localhost/orchestrator-core" # type: ignore[assignment]
|
|
24
|
+
WFOSHELL_HISTFILE: Path = Path("~/.wfoshell_history").expanduser()
|
|
25
|
+
WFOSHELL_HISTFILE_SIZE: int = 1000
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
settings = Settings()
|
wfoshell/state.py
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# Copyright 2024 SURF.
|
|
2
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
# you may not use this file except in compliance with the License.
|
|
4
|
+
# You may obtain a copy of the License at
|
|
5
|
+
#
|
|
6
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
#
|
|
8
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
10
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
# See the License for the specific language governing permissions and
|
|
12
|
+
# limitations under the License.
|
|
13
|
+
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
|
|
16
|
+
from orchestrator.db import SubscriptionInstanceTable, SubscriptionInstanceValueTable, SubscriptionTable
|
|
17
|
+
from tabulate import tabulate
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class State:
|
|
22
|
+
"""State that is shared between the WFO shell commands."""
|
|
23
|
+
|
|
24
|
+
subscriptions: list[SubscriptionTable] = field(default_factory=list)
|
|
25
|
+
filtered_subscriptions: list[SubscriptionTable] | None = None
|
|
26
|
+
subscription_index: int | None = None
|
|
27
|
+
product_block_index: int | None = None
|
|
28
|
+
resource_type_index: int | None = None
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def selected_subscription(self) -> SubscriptionTable:
|
|
32
|
+
"""Return the subscription indexed by subscription_index."""
|
|
33
|
+
if self.subscription_index is not None:
|
|
34
|
+
return self.subscriptions[self.subscription_index]
|
|
35
|
+
raise IndexError("subscription_index not set")
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def selected_product_blocks(self) -> list[SubscriptionInstanceTable]:
|
|
39
|
+
"""Return sorted list of product blocks for the subscription indexed by subscription_index."""
|
|
40
|
+
return (
|
|
41
|
+
(sorted_product_blocks(self.selected_subscription.instances)) if self.subscription_index is not None else []
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def selected_product_block(self) -> SubscriptionInstanceTable:
|
|
46
|
+
"""Return the product block indexed by product_block_index."""
|
|
47
|
+
if self.product_block_index is not None:
|
|
48
|
+
return self.selected_product_blocks[self.product_block_index]
|
|
49
|
+
raise IndexError("product_block_index not set")
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def selected_resource_types(self) -> list[SubscriptionInstanceValueTable]:
|
|
53
|
+
"""Return sorted list of resource types for the product block indexed by product_block_index."""
|
|
54
|
+
return (
|
|
55
|
+
sorted_resource_types(all_resource_types(self.selected_product_block))
|
|
56
|
+
if self.product_block_index is not None
|
|
57
|
+
else []
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def selected_resource_type(self) -> SubscriptionInstanceValueTable:
|
|
62
|
+
"""Return the resource type indexed by resource_type_index."""
|
|
63
|
+
if self.resource_type_index is not None:
|
|
64
|
+
return self.selected_resource_types[self.resource_type_index]
|
|
65
|
+
raise IndexError("resource_type_index not set")
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def summary(self) -> str:
|
|
69
|
+
"""List summary of the selected subscription, product block and resource type."""
|
|
70
|
+
summary = []
|
|
71
|
+
if self.subscription_index is not None:
|
|
72
|
+
summary.append(
|
|
73
|
+
(
|
|
74
|
+
"subscription",
|
|
75
|
+
self.selected_subscription.description,
|
|
76
|
+
self.selected_subscription.subscription_id,
|
|
77
|
+
)
|
|
78
|
+
)
|
|
79
|
+
if self.product_block_index is not None:
|
|
80
|
+
summary.append(
|
|
81
|
+
(
|
|
82
|
+
"product block",
|
|
83
|
+
self.selected_product_block.product_block.name,
|
|
84
|
+
self.selected_product_block.subscription_instance_id,
|
|
85
|
+
),
|
|
86
|
+
)
|
|
87
|
+
if self.resource_type_index is not None:
|
|
88
|
+
rt = self.selected_resource_type
|
|
89
|
+
summary.append(
|
|
90
|
+
(
|
|
91
|
+
"resource_type",
|
|
92
|
+
rt.resource_type.resource_type,
|
|
93
|
+
rt.subscription_instance_value_id if rt.value is not None else "<unset or non-scalar>",
|
|
94
|
+
),
|
|
95
|
+
)
|
|
96
|
+
return tabulate(summary, tablefmt="plain")
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def details(self) -> str:
|
|
100
|
+
"""Show state details."""
|
|
101
|
+
return tabulate(
|
|
102
|
+
[
|
|
103
|
+
("number of subscriptions", len(self.subscriptions)),
|
|
104
|
+
(
|
|
105
|
+
"number of filtered subscriptions",
|
|
106
|
+
len(self.filtered_subscriptions) if self.filtered_subscriptions is not None else "0",
|
|
107
|
+
),
|
|
108
|
+
("subscription index", self.subscription_index if self.subscription_index is not None else "unset"),
|
|
109
|
+
("product block index", self.product_block_index if self.subscription_index is not None else "unset"),
|
|
110
|
+
("resource type index", self.resource_type_index if self.subscription_index is not None else "unset"),
|
|
111
|
+
("currently selected", self.summary),
|
|
112
|
+
],
|
|
113
|
+
tablefmt="plain",
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
state = State()
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def all_resource_types(product_block: SubscriptionInstanceTable) -> list[SubscriptionInstanceValueTable]:
|
|
121
|
+
"""Add optional unset resource type(s) with value None to list of already set resource types."""
|
|
122
|
+
return list(
|
|
123
|
+
(
|
|
124
|
+
{
|
|
125
|
+
rt.resource_type: SubscriptionInstanceValueTable(
|
|
126
|
+
resource_type_id=rt.resource_type_id, resource_type=rt, value=None
|
|
127
|
+
)
|
|
128
|
+
for rt in product_block.product_block.resource_types
|
|
129
|
+
}
|
|
130
|
+
| {v.resource_type.resource_type: v for v in product_block.values}
|
|
131
|
+
).values()
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def sorted_subscriptions(subscriptions: list[SubscriptionTable]) -> list[SubscriptionTable]:
|
|
136
|
+
"""Sort subscriptions on description."""
|
|
137
|
+
return sorted(subscriptions, key=lambda subscription: subscription.description)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def sorted_product_blocks(product_blocks: list[SubscriptionInstanceTable]) -> list[SubscriptionInstanceTable]:
|
|
141
|
+
"""Sort product blocks on product block name."""
|
|
142
|
+
return sorted(
|
|
143
|
+
product_blocks,
|
|
144
|
+
key=lambda subscription_instance: subscription_instance.product_block.name,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def sorted_resource_types(resource_types: list[SubscriptionInstanceValueTable]) -> list[SubscriptionInstanceValueTable]:
|
|
149
|
+
"""Sort resource types on resource type name."""
|
|
150
|
+
return sorted(
|
|
151
|
+
resource_types,
|
|
152
|
+
key=lambda subscription_instance_value: subscription_instance_value.resource_type.resource_type,
|
|
153
|
+
)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Copyright 2024 SURF.
|
|
2
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
# you may not use this file except in compliance with the License.
|
|
4
|
+
# You may obtain a copy of the License at
|
|
5
|
+
#
|
|
6
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
#
|
|
8
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
10
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
# See the License for the specific language governing permissions and
|
|
12
|
+
# limitations under the License.
|
|
13
|
+
|
|
14
|
+
import re
|
|
15
|
+
from datetime import datetime
|
|
16
|
+
|
|
17
|
+
from orchestrator.db import SubscriptionTable, db, transactional
|
|
18
|
+
from structlog import get_logger
|
|
19
|
+
from tabulate import tabulate
|
|
20
|
+
|
|
21
|
+
from wfoshell.product_block import product_block_table
|
|
22
|
+
from wfoshell.state import sorted_subscriptions, state
|
|
23
|
+
|
|
24
|
+
logger = get_logger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def indexed_subscription_list(subscriptions: list[SubscriptionTable]) -> str:
|
|
28
|
+
"""Return tabulated indexed list of subscriptions."""
|
|
29
|
+
return tabulate(
|
|
30
|
+
[(subscription.description, subscription.subscription_id) for subscription in subscriptions],
|
|
31
|
+
tablefmt="plain",
|
|
32
|
+
disable_numparse=True,
|
|
33
|
+
showindex=True,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def query_db() -> list[SubscriptionTable]:
|
|
38
|
+
"""Return sorted and list of subscriptions from the database."""
|
|
39
|
+
return sorted_subscriptions(SubscriptionTable.query.all())
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def filtered_subscriptions(regular_expression: str, subscriptions: list[SubscriptionTable]) -> list[SubscriptionTable]:
|
|
43
|
+
"""Return filtered list of subscriptions."""
|
|
44
|
+
pattern = re.compile(regular_expression, flags=re.IGNORECASE)
|
|
45
|
+
return list(filter(lambda subscription: pattern.search(subscription.description), subscriptions))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def details_subscription_only(subscription: SubscriptionTable) -> list[tuple[str, str]]:
|
|
49
|
+
"""Return list of tuples with subscription details only."""
|
|
50
|
+
return [
|
|
51
|
+
("description", subscription.description),
|
|
52
|
+
("subscription_id", subscription.subscription_id),
|
|
53
|
+
("status", subscription.status),
|
|
54
|
+
("product_id", subscription.product_id),
|
|
55
|
+
("customer_id", subscription.customer_id),
|
|
56
|
+
("insync", subscription.insync),
|
|
57
|
+
("start_date", subscription.start_date),
|
|
58
|
+
("end_date", subscription.end_date),
|
|
59
|
+
("note", subscription.note),
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def details_product_blocks_only() -> list[tuple[str, str]]:
|
|
64
|
+
"""Return list of tuples with product blocks details only."""
|
|
65
|
+
return [
|
|
66
|
+
("product block(s)", product_block_table(state.selected_product_blocks)),
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def details_all(subscription: SubscriptionTable) -> list[tuple[str, str]]:
|
|
71
|
+
"""Return list of tuples with all subscription details."""
|
|
72
|
+
return details_subscription_only(subscription) + details_product_blocks_only()
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def subscription_list() -> str:
|
|
76
|
+
"""Add list of all subscriptions to the state and return this list tabulated and indexed."""
|
|
77
|
+
state.subscriptions = query_db()
|
|
78
|
+
state.filtered_subscriptions = None
|
|
79
|
+
return indexed_subscription_list(state.subscriptions)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def subscription_search(regular_expression: str) -> str:
|
|
83
|
+
"""Add list of filtered subscriptions to the state and return this list tabulated and indexed."""
|
|
84
|
+
state.subscriptions = query_db()
|
|
85
|
+
state.filtered_subscriptions = filtered_subscriptions(regular_expression, state.subscriptions)
|
|
86
|
+
return indexed_subscription_list(state.filtered_subscriptions)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def subscription_select(index: int) -> str:
|
|
90
|
+
"""Implementation of the 'subscription select' subcommand."""
|
|
91
|
+
if state.filtered_subscriptions is None:
|
|
92
|
+
state.subscription_index = index
|
|
93
|
+
else:
|
|
94
|
+
state.subscription_index = state.subscriptions.index(state.filtered_subscriptions[index])
|
|
95
|
+
state.product_block_index = None
|
|
96
|
+
state.resource_type_index = None
|
|
97
|
+
return state.summary
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def subscription_details(subscription_only: bool, product_blocks_only: bool) -> str:
|
|
101
|
+
"""Implementation of the 'subscription details' subcommand."""
|
|
102
|
+
if subscription_only:
|
|
103
|
+
return tabulate(details_subscription_only(state.selected_subscription), tablefmt="plain")
|
|
104
|
+
elif product_blocks_only: # noqa: RET505
|
|
105
|
+
return tabulate(details_product_blocks_only(), tablefmt="plain")
|
|
106
|
+
else:
|
|
107
|
+
return tabulate(details_all(state.selected_subscription), tablefmt="plain")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def subscription_update(field: str, new_value: str | bool | datetime | None) -> None:
|
|
111
|
+
"""Implementation of the 'subscription update' subcommand."""
|
|
112
|
+
with transactional(db, logger):
|
|
113
|
+
setattr(state.selected_subscription, field, new_value)
|