kubeler 0.1.3__py3-none-any.whl → 0.1.4__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.
- kubeler/main.py +2 -1
- kubeler/scripts/installer.py +64 -13
- kubeler/scripts/models/kubeler.py +5 -0
- kubeler/scripts/models/kubernetes.py +7 -0
- kubeler/scripts/watchdog.py +36 -0
- {kubeler-0.1.3.dist-info → kubeler-0.1.4.dist-info}/METADATA +27 -1
- kubeler-0.1.4.dist-info/RECORD +14 -0
- kubeler-0.1.3.dist-info/RECORD +0 -12
- {kubeler-0.1.3.dist-info → kubeler-0.1.4.dist-info}/LICENSE +0 -0
- {kubeler-0.1.3.dist-info → kubeler-0.1.4.dist-info}/WHEEL +0 -0
- {kubeler-0.1.3.dist-info → kubeler-0.1.4.dist-info}/entry_points.txt +0 -0
- {kubeler-0.1.3.dist-info → kubeler-0.1.4.dist-info}/top_level.txt +0 -0
kubeler/main.py
CHANGED
@@ -13,13 +13,14 @@ def main():
|
|
13
13
|
install_parser.add_argument('--start-from', type=str, default=None, help='Start from a specific step')
|
14
14
|
install_parser.add_argument('--steps', type=str, default=None, help='Only run specific steps, comma separated')
|
15
15
|
install_parser.add_argument('--excludes', type=str, default=None, help='Exlude some steps, comma separated')
|
16
|
+
install_parser.add_argument('--watch', type=str, default=None, help='Watch for changes')
|
16
17
|
args = parser.parse_args()
|
17
18
|
|
18
19
|
# load .env content to environment variables
|
19
20
|
load_dotenv()
|
20
21
|
|
21
22
|
if args.command == 'install':
|
22
|
-
installer = Installer(installer=args.installer, kube_config=args.kube_config, start_from=args.start_from, steps=args.steps, excludes=args.excludes)
|
23
|
+
installer = Installer(installer=args.installer, kube_config=args.kube_config, start_from=args.start_from, steps=args.steps, excludes=args.excludes, watch=args.watch)
|
23
24
|
installer.install()
|
24
25
|
|
25
26
|
if __name__ == "__main__":
|
kubeler/scripts/installer.py
CHANGED
@@ -1,16 +1,21 @@
|
|
1
1
|
import yaml, os, sys, subprocess, shutil, jinja2
|
2
|
+
from kubernetes import client, config as k8sconfig
|
2
3
|
from .models.kubeler import Kubeler
|
4
|
+
from .watchdog import watch_directory
|
3
5
|
|
4
6
|
tmp_dir = "/tmp/kubeler"
|
7
|
+
k8sconfig.load_kube_config()
|
8
|
+
v1 = client.CoreV1Api()
|
5
9
|
|
6
10
|
class Installer:
|
7
|
-
def __init__(self, installer, kube_config, start_from, steps, excludes):
|
11
|
+
def __init__(self, installer, kube_config, start_from, steps, excludes, watch):
|
8
12
|
self.installer = installer
|
9
13
|
self.kube_config = kube_config
|
10
14
|
self.start_from = start_from
|
11
15
|
self.kube_config = kube_config
|
12
16
|
self.steps = steps
|
13
17
|
self.excludes = excludes
|
18
|
+
self.watch = watch
|
14
19
|
|
15
20
|
# get the directory path of the installer and kube_config
|
16
21
|
self.installer_dir_path = os.path.dirname(installer)
|
@@ -72,19 +77,25 @@ class Installer:
|
|
72
77
|
|
73
78
|
# get commands defined in the file
|
74
79
|
commands = self.load_file(file_path)
|
75
|
-
|
76
|
-
print("Executing command: ", command)
|
77
|
-
cmd = command.split()
|
78
|
-
process = subprocess.Popen(cmd, cwd=execution_dir, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=1, universal_newlines=True)
|
79
|
-
for line in process.stdout:
|
80
|
-
print(line, end="")
|
81
|
-
sys.stdout.flush()
|
82
|
-
process.wait()
|
80
|
+
self.execute(commands, execution_dir)
|
83
81
|
|
84
82
|
# if config_path exists, restore the file
|
85
83
|
if os.path.exists(config_path):
|
86
84
|
os.remove(config_path)
|
87
|
-
|
85
|
+
|
86
|
+
if kubeler.group.watch.enabled == True and self.watch == "true":
|
87
|
+
watch_directory(self, os.path.abspath(self.installer_dir_path), kubeler.group.watch.dir)
|
88
|
+
|
89
|
+
def execute(self, commands, execution_dir):
|
90
|
+
for command in commands:
|
91
|
+
print("Executing command: ", command)
|
92
|
+
cmd = command.split()
|
93
|
+
process = subprocess.Popen(cmd, cwd=execution_dir, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=1, universal_newlines=True)
|
94
|
+
for line in process.stdout:
|
95
|
+
print(line, end="")
|
96
|
+
sys.stdout.flush()
|
97
|
+
process.wait()
|
98
|
+
|
88
99
|
# if there some files in the directory, load them
|
89
100
|
def load_files_in_dir(self, dir):
|
90
101
|
files = []
|
@@ -100,7 +111,9 @@ class Installer:
|
|
100
111
|
for line in file:
|
101
112
|
if line.startswith("#cmd:"):
|
102
113
|
command = line.split(":", 1)[1].strip()
|
114
|
+
command = self.handle_helm_chart_idempotency(command)
|
103
115
|
commands.append(command)
|
116
|
+
|
104
117
|
return commands
|
105
118
|
|
106
119
|
# load the configuration file and parse to Kubeler model
|
@@ -154,10 +167,10 @@ class Installer:
|
|
154
167
|
# handle only run specific steps
|
155
168
|
if self.steps != None:
|
156
169
|
steps = self.steps.split(",")
|
157
|
-
for step in kubeler.group.steps:
|
170
|
+
for step in kubeler.group.steps[:]:
|
158
171
|
if step.name not in steps:
|
159
172
|
kubeler.group.steps.remove(step)
|
160
|
-
|
173
|
+
|
161
174
|
return kubeler
|
162
175
|
|
163
176
|
# open the configuration file
|
@@ -167,4 +180,42 @@ class Installer:
|
|
167
180
|
data = yaml.safe_load(stream)
|
168
181
|
return data
|
169
182
|
except yaml.YAMLError as exc:
|
170
|
-
raise ValueError("Failed to load configuration")
|
183
|
+
raise ValueError("Failed to load configuration")
|
184
|
+
|
185
|
+
def is_helm_chart_installed(self, release_name, namespace="default"):
|
186
|
+
secrets = v1.list_namespaced_secret(namespace).items
|
187
|
+
for secret in secrets:
|
188
|
+
labels = secret.metadata.labels or {}
|
189
|
+
if labels.get("owner") == "helm" and labels.get("name") == release_name:
|
190
|
+
return True
|
191
|
+
return False
|
192
|
+
|
193
|
+
def handle_helm_chart_idempotency(self, command):
|
194
|
+
# check if it's helm from: #cmd: helm install ...
|
195
|
+
if command.startswith("helm install"):
|
196
|
+
# get namespace from some scenarios:
|
197
|
+
# -n <namespace>
|
198
|
+
# --namespace <namespace>
|
199
|
+
# --namespace=<namespace>
|
200
|
+
namespace = None
|
201
|
+
if "-n" in command:
|
202
|
+
namespace = command.split("-n")[1].split()[0]
|
203
|
+
elif "--namespace" in command:
|
204
|
+
namespace = command.split("--namespace")[1].split()[0]
|
205
|
+
elif "--namespace=" in command:
|
206
|
+
namespace = command.split("--namespace=")[1].split()[0]
|
207
|
+
|
208
|
+
# get release name from: #cmd: helm install <release_name> ...
|
209
|
+
# get after `helm install` or `helm upgrade`
|
210
|
+
release_name = None
|
211
|
+
if "install" in command:
|
212
|
+
release_name = command.split("install")[1].split()[0]
|
213
|
+
elif "upgrade" in command:
|
214
|
+
release_name = command.split("upgrade")[1].split()[0]
|
215
|
+
|
216
|
+
if namespace != None:
|
217
|
+
if self.is_helm_chart_installed(release_name, namespace):
|
218
|
+
# remove --install and replace install with upgrade
|
219
|
+
command = command.replace("--install", "").replace("install", "upgrade")
|
220
|
+
|
221
|
+
return command
|
@@ -16,9 +16,14 @@ class Step(BaseModel):
|
|
16
16
|
vars: List[Variable] | None = Field(default=None,title="Variables to be passed to the step")
|
17
17
|
exclude: str | bool | None = Field(default=None, title="Exclude the step from execution")
|
18
18
|
|
19
|
+
class Watch(BaseModel):
|
20
|
+
enabled: bool = Field(title="Watch enabled")
|
21
|
+
dir: List[str] = Field(default=None, title="Directory to be watched")
|
22
|
+
|
19
23
|
class Group(BaseModel):
|
20
24
|
name: str = Field(title="Initial Command")
|
21
25
|
steps: List[Step] = Field(title="List of steps to be executed")
|
26
|
+
watch: Watch | None = Field(default=None,title="Watch configuration")
|
22
27
|
|
23
28
|
class Kubeler(BaseModel):
|
24
29
|
init: Init | None = Field(default=None,title="Initial Command")
|
@@ -0,0 +1,7 @@
|
|
1
|
+
from pydantic import BaseModel
|
2
|
+
from pydantic.fields import Field
|
3
|
+
|
4
|
+
class Resource(BaseModel):
|
5
|
+
type: str = Field(title="Type of the resource", min_length=3)
|
6
|
+
name: str | bool | int = Field(title="Name of the resource", min_length=3)
|
7
|
+
namespace: str | bool | int = Field(title="Namespace of the resource", min_length=3)
|
@@ -0,0 +1,36 @@
|
|
1
|
+
import time, os
|
2
|
+
from watchdog.observers import Observer
|
3
|
+
from watchdog.events import FileSystemEventHandler
|
4
|
+
|
5
|
+
class WatchHandler(FileSystemEventHandler):
|
6
|
+
def __init__(self, handler):
|
7
|
+
self.handler = handler
|
8
|
+
|
9
|
+
def on_modified(self, event):
|
10
|
+
if not event.is_directory:
|
11
|
+
print(f"File modified: {event.src_path}")
|
12
|
+
commands = self.handler.load_file(event.src_path)
|
13
|
+
self.handler.execute(commands, os.path.dirname(event.src_path))
|
14
|
+
|
15
|
+
def on_created(self, event):
|
16
|
+
if not event.is_directory:
|
17
|
+
print(f"File created: {event.src_path}")
|
18
|
+
|
19
|
+
def on_deleted(self, event):
|
20
|
+
if not event.is_directory:
|
21
|
+
print(f"File deleted: {event.src_path}")
|
22
|
+
|
23
|
+
def watch_directory(handler, dir, paths=["."]):
|
24
|
+
event_handler = WatchHandler(handler)
|
25
|
+
observer = Observer()
|
26
|
+
for path in paths:
|
27
|
+
dir_path = os.path.join(dir, path)
|
28
|
+
observer.schedule(event_handler, dir_path, recursive=True)
|
29
|
+
observer.start()
|
30
|
+
|
31
|
+
try:
|
32
|
+
while True:
|
33
|
+
time.sleep(1)
|
34
|
+
except KeyboardInterrupt:
|
35
|
+
observer.stop()
|
36
|
+
observer.join()
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: kubeler
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.4
|
4
4
|
Summary: A dead simple Kubernetes Resources installer
|
5
5
|
Home-page: https://github.com/glendmaatita/kubeler
|
6
6
|
Author: Glend Maatita
|
@@ -14,6 +14,8 @@ License-File: LICENSE
|
|
14
14
|
Requires-Dist: jinja2>=3.1.5
|
15
15
|
Requires-Dist: kubernetes>=32.0.0
|
16
16
|
Requires-Dist: pydantic>=2.10.6
|
17
|
+
Requires-Dist: python-dotenv>=1.0.1
|
18
|
+
Requires-Dist: watchdog>=6.0.0
|
17
19
|
Dynamic: author
|
18
20
|
Dynamic: author-email
|
19
21
|
Dynamic: classifier
|
@@ -190,3 +192,27 @@ Use argument `--steps` if you want to only execute some steps and exclude the ot
|
|
190
192
|
```
|
191
193
|
--installer=./../examples/installer.yaml --steps=argocd,redis
|
192
194
|
```
|
195
|
+
|
196
|
+
## Watching for file changes
|
197
|
+
|
198
|
+
Use the `--watch` argument to monitor a directory and automatically apply changes when they occur.
|
199
|
+
|
200
|
+
Update `installer.yaml` to set `watch` attribute
|
201
|
+
|
202
|
+
```
|
203
|
+
group:
|
204
|
+
name: k8s
|
205
|
+
watch:
|
206
|
+
enabled: true
|
207
|
+
dir:
|
208
|
+
- ./applications
|
209
|
+
- ./cluster
|
210
|
+
```
|
211
|
+
|
212
|
+
and then, run
|
213
|
+
|
214
|
+
```
|
215
|
+
kubeler install --installer=./examples/installer.yaml --watch=true
|
216
|
+
```
|
217
|
+
|
218
|
+
|
@@ -0,0 +1,14 @@
|
|
1
|
+
kubeler/__init__.py,sha256=xuYziAiR_MvvngneqgskzBPoVTHLwxRs6l--kORxv7s,7
|
2
|
+
kubeler/main.py,sha256=6Bn1OguVUMjx24plZ3J9PCeVbQrcMAxLP7ukIg9CrOI,1437
|
3
|
+
kubeler/scripts/__init__.py,sha256=xuYziAiR_MvvngneqgskzBPoVTHLwxRs6l--kORxv7s,7
|
4
|
+
kubeler/scripts/installer.py,sha256=ercx3I4ZCs02-QMxoSGm21TIyNJwRvwZeQWTk1hnM9Y,8937
|
5
|
+
kubeler/scripts/watchdog.py,sha256=t-iSqNtYm0_i4BOnnDScGdYvder9QUjEid346zoiW9E,1123
|
6
|
+
kubeler/scripts/models/__init__.py,sha256=xuYziAiR_MvvngneqgskzBPoVTHLwxRs6l--kORxv7s,7
|
7
|
+
kubeler/scripts/models/kubeler.py,sha256=xdpHTxt1wNwjBNHrRu9alj7M8Jp6UDXwPxe_adPH5kc,1357
|
8
|
+
kubeler/scripts/models/kubernetes.py,sha256=-G6ppVwXu2Z2zfXXf2Zl9T6fXofSbF25MuYaKdsclV0,327
|
9
|
+
kubeler-0.1.4.dist-info/LICENSE,sha256=g8iEDGIIhDh0wHq4B7md4cWUJ69HC13i010cmASC9Lg,1059
|
10
|
+
kubeler-0.1.4.dist-info/METADATA,sha256=UagyoubabOH9YjXEj6WLaMGVcZJ_6CS8PFt6-zzBUUY,4661
|
11
|
+
kubeler-0.1.4.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
12
|
+
kubeler-0.1.4.dist-info/entry_points.txt,sha256=-HM8L2j9zOFjn8DGCEqANNS-rSvA8dXlyzWhTZD_02Q,46
|
13
|
+
kubeler-0.1.4.dist-info/top_level.txt,sha256=Chw1LcDOq_cKEex9nHyQOMq8-C6QpLdJjK1s_1MnDEk,8
|
14
|
+
kubeler-0.1.4.dist-info/RECORD,,
|
kubeler-0.1.3.dist-info/RECORD
DELETED
@@ -1,12 +0,0 @@
|
|
1
|
-
kubeler/__init__.py,sha256=xuYziAiR_MvvngneqgskzBPoVTHLwxRs6l--kORxv7s,7
|
2
|
-
kubeler/main.py,sha256=PN7Q2SmnMcF_HKbdUMNsk7mbVj9pTsu5IFMjyQjuUmc,1326
|
3
|
-
kubeler/scripts/__init__.py,sha256=xuYziAiR_MvvngneqgskzBPoVTHLwxRs6l--kORxv7s,7
|
4
|
-
kubeler/scripts/installer.py,sha256=iITHzlaKJ4rOT9vIEVHDcGn9tbh9z7fCSr9hT65fcZI,6736
|
5
|
-
kubeler/scripts/models/__init__.py,sha256=xuYziAiR_MvvngneqgskzBPoVTHLwxRs6l--kORxv7s,7
|
6
|
-
kubeler/scripts/models/kubeler.py,sha256=Tq5Ttz1VdfBaKgdVH5DWKUPT9yTm4uPR_CcvmLjtuJY,1135
|
7
|
-
kubeler-0.1.3.dist-info/LICENSE,sha256=g8iEDGIIhDh0wHq4B7md4cWUJ69HC13i010cmASC9Lg,1059
|
8
|
-
kubeler-0.1.3.dist-info/METADATA,sha256=DqgtM6m8Xz9PvabjIpJLMRT39jIyzKWgcqOPvi1LPug,4215
|
9
|
-
kubeler-0.1.3.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
10
|
-
kubeler-0.1.3.dist-info/entry_points.txt,sha256=-HM8L2j9zOFjn8DGCEqANNS-rSvA8dXlyzWhTZD_02Q,46
|
11
|
-
kubeler-0.1.3.dist-info/top_level.txt,sha256=Chw1LcDOq_cKEex9nHyQOMq8-C6QpLdJjK1s_1MnDEk,8
|
12
|
-
kubeler-0.1.3.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|