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 ADDED
@@ -0,0 +1,7 @@
1
+ # A list of people who have contributed to twine in order of their first
2
+ # contribution.
3
+ #
4
+ # Uses the format of ``Name <email@domain.tld> (url)`` with the ``(url)``
5
+ # being optional.
6
+
7
+ Lyndon Adams <lyndon.adams@fractalworks.io> (https://fractalworks.io)
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.
@@ -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.
@@ -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