joulectl 0.1.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- joulectl-0.1.3/AUTHORS +7 -0
- joulectl-0.1.3/LICENSE +21 -0
- joulectl-0.1.3/PKG-INFO +113 -0
- joulectl-0.1.3/README.md +96 -0
- joulectl-0.1.3/joulectl/__init__.py +0 -0
- joulectl-0.1.3/joulectl/commands/__init__.py +0 -0
- joulectl-0.1.3/joulectl/commands/deploycmds.py +72 -0
- joulectl-0.1.3/joulectl/commands/inspectcmds.py +70 -0
- joulectl-0.1.3/joulectl/commands/listcmds.py +134 -0
- joulectl-0.1.3/joulectl/commands/ucmgmtcmds.py +47 -0
- joulectl-0.1.3/joulectl/commands/undeploycmds.py +74 -0
- joulectl-0.1.3/joulectl/config/__init__.py +0 -0
- joulectl-0.1.3/joulectl/config/config.py +19 -0
- joulectl-0.1.3/joulectl/config/file.py +57 -0
- joulectl-0.1.3/joulectl/joule.py +108 -0
- joulectl-0.1.3/joulectl/joulecomms.py +21 -0
- joulectl-0.1.3/pyproject.toml +40 -0
joulectl-0.1.3/AUTHORS
ADDED
joulectl-0.1.3/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Fractalworks
|
|
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.
|
joulectl-0.1.3/PKG-INFO
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: joulectl
|
|
3
|
+
Version: 0.1.3
|
|
4
|
+
Summary: Joule CLI tool to manage Joule transports, streams and use cases
|
|
5
|
+
License: MIT
|
|
6
|
+
Keywords: joule,streaming
|
|
7
|
+
Author: Lyndon Adams
|
|
8
|
+
Author-email: lyndon.adams@fractalworks.io
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Requires-Dist: click (>=8.1.8,<9.0.0)
|
|
12
|
+
Requires-Dist: requests (>=2.32.3,<3.0.0)
|
|
13
|
+
Requires-Dist: tabulate (>=0.9.0,<1.0.0)
|
|
14
|
+
Project-URL: Documentation, https://docs.fractalworks.io/joule
|
|
15
|
+
Project-URL: Homepage, https://https://getjoule.io/
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
## Python Joule Tools
|
|
19
|
+
Joule is a low-code real-time stream processing engine.
|
|
20
|
+
|
|
21
|
+
Joule currently supports wide range of data sources (such as Kafka, MQTT, RabbitMQ, InfuxDB, Minio S3 etc),
|
|
22
|
+
machine learning inference, custom analytics, event windows
|
|
23
|
+
|
|
24
|
+
## Why Should I Use This?
|
|
25
|
+
When you need to deploy and manage use cases within the Joule processing environment.
|
|
26
|
+
|
|
27
|
+
## Features
|
|
28
|
+
- Deploy transports, streams and use cases.
|
|
29
|
+
- Inspect all deployed specifications for transports, streams and use cases.
|
|
30
|
+
- List all deployed transports, streams and use cases.
|
|
31
|
+
- Undeploy transports, streams and use cases.
|
|
32
|
+
- Use case management to pause and resume processing.
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
```bash
|
|
36
|
+
$ pip install joulectl
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Using joulectl
|
|
40
|
+
The tool provides a number of commands to
|
|
41
|
+
support the deployment and management of Joule use case configurations
|
|
42
|
+
|
|
43
|
+
### Setup tool
|
|
44
|
+
First create a configuration file
|
|
45
|
+
```bash
|
|
46
|
+
joulectl config create
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Now update the Joule host which you would like to connect too.
|
|
50
|
+
The default is ```localhost:9080```
|
|
51
|
+
```bash
|
|
52
|
+
joulectl config update --host 192.168.86.48:9080
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
You are now ready to deploy and manage joule remotely.
|
|
56
|
+
|
|
57
|
+
## Commands
|
|
58
|
+
List of available commands
|
|
59
|
+
|
|
60
|
+
### deploy
|
|
61
|
+
Deploy command for transports, streams and use cases.
|
|
62
|
+
|
|
63
|
+
#### Subcommands
|
|
64
|
+
- ```stream``` Deploy a stream
|
|
65
|
+
- ```transport``` Deploy a transport
|
|
66
|
+
- ```usecase``` Deploy a use case
|
|
67
|
+
|
|
68
|
+
### list
|
|
69
|
+
List deployed transports, streams and use cases.
|
|
70
|
+
|
|
71
|
+
#### Subcommands
|
|
72
|
+
- ```transports``` List transport by provided type
|
|
73
|
+
- ```streams``` List registered streams
|
|
74
|
+
- ```usecases``` List use cases
|
|
75
|
+
|
|
76
|
+
### inspect
|
|
77
|
+
Display deployed specification for transports, streams and use caase.
|
|
78
|
+
|
|
79
|
+
#### Subcommands
|
|
80
|
+
- ```stream``` Get a stream specification
|
|
81
|
+
- ```transport``` Get a transport specification
|
|
82
|
+
- ```usecase``` Get a use case specification
|
|
83
|
+
|
|
84
|
+
### usecase
|
|
85
|
+
Management command for use cases.
|
|
86
|
+
|
|
87
|
+
#### Subcommands
|
|
88
|
+
- ```pause``` Pause use case processing
|
|
89
|
+
- ```resume``` Resume use case processing
|
|
90
|
+
|
|
91
|
+
### undeploy
|
|
92
|
+
Use case management command for transports, streams and use cases.
|
|
93
|
+
|
|
94
|
+
#### Subcommands
|
|
95
|
+
- ```stream``` Undeploy stream
|
|
96
|
+
- ```transport``` Undeploy transport by provided type
|
|
97
|
+
- ```usecase``` Undeploy use case
|
|
98
|
+
|
|
99
|
+
### config
|
|
100
|
+
Configure tool setting.
|
|
101
|
+
|
|
102
|
+
#### Subcommands
|
|
103
|
+
- ```create``` Create a new configuration file.
|
|
104
|
+
- ```show``` Show configuration setting.
|
|
105
|
+
- ```update``` Update configuration setting.
|
|
106
|
+
|
|
107
|
+
## Resources
|
|
108
|
+
- Discord Joule [server](https://discord.com/channels/1080521605786116196/1080521606247493724)
|
|
109
|
+
- User and developer [documentation](https://docs.fractalworks.io/joule)
|
|
110
|
+
- Joule [website](https://getjoule.io)
|
|
111
|
+
|
|
112
|
+
## License
|
|
113
|
+
Joule Tools are released under an MIT License.
|
joulectl-0.1.3/README.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
## Python Joule Tools
|
|
2
|
+
Joule is a low-code real-time stream processing engine.
|
|
3
|
+
|
|
4
|
+
Joule currently supports wide range of data sources (such as Kafka, MQTT, RabbitMQ, InfuxDB, Minio S3 etc),
|
|
5
|
+
machine learning inference, custom analytics, event windows
|
|
6
|
+
|
|
7
|
+
## Why Should I Use This?
|
|
8
|
+
When you need to deploy and manage use cases within the Joule processing environment.
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
- Deploy transports, streams and use cases.
|
|
12
|
+
- Inspect all deployed specifications for transports, streams and use cases.
|
|
13
|
+
- List all deployed transports, streams and use cases.
|
|
14
|
+
- Undeploy transports, streams and use cases.
|
|
15
|
+
- Use case management to pause and resume processing.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
```bash
|
|
19
|
+
$ pip install joulectl
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Using joulectl
|
|
23
|
+
The tool provides a number of commands to
|
|
24
|
+
support the deployment and management of Joule use case configurations
|
|
25
|
+
|
|
26
|
+
### Setup tool
|
|
27
|
+
First create a configuration file
|
|
28
|
+
```bash
|
|
29
|
+
joulectl config create
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Now update the Joule host which you would like to connect too.
|
|
33
|
+
The default is ```localhost:9080```
|
|
34
|
+
```bash
|
|
35
|
+
joulectl config update --host 192.168.86.48:9080
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
You are now ready to deploy and manage joule remotely.
|
|
39
|
+
|
|
40
|
+
## Commands
|
|
41
|
+
List of available commands
|
|
42
|
+
|
|
43
|
+
### deploy
|
|
44
|
+
Deploy command for transports, streams and use cases.
|
|
45
|
+
|
|
46
|
+
#### Subcommands
|
|
47
|
+
- ```stream``` Deploy a stream
|
|
48
|
+
- ```transport``` Deploy a transport
|
|
49
|
+
- ```usecase``` Deploy a use case
|
|
50
|
+
|
|
51
|
+
### list
|
|
52
|
+
List deployed transports, streams and use cases.
|
|
53
|
+
|
|
54
|
+
#### Subcommands
|
|
55
|
+
- ```transports``` List transport by provided type
|
|
56
|
+
- ```streams``` List registered streams
|
|
57
|
+
- ```usecases``` List use cases
|
|
58
|
+
|
|
59
|
+
### inspect
|
|
60
|
+
Display deployed specification for transports, streams and use caase.
|
|
61
|
+
|
|
62
|
+
#### Subcommands
|
|
63
|
+
- ```stream``` Get a stream specification
|
|
64
|
+
- ```transport``` Get a transport specification
|
|
65
|
+
- ```usecase``` Get a use case specification
|
|
66
|
+
|
|
67
|
+
### usecase
|
|
68
|
+
Management command for use cases.
|
|
69
|
+
|
|
70
|
+
#### Subcommands
|
|
71
|
+
- ```pause``` Pause use case processing
|
|
72
|
+
- ```resume``` Resume use case processing
|
|
73
|
+
|
|
74
|
+
### undeploy
|
|
75
|
+
Use case management command for transports, streams and use cases.
|
|
76
|
+
|
|
77
|
+
#### Subcommands
|
|
78
|
+
- ```stream``` Undeploy stream
|
|
79
|
+
- ```transport``` Undeploy transport by provided type
|
|
80
|
+
- ```usecase``` Undeploy use case
|
|
81
|
+
|
|
82
|
+
### config
|
|
83
|
+
Configure tool setting.
|
|
84
|
+
|
|
85
|
+
#### Subcommands
|
|
86
|
+
- ```create``` Create a new configuration file.
|
|
87
|
+
- ```show``` Show configuration setting.
|
|
88
|
+
- ```update``` Update configuration setting.
|
|
89
|
+
|
|
90
|
+
## Resources
|
|
91
|
+
- Discord Joule [server](https://discord.com/channels/1080521605786116196/1080521606247493724)
|
|
92
|
+
- User and developer [documentation](https://docs.fractalworks.io/joule)
|
|
93
|
+
- Joule [website](https://getjoule.io)
|
|
94
|
+
|
|
95
|
+
## License
|
|
96
|
+
Joule Tools are released under an MIT License.
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from tabulate import tabulate
|
|
3
|
+
from joulectl import joulecomms as jc
|
|
4
|
+
|
|
5
|
+
def executeDeploy(url: str, filename: str, params: dict, host: str):
|
|
6
|
+
response = jc.postData(url=url,
|
|
7
|
+
filename=filename,
|
|
8
|
+
params=params,
|
|
9
|
+
headers={"Content-Type": "application/json; charset=utf-8"},
|
|
10
|
+
host=host)
|
|
11
|
+
if params["pod"]:
|
|
12
|
+
data = response.json()
|
|
13
|
+
table = []
|
|
14
|
+
for rsp in data["hostResponses"]:
|
|
15
|
+
cmd = rsp["command"]
|
|
16
|
+
msg = rsp["message"]
|
|
17
|
+
code = rsp["serviceCode"]
|
|
18
|
+
table.append([rsp["host"], cmd, code, msg])
|
|
19
|
+
click.echo(tabulate(table, headers=["Host","Command", "Response", "Message"]))
|
|
20
|
+
else:
|
|
21
|
+
click.echo(response.text)
|
|
22
|
+
|
|
23
|
+
@click.command()
|
|
24
|
+
@click.argument("filename", type=click.Path(exists=True))
|
|
25
|
+
@click.option("--pod", help="Joule pod name", default="")
|
|
26
|
+
@click.pass_context
|
|
27
|
+
def transport(ctx: click.Context, filename: str, pod: str):
|
|
28
|
+
"""Deploy transport to the Joule server.
|
|
29
|
+
|
|
30
|
+
FILENAME is the specification file to deploy
|
|
31
|
+
"""
|
|
32
|
+
joule_host = ctx.obj["joule_host"]
|
|
33
|
+
url = f"http://{joule_host}/joule/management/transports/register"
|
|
34
|
+
options = {'pod':pod}
|
|
35
|
+
executeDeploy(url=url,
|
|
36
|
+
filename=filename,
|
|
37
|
+
params=options,
|
|
38
|
+
host=joule_host)
|
|
39
|
+
|
|
40
|
+
@click.command()
|
|
41
|
+
@click.argument("filename",type=click.Path(exists=True))
|
|
42
|
+
@click.option("--pod", help="Joule pod name", default="")
|
|
43
|
+
@click.pass_context
|
|
44
|
+
def stream(ctx: click.Context, filename: str, pod: str):
|
|
45
|
+
"""Deploy a stream to the Joule server.
|
|
46
|
+
|
|
47
|
+
FILENAME is the specification file to deploy
|
|
48
|
+
"""
|
|
49
|
+
joule_host = ctx.obj["joule_host"]
|
|
50
|
+
url = f"http://{joule_host}/joule/management/stream/register"
|
|
51
|
+
options = {'pod':pod}
|
|
52
|
+
executeDeploy(url=url,
|
|
53
|
+
filename=filename,
|
|
54
|
+
params=options,
|
|
55
|
+
host=joule_host)
|
|
56
|
+
|
|
57
|
+
@click.command()
|
|
58
|
+
@click.argument("filename", type=click.Path(exists=True))
|
|
59
|
+
@click.option("--pod", help="Joule pod name", default="")
|
|
60
|
+
@click.pass_context
|
|
61
|
+
def usecase(ctx: click.Context, filename: str, pod: str):
|
|
62
|
+
"""Deploy use case to the Joule server.
|
|
63
|
+
|
|
64
|
+
FILENAME is the specification file to deploy
|
|
65
|
+
"""
|
|
66
|
+
joule_host = ctx.obj["joule_host"]
|
|
67
|
+
url = f"http://{joule_host}/joule/management/usecase/deploy"
|
|
68
|
+
params = {'pod':pod}
|
|
69
|
+
executeDeploy(url=url,
|
|
70
|
+
filename=filename,
|
|
71
|
+
params=params,
|
|
72
|
+
host=joule_host)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import json
|
|
3
|
+
import requests
|
|
4
|
+
|
|
5
|
+
def executeDetail(url: str, params: dict, filename: str):
|
|
6
|
+
response = requests.get(url, params=params)
|
|
7
|
+
if response.status_code == 652:
|
|
8
|
+
click.echo("Resource not found")
|
|
9
|
+
return
|
|
10
|
+
|
|
11
|
+
if response.status_code == 200:
|
|
12
|
+
data = response.json()
|
|
13
|
+
with open(filename, 'w') as f:
|
|
14
|
+
json.dump(data,f, ensure_ascii=False)
|
|
15
|
+
|
|
16
|
+
@click.command()
|
|
17
|
+
@click.pass_context
|
|
18
|
+
@click.argument("name")
|
|
19
|
+
@click.argument("type")
|
|
20
|
+
@click.option("--pod", help="Joule pod name", default="")
|
|
21
|
+
@click.option("--filename", help="filename to use for returned specification", default="")
|
|
22
|
+
def transport(ctx: click.Context, name: str, type: str, pod: str, filename: str):
|
|
23
|
+
"""Return a transport specification as a file
|
|
24
|
+
|
|
25
|
+
NAME of the transport \n
|
|
26
|
+
TYPE must be one of: SOURCE or SINK
|
|
27
|
+
"""
|
|
28
|
+
joule_host = ctx.obj["joule_host"]
|
|
29
|
+
url = f"http://{joule_host}/joule/management/transports/detail"
|
|
30
|
+
params = {'type': type, 'name': name, 'pod': pod}
|
|
31
|
+
specfilename = name + ".json"
|
|
32
|
+
if filename:
|
|
33
|
+
specfilename = filename
|
|
34
|
+
executeDetail(url, params=params, filename=specfilename)
|
|
35
|
+
|
|
36
|
+
@click.command()
|
|
37
|
+
@click.pass_context
|
|
38
|
+
@click.argument("name")
|
|
39
|
+
@click.option("--pod", help="Joule pod name", default="")
|
|
40
|
+
@click.option("--filename", help="filename to use for returned specification", default="")
|
|
41
|
+
def stream(ctx: click.Context, name: str, pod: str, filename: str):
|
|
42
|
+
"""Return a stream specification as a file
|
|
43
|
+
|
|
44
|
+
NAME of the stream
|
|
45
|
+
"""
|
|
46
|
+
joule_host = ctx.obj["joule_host"]
|
|
47
|
+
url = f"http://{joule_host}/joule/management/stream/detail"
|
|
48
|
+
params = {'name': name, 'pod': pod}
|
|
49
|
+
specfilename = name + ".json"
|
|
50
|
+
if filename:
|
|
51
|
+
specfilename = filename
|
|
52
|
+
executeDetail(url, params=params, filename=specfilename)
|
|
53
|
+
|
|
54
|
+
@click.command()
|
|
55
|
+
@click.pass_context
|
|
56
|
+
@click.argument("name")
|
|
57
|
+
@click.option("--pod", help="Joule pod name", default="")
|
|
58
|
+
@click.option("--filename", help="filename to use for returned specification", default="")
|
|
59
|
+
def usecase(ctx: click.Context, name: str,pod: str, filename: str):
|
|
60
|
+
"""Return a use case specification as a file
|
|
61
|
+
|
|
62
|
+
NAME of the use case
|
|
63
|
+
"""
|
|
64
|
+
joule_host = ctx.obj["joule_host"]
|
|
65
|
+
url = f"http://{joule_host}/joule/management/usecase/detail"
|
|
66
|
+
params = {'name': name, 'pod':pod}
|
|
67
|
+
specfilename = name + ".json"
|
|
68
|
+
if filename:
|
|
69
|
+
specfilename = filename
|
|
70
|
+
executeDetail(url, params=params,filename=specfilename)
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import requests
|
|
3
|
+
from tabulate import tabulate
|
|
4
|
+
|
|
5
|
+
from joulectl import joulecomms as jc
|
|
6
|
+
|
|
7
|
+
def displayTransportList(response: requests.Response):
|
|
8
|
+
table = []
|
|
9
|
+
if response.status_code != 204:
|
|
10
|
+
data = response.json()
|
|
11
|
+
for key in data.keys():
|
|
12
|
+
dd = data.get(key)
|
|
13
|
+
table.append([key, dd.get("x"), dd.get("y")])
|
|
14
|
+
click.echo(tabulate(table, headers=["Name", "Type","Description"]))
|
|
15
|
+
|
|
16
|
+
@click.command()
|
|
17
|
+
@click.pass_context
|
|
18
|
+
def pods(ctx: click.Context):
|
|
19
|
+
"""List running pods with the number of running joule processes"""
|
|
20
|
+
joule_host = ctx.obj["joule_host"]
|
|
21
|
+
url = f"http://{joule_host}/joule/management/pods/list"
|
|
22
|
+
response = jc.callServer(method="GET",url=url, host=joule_host, headers={}, params={})
|
|
23
|
+
table = []
|
|
24
|
+
if response.status_code != 204:
|
|
25
|
+
data = response.json()
|
|
26
|
+
for element in data:
|
|
27
|
+
pod = element['name']
|
|
28
|
+
members = element['members']
|
|
29
|
+
table.append([pod,members])
|
|
30
|
+
|
|
31
|
+
click.echo(tabulate(table, headers=["Pod", "Members"]))
|
|
32
|
+
|
|
33
|
+
@click.command()
|
|
34
|
+
@click.argument("pod")
|
|
35
|
+
@click.pass_context
|
|
36
|
+
def members(ctx: click.Context, pod: str):
|
|
37
|
+
"""List running members within a pod
|
|
38
|
+
|
|
39
|
+
POD name
|
|
40
|
+
"""
|
|
41
|
+
joule_host = ctx.obj["joule_host"]
|
|
42
|
+
url = f"http://{joule_host}/joule/management/pods/connected"
|
|
43
|
+
response = jc.callServer(method="GET",url=url, host=joule_host, headers={}, params={'pod':pod})
|
|
44
|
+
table = []
|
|
45
|
+
if response.status_code != 204:
|
|
46
|
+
data = response.json()
|
|
47
|
+
for host in data:
|
|
48
|
+
for process in data[host]:
|
|
49
|
+
created = data[host][process]["created"]
|
|
50
|
+
updated = data[host][process]["updated"]
|
|
51
|
+
status = data[host][process]["status"]
|
|
52
|
+
table.append([host,process,status, created, updated])
|
|
53
|
+
|
|
54
|
+
click.echo(tabulate(table, headers=["Host", "Process", "Status", "Created At", "Updated At"]))
|
|
55
|
+
|
|
56
|
+
@click.command()
|
|
57
|
+
@click.option("--pod", prompt=False)
|
|
58
|
+
@click.option("--type", prompt=False)
|
|
59
|
+
@click.pass_context
|
|
60
|
+
def transports(ctx: click.Context, pod: str, type: str):
|
|
61
|
+
"""List deployed transports
|
|
62
|
+
|
|
63
|
+
POD name \n
|
|
64
|
+
TYPE must be one of: SOURCE or SINK if provided, otherwise all transports are listed
|
|
65
|
+
"""
|
|
66
|
+
joule_host = ctx.obj["joule_host"]
|
|
67
|
+
url = f"http://{joule_host}/joule/management/transports/list"
|
|
68
|
+
if type == "SOURCE" or type == "SINK":
|
|
69
|
+
response = jc.callServer(method="GET",url=url, host=joule_host, headers={}, params={'type':type, 'pod':pod})
|
|
70
|
+
displayTransportList(response)
|
|
71
|
+
else:
|
|
72
|
+
response = jc.callServer(method="GET",url=url, host=joule_host, headers={}, params={})
|
|
73
|
+
table = []
|
|
74
|
+
if response.status_code != 204:
|
|
75
|
+
data = response.json()
|
|
76
|
+
for e in data:
|
|
77
|
+
pod = e["pod"]
|
|
78
|
+
name = e["name"]
|
|
79
|
+
transport = e["transport"]
|
|
80
|
+
type = e["type"]
|
|
81
|
+
created = e["created_at"]
|
|
82
|
+
table.append([pod,name,transport, type,created])
|
|
83
|
+
click.echo(tabulate(table, headers=["Pod", "Name", "Transport", "Type", "Created At"]))
|
|
84
|
+
|
|
85
|
+
@click.command()
|
|
86
|
+
@click.option("--pod", prompt=False)
|
|
87
|
+
@click.pass_context
|
|
88
|
+
def streams(ctx: click.Context, pod:str):
|
|
89
|
+
"""List registered streams
|
|
90
|
+
|
|
91
|
+
POD name
|
|
92
|
+
"""
|
|
93
|
+
joule_host = ctx.obj["joule_host"]
|
|
94
|
+
url = f"http://{joule_host}/joule/management/stream/list"
|
|
95
|
+
|
|
96
|
+
response = jc.callServer(method="GET", url=url, host=joule_host, headers={}, params={'pod':pod})
|
|
97
|
+
table = []
|
|
98
|
+
if response.status_code != 204:
|
|
99
|
+
data = response.json()
|
|
100
|
+
for e in data:
|
|
101
|
+
pod = e["pod"]
|
|
102
|
+
name = e["name"]
|
|
103
|
+
metrics_enabled = e["metrics_enabled"]
|
|
104
|
+
telemetry_enabled = e["telemetry_enabled"]
|
|
105
|
+
valid_from = e["valid_from"]
|
|
106
|
+
valid_to = e["valid_to"]
|
|
107
|
+
created = e["created_at"]
|
|
108
|
+
table.append([pod,name,metrics_enabled,telemetry_enabled, valid_from,valid_to,created])
|
|
109
|
+
click.echo(tabulate(table, headers=["Pod", "Name", "Metrics Enabled","Telemetry Enabled","Valid From", "Valid To", "Created At"]))
|
|
110
|
+
|
|
111
|
+
@click.command()
|
|
112
|
+
@click.pass_context
|
|
113
|
+
def usecases(ctx: click.Context):
|
|
114
|
+
"""List all deployed use cases
|
|
115
|
+
"""
|
|
116
|
+
joule_host = ctx.obj["joule_host"]
|
|
117
|
+
url = f"http://{joule_host}/joule/management/usecase/list"
|
|
118
|
+
response = jc.callServer(method="GET", url=url, host=joule_host, headers={}, params={})
|
|
119
|
+
data = response.json()
|
|
120
|
+
click.echo(data)
|
|
121
|
+
table = []
|
|
122
|
+
for element in data:
|
|
123
|
+
pod = element['pod']
|
|
124
|
+
name = element['name']
|
|
125
|
+
stream = element['stream']
|
|
126
|
+
sources = element['sources']
|
|
127
|
+
sinks = element['sinks']
|
|
128
|
+
refData = element['ref_data']
|
|
129
|
+
validFrom = element['valid_from']
|
|
130
|
+
validTo = element['valid_to']
|
|
131
|
+
createdAt = element['created_at']
|
|
132
|
+
table.append([pod,name,stream, sources, sinks, refData, validFrom, validTo, createdAt])
|
|
133
|
+
click.echo(tabulate(table, headers=["Pod","Name","Stream","Sources","Sinks","Reference Data", "Valid From", "Valid To","Created At"]))
|
|
134
|
+
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import requests
|
|
3
|
+
from tabulate import tabulate
|
|
4
|
+
|
|
5
|
+
def displayResponse(name: str,response: requests.Response):
|
|
6
|
+
table = []
|
|
7
|
+
if response.status_code != 204:
|
|
8
|
+
data = response.json()
|
|
9
|
+
cmd = data["command"]
|
|
10
|
+
code = data["aggregatedServiceCode"]
|
|
11
|
+
table.append([name,cmd,code])
|
|
12
|
+
click.echo(tabulate(table, headers=["Name", "Command","Response"]))
|
|
13
|
+
else:
|
|
14
|
+
click.echo("Unknown use case - {}".format(name))
|
|
15
|
+
|
|
16
|
+
@click.command()
|
|
17
|
+
@click.pass_context
|
|
18
|
+
@click.argument("name")
|
|
19
|
+
@click.option("--pod", help="Joule pod name", default="")
|
|
20
|
+
def resume(ctx: click.Context, name: str, pod: str):
|
|
21
|
+
"""Resume use case processing
|
|
22
|
+
|
|
23
|
+
NAME is the use case
|
|
24
|
+
"""
|
|
25
|
+
joule_host = ctx.obj["joule_host"]
|
|
26
|
+
url = f"http://{joule_host}/joule/management/usecase/resume"
|
|
27
|
+
params={"name": name, "pod": pod}
|
|
28
|
+
response = requests.request(method="PUT", url=url, params=params)
|
|
29
|
+
if response.status_code != 204:
|
|
30
|
+
displayResponse(name,response)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@click.command()
|
|
34
|
+
@click.argument("name")
|
|
35
|
+
@click.pass_context
|
|
36
|
+
@click.option("--pod", help="Joule pod name", default="")
|
|
37
|
+
def pause(ctx: click.Context, name: str, pod: str):
|
|
38
|
+
"""Pause use case processing
|
|
39
|
+
|
|
40
|
+
NAME is the use case
|
|
41
|
+
"""
|
|
42
|
+
joule_host = ctx.obj["joule_host"]
|
|
43
|
+
url = f"http://{joule_host}/joule/management/usecase/pause"
|
|
44
|
+
params={"name": name, "pod": pod}
|
|
45
|
+
response = requests.request(method="PUT", url=url, params=params)
|
|
46
|
+
if response.status_code != 204:
|
|
47
|
+
displayResponse(name,response)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import requests
|
|
3
|
+
from tabulate import tabulate
|
|
4
|
+
from joulectl import joulecomms as jc
|
|
5
|
+
|
|
6
|
+
def displayResponse(pod: str, response: requests.Response):
|
|
7
|
+
if pod:
|
|
8
|
+
data = response.json()
|
|
9
|
+
table = []
|
|
10
|
+
for rsp in data["hostResponses"]:
|
|
11
|
+
cmd = rsp["command"]
|
|
12
|
+
msg = rsp["message"]
|
|
13
|
+
code = rsp["serviceCode"]
|
|
14
|
+
table.append([rsp["host"], cmd, code, msg])
|
|
15
|
+
click.echo(tabulate(table,headers=["Host","Command", "Response", "Message"]))
|
|
16
|
+
else:
|
|
17
|
+
click.echo(response.text)
|
|
18
|
+
|
|
19
|
+
@click.command()
|
|
20
|
+
@click.option("--pod", help="Joule pod name", default="")
|
|
21
|
+
@click.argument("name")
|
|
22
|
+
@click.argument("type")
|
|
23
|
+
@click.pass_context
|
|
24
|
+
def transport(ctx: click.Context, pod: str, name: str, type: str):
|
|
25
|
+
"""Undeploy transport
|
|
26
|
+
|
|
27
|
+
NAME of the transport \n
|
|
28
|
+
TYPE must be one of: SOURCE or SINK
|
|
29
|
+
"""
|
|
30
|
+
if type == "SOURCE" or type == "SINK":
|
|
31
|
+
joule_host = ctx.obj["joule_host"]
|
|
32
|
+
|
|
33
|
+
url = f"http://{joule_host}/joule/management/transports/unregister"
|
|
34
|
+
params = {'name': name, 'type': type, 'pod': pod}
|
|
35
|
+
response = jc.callServer(method="DELETE", url=url, host=joule_host, headers={},params=params)
|
|
36
|
+
if response.status_code == 200:
|
|
37
|
+
displayResponse(pod, response)
|
|
38
|
+
else:
|
|
39
|
+
click.echo(response.text)
|
|
40
|
+
else:
|
|
41
|
+
click.echo("Unknown transport type: {}".format(type))
|
|
42
|
+
|
|
43
|
+
@click.command()
|
|
44
|
+
@click.option("--pod", help="Joule pod name", default="")
|
|
45
|
+
@click.argument("name")
|
|
46
|
+
@click.pass_context
|
|
47
|
+
def stream(ctx: click.Context, name: str, pod: str):
|
|
48
|
+
"""Undeploy stream
|
|
49
|
+
|
|
50
|
+
NAME of stream
|
|
51
|
+
"""
|
|
52
|
+
joule_host = ctx.obj["joule_host"]
|
|
53
|
+
url = f"http://{joule_host}/joule/management/stream/unregister"
|
|
54
|
+
params = {'name': name, 'pod': pod}
|
|
55
|
+
response = jc.callServer(method="DELETE", url=url, host=joule_host, headers={},params=params)
|
|
56
|
+
if response.status_code == 200:
|
|
57
|
+
displayResponse(pod, response)
|
|
58
|
+
else:
|
|
59
|
+
click.echo(response.text)
|
|
60
|
+
|
|
61
|
+
@click.command()
|
|
62
|
+
@click.option("--pod", help="Joule pod name", default="")
|
|
63
|
+
@click.argument("name")
|
|
64
|
+
@click.pass_context
|
|
65
|
+
def usecase(ctx: click.Context, name: str, pod: str):
|
|
66
|
+
"""Undeploy use case
|
|
67
|
+
|
|
68
|
+
NAME of use case
|
|
69
|
+
"""
|
|
70
|
+
joule_host = ctx.obj["joule_host"]
|
|
71
|
+
url = f"http://{joule_host}/joule/management/usecase/undeploy"
|
|
72
|
+
params = {'name': name, 'pod': pod}
|
|
73
|
+
response = jc.callServer(method="DELETE", url=url, host=joule_host, headers={},params=params)
|
|
74
|
+
displayResponse(pod, response)
|
|
File without changes
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
CONFIG_DIR = Path.home() / ".config" / "joule"
|
|
6
|
+
CONFIG_FILE = CONFIG_DIR / "config.json"
|
|
7
|
+
DEFAULT_JOULE_HOST = "127.0.0.1:9080"
|
|
8
|
+
|
|
9
|
+
def load_config(config_path: Path = CONFIG_FILE):
|
|
10
|
+
if not config_path.exists():
|
|
11
|
+
return {"joule_dir": str(CONFIG_DIR)}
|
|
12
|
+
with open(config_path, "r") as file:
|
|
13
|
+
return json.load(file)
|
|
14
|
+
|
|
15
|
+
def save_config(config: dict[str, Any]):
|
|
16
|
+
if not CONFIG_DIR.exists():
|
|
17
|
+
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
18
|
+
with open(CONFIG_FILE, "w") as file:
|
|
19
|
+
json.dump(config, file, indent=4)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from config import load_config
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def create_files(pattern: str, start: int = 0, end: int = 10) -> list[str]:
|
|
7
|
+
config = load_config()
|
|
8
|
+
joule_dir = config.get("joule_dir")
|
|
9
|
+
|
|
10
|
+
if joule_dir is None:
|
|
11
|
+
raise ValueError("Joule directory not configured.")
|
|
12
|
+
|
|
13
|
+
base_name, extension = pattern.split("*")
|
|
14
|
+
extension = extension.lstrip(".")
|
|
15
|
+
|
|
16
|
+
files: list[str] = []
|
|
17
|
+
for i in range(start, end + 1):
|
|
18
|
+
file_name = f"{base_name}{i}.{extension}"
|
|
19
|
+
|
|
20
|
+
if exists(file_name):
|
|
21
|
+
continue
|
|
22
|
+
|
|
23
|
+
with open(os.path.join(joule_dir, file_name), "w") as file:
|
|
24
|
+
file.write(f"This is the content of {file_name}\n")
|
|
25
|
+
files.append(file_name)
|
|
26
|
+
|
|
27
|
+
return files
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def load(path: str):
|
|
31
|
+
config = load_config()
|
|
32
|
+
joule_dir = config.get("joule_dir")
|
|
33
|
+
|
|
34
|
+
if joule_dir is None:
|
|
35
|
+
raise ValueError("Joule directory not configured.")
|
|
36
|
+
|
|
37
|
+
with open(os.path.join(joule_dir, path), "r") as file:
|
|
38
|
+
return file.read()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def save(path: str, content: str):
|
|
42
|
+
config = load_config()
|
|
43
|
+
joule_dir = config.get("joule_dir")
|
|
44
|
+
|
|
45
|
+
if joule_dir is None:
|
|
46
|
+
raise ValueError("Joule directory not configured.")
|
|
47
|
+
with open(os.path.join(joule_dir, path), "w+") as file:
|
|
48
|
+
file.write(content)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def exists(path: str):
|
|
52
|
+
config = load_config()
|
|
53
|
+
joule_dir = config.get("joule_dir")
|
|
54
|
+
|
|
55
|
+
if joule_dir is None:
|
|
56
|
+
raise ValueError("Joule directory not configured.")
|
|
57
|
+
return os.path.isfile(os.path.join(joule_dir, path))
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from tabulate import tabulate
|
|
3
|
+
|
|
4
|
+
from joulectl.commands import listcmds, deploycmds, inspectcmds, undeploycmds, ucmgmtcmds
|
|
5
|
+
from joulectl.config.config import CONFIG_FILE, DEFAULT_JOULE_HOST, load_config, save_config
|
|
6
|
+
|
|
7
|
+
@click.group()
|
|
8
|
+
@click.version_option()
|
|
9
|
+
@click.pass_context
|
|
10
|
+
def cli(ctx: click.Context):
|
|
11
|
+
"""Joule deployment and management tool \n
|
|
12
|
+
joulctl version 0.1.3
|
|
13
|
+
"""
|
|
14
|
+
tc = load_config()
|
|
15
|
+
|
|
16
|
+
# Initialise ctx with empty dict
|
|
17
|
+
ctx.ensure_object(dict)
|
|
18
|
+
ctx.obj["joule_host"] = tc.get("joule_host", DEFAULT_JOULE_HOST)
|
|
19
|
+
ctx.obj["config"] = tc
|
|
20
|
+
|
|
21
|
+
@cli.group()
|
|
22
|
+
def ls():
|
|
23
|
+
"""List deployed transports, streams and use cases."""
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
@cli.group()
|
|
27
|
+
def deploy():
|
|
28
|
+
"""Deploy command for transports, streams and use cases."""
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
@cli.group()
|
|
32
|
+
def undeploy():
|
|
33
|
+
"""Undeploy command for transports, streams and use cases."""
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
@cli.group()
|
|
37
|
+
def process():
|
|
38
|
+
"""Management command for use cases"""
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
@cli.group()
|
|
42
|
+
def inspect():
|
|
43
|
+
"""Get deployed specifications for transports, streams and use cases."""
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
@cli.group()
|
|
47
|
+
def config():
|
|
48
|
+
"""Configure joulectl setting."""
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
@config.command()
|
|
52
|
+
def create():
|
|
53
|
+
"""Create a new configuration file."""
|
|
54
|
+
nc = load_config()
|
|
55
|
+
save_config(nc)
|
|
56
|
+
click.echo("Configuration created.")
|
|
57
|
+
|
|
58
|
+
@config.command()
|
|
59
|
+
def show():
|
|
60
|
+
"""Show configuration setting"""
|
|
61
|
+
if not CONFIG_FILE.exists():
|
|
62
|
+
click.echo("No configuration found.")
|
|
63
|
+
return
|
|
64
|
+
displyConfiguration()
|
|
65
|
+
|
|
66
|
+
@config.command()
|
|
67
|
+
@click.argument("host")
|
|
68
|
+
def update(host: str):
|
|
69
|
+
"""Update configuration setting for joule host.
|
|
70
|
+
|
|
71
|
+
HOST address and port send commands too e.g., 192.168.1.10:60110
|
|
72
|
+
"""
|
|
73
|
+
cg = load_config()
|
|
74
|
+
if host:
|
|
75
|
+
cg['joule_host'] = host
|
|
76
|
+
save_config(cg)
|
|
77
|
+
click.echo(f"Joule host set to {host}")
|
|
78
|
+
|
|
79
|
+
def displyConfiguration():
|
|
80
|
+
dc = load_config()
|
|
81
|
+
table = []
|
|
82
|
+
for key, value in dc.items():
|
|
83
|
+
table.append([key, value])
|
|
84
|
+
|
|
85
|
+
click.echo(tabulate(table, headers=["Key", "Configuration"]))
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
## Command setting
|
|
89
|
+
ls.add_command(listcmds.transports)
|
|
90
|
+
ls.add_command(listcmds.streams)
|
|
91
|
+
ls.add_command(listcmds.usecases)
|
|
92
|
+
ls.add_command(listcmds.pods)
|
|
93
|
+
ls.add_command(listcmds.members)
|
|
94
|
+
|
|
95
|
+
deploy.add_command(deploycmds.transport)
|
|
96
|
+
deploy.add_command(deploycmds.stream)
|
|
97
|
+
deploy.add_command(deploycmds.usecase)
|
|
98
|
+
|
|
99
|
+
process.add_command(ucmgmtcmds.pause)
|
|
100
|
+
process.add_command(ucmgmtcmds.resume)
|
|
101
|
+
|
|
102
|
+
undeploy.add_command(undeploycmds.transport)
|
|
103
|
+
undeploy.add_command(undeploycmds.stream)
|
|
104
|
+
undeploy.add_command(undeploycmds.usecase)
|
|
105
|
+
|
|
106
|
+
inspect.add_command(inspectcmds.transport)
|
|
107
|
+
inspect.add_command(inspectcmds.stream)
|
|
108
|
+
inspect.add_command(inspectcmds.usecase)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
def callServer(method: str, url: str, host: str, headers: dict, params: dict):
|
|
5
|
+
try:
|
|
6
|
+
response = requests.request(method=method, url=url, headers=headers, params=params)
|
|
7
|
+
except requests.ConnectionError as e:
|
|
8
|
+
print("Error connecting to Joule server: {}".format(host))
|
|
9
|
+
exit(1)
|
|
10
|
+
return response
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def postData(url: str, filename: str, params: dict, host: str, headers: dict):
|
|
14
|
+
with open(filename) as json_data:
|
|
15
|
+
data = json.load(json_data)
|
|
16
|
+
try:
|
|
17
|
+
response = requests.post(url, headers=headers, json=data, params=params)
|
|
18
|
+
except requests.ConnectionError as e:
|
|
19
|
+
print("Error connecting to Joule server: {}".format(host))
|
|
20
|
+
exit(1)
|
|
21
|
+
return response
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "joulectl"
|
|
3
|
+
version = "0.1.3"
|
|
4
|
+
description = "Joule CLI tool to manage Joule transports, streams and use cases"
|
|
5
|
+
authors = [
|
|
6
|
+
{name = "Lyndon Adams",email = "lyndon.adams@fractalworks.io"}
|
|
7
|
+
]
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
requires-python = ">=3.10"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"click (>=8.1.8,<9.0.0)",
|
|
12
|
+
"requests (>=2.32.3,<3.0.0)",
|
|
13
|
+
"tabulate (>=0.9.0,<1.0.0)"
|
|
14
|
+
]
|
|
15
|
+
packages = [
|
|
16
|
+
{ include = "app" },
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
license = { text = "MIT" }
|
|
20
|
+
keywords = [ "joule", "streaming" ]
|
|
21
|
+
classifiers = [
|
|
22
|
+
"License :: OSI Approved :: MIT License",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[project.urls]
|
|
26
|
+
homepage = "https://https://getjoule.io/"
|
|
27
|
+
documentation = "https://docs.fractalworks.io/joule"
|
|
28
|
+
|
|
29
|
+
[build-system]
|
|
30
|
+
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
|
31
|
+
build-backend = "poetry.core.masonry.api"
|
|
32
|
+
|
|
33
|
+
[tool.poetry.group.dev.dependencies]
|
|
34
|
+
twine = "^6.1.0"
|
|
35
|
+
|
|
36
|
+
[tool.poetry]
|
|
37
|
+
package-mode = true
|
|
38
|
+
|
|
39
|
+
[tool.poetry.scripts]
|
|
40
|
+
joulectl = "joulectl.joule:cli" # entrypoint to the CLI
|