kubeler 0.1.2__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 +9 -2
- kubeler/scripts/installer.py +93 -12
- kubeler/scripts/models/kubeler.py +6 -0
- kubeler/scripts/models/kubernetes.py +7 -0
- kubeler/scripts/watchdog.py +36 -0
- {kubeler-0.1.2.dist-info → kubeler-0.1.4.dist-info}/METADATA +64 -2
- kubeler-0.1.4.dist-info/RECORD +14 -0
- kubeler-0.1.2.dist-info/RECORD +0 -12
- {kubeler-0.1.2.dist-info → kubeler-0.1.4.dist-info}/LICENSE +0 -0
- {kubeler-0.1.2.dist-info → kubeler-0.1.4.dist-info}/WHEEL +0 -0
- {kubeler-0.1.2.dist-info → kubeler-0.1.4.dist-info}/entry_points.txt +0 -0
- {kubeler-0.1.2.dist-info → kubeler-0.1.4.dist-info}/top_level.txt +0 -0
kubeler/main.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
import argparse
|
2
2
|
from .scripts.installer import Installer
|
3
|
+
from dotenv import load_dotenv
|
3
4
|
|
4
5
|
def main():
|
5
6
|
parser = argparse.ArgumentParser(description="Installer script")
|
@@ -9,11 +10,17 @@ def main():
|
|
9
10
|
install_parser = subparsers.add_parser('install', help='Install command')
|
10
11
|
install_parser.add_argument('--installer', type=str, default='./installer.yaml', help='Path to the config YAML file')
|
11
12
|
install_parser.add_argument('--kube-config', type=str, default='~/kube/config', help='Path to the kube config file')
|
12
|
-
|
13
|
+
install_parser.add_argument('--start-from', type=str, default=None, help='Start from a specific step')
|
14
|
+
install_parser.add_argument('--steps', type=str, default=None, help='Only run specific steps, comma separated')
|
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')
|
13
17
|
args = parser.parse_args()
|
14
18
|
|
19
|
+
# load .env content to environment variables
|
20
|
+
load_dotenv()
|
21
|
+
|
15
22
|
if args.command == 'install':
|
16
|
-
installer = Installer(installer=args.installer, kube_config=args.kube_config)
|
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)
|
17
24
|
installer.install()
|
18
25
|
|
19
26
|
if __name__ == "__main__":
|
kubeler/scripts/installer.py
CHANGED
@@ -1,12 +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):
|
11
|
+
def __init__(self, installer, kube_config, start_from, steps, excludes, watch):
|
8
12
|
self.installer = installer
|
9
13
|
self.kube_config = kube_config
|
14
|
+
self.start_from = start_from
|
15
|
+
self.kube_config = kube_config
|
16
|
+
self.steps = steps
|
17
|
+
self.excludes = excludes
|
18
|
+
self.watch = watch
|
10
19
|
|
11
20
|
# get the directory path of the installer and kube_config
|
12
21
|
self.installer_dir_path = os.path.dirname(installer)
|
@@ -68,19 +77,25 @@ class Installer:
|
|
68
77
|
|
69
78
|
# get commands defined in the file
|
70
79
|
commands = self.load_file(file_path)
|
71
|
-
|
72
|
-
print("Executing command: ", command)
|
73
|
-
cmd = command.split()
|
74
|
-
process = subprocess.Popen(cmd, cwd=execution_dir, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=1, universal_newlines=True)
|
75
|
-
for line in process.stdout:
|
76
|
-
print(line, end="")
|
77
|
-
sys.stdout.flush()
|
78
|
-
process.wait()
|
80
|
+
self.execute(commands, execution_dir)
|
79
81
|
|
80
82
|
# if config_path exists, restore the file
|
81
83
|
if os.path.exists(config_path):
|
82
84
|
os.remove(config_path)
|
83
|
-
|
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
|
+
|
84
99
|
# if there some files in the directory, load them
|
85
100
|
def load_files_in_dir(self, dir):
|
86
101
|
files = []
|
@@ -96,7 +111,9 @@ class Installer:
|
|
96
111
|
for line in file:
|
97
112
|
if line.startswith("#cmd:"):
|
98
113
|
command = line.split(":", 1)[1].strip()
|
114
|
+
command = self.handle_helm_chart_idempotency(command)
|
99
115
|
commands.append(command)
|
116
|
+
|
100
117
|
return commands
|
101
118
|
|
102
119
|
# load the configuration file and parse to Kubeler model
|
@@ -127,7 +144,33 @@ class Installer:
|
|
127
144
|
if var.value.startswith("env."):
|
128
145
|
env_var = var.value.split("env.")[1]
|
129
146
|
var.value = os.environ.get(env_var)
|
130
|
-
|
147
|
+
|
148
|
+
# handle excludes
|
149
|
+
if self.excludes != None:
|
150
|
+
excludes = self.excludes.split(",")
|
151
|
+
for exclude in excludes:
|
152
|
+
for step in kubeler.group.steps:
|
153
|
+
if step.name == exclude:
|
154
|
+
kubeler.group.steps.remove(step)
|
155
|
+
|
156
|
+
for step in kubeler.group.steps:
|
157
|
+
if step.exclude == "yes" or step.exclude == True:
|
158
|
+
kubeler.group.steps.remove(step)
|
159
|
+
|
160
|
+
# handle start from step
|
161
|
+
if self.start_from != None:
|
162
|
+
start_from = self.start_from
|
163
|
+
for step in kubeler.group.steps:
|
164
|
+
if step.name == start_from:
|
165
|
+
kubeler.group.steps = kubeler.group.steps[kubeler.group.steps.index(step):]
|
166
|
+
|
167
|
+
# handle only run specific steps
|
168
|
+
if self.steps != None:
|
169
|
+
steps = self.steps.split(",")
|
170
|
+
for step in kubeler.group.steps[:]:
|
171
|
+
if step.name not in steps:
|
172
|
+
kubeler.group.steps.remove(step)
|
173
|
+
|
131
174
|
return kubeler
|
132
175
|
|
133
176
|
# open the configuration file
|
@@ -137,4 +180,42 @@ class Installer:
|
|
137
180
|
data = yaml.safe_load(stream)
|
138
181
|
return data
|
139
182
|
except yaml.YAMLError as exc:
|
140
|
-
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
|
@@ -14,10 +14,16 @@ class Step(BaseModel):
|
|
14
14
|
dir: str = Field(title="Directory of the step", min_length=3, max_length=255)
|
15
15
|
files: List[str] | None = Field(default=None, title="Files to be processed in order")
|
16
16
|
vars: List[Variable] | None = Field(default=None,title="Variables to be passed to the step")
|
17
|
+
exclude: str | bool | None = Field(default=None, title="Exclude the step from execution")
|
18
|
+
|
19
|
+
class Watch(BaseModel):
|
20
|
+
enabled: bool = Field(title="Watch enabled")
|
21
|
+
dir: List[str] = Field(default=None, title="Directory to be watched")
|
17
22
|
|
18
23
|
class Group(BaseModel):
|
19
24
|
name: str = Field(title="Initial Command")
|
20
25
|
steps: List[Step] = Field(title="List of steps to be executed")
|
26
|
+
watch: Watch | None = Field(default=None,title="Watch configuration")
|
21
27
|
|
22
28
|
class Kubeler(BaseModel):
|
23
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
|
@@ -138,7 +140,7 @@ You can also reference variables from a previous step using `ref.`.
|
|
138
140
|
value: ref.redis.vars.password
|
139
141
|
```
|
140
142
|
|
141
|
-
You can also reference variables from environment variables using `env.`.
|
143
|
+
You can also reference variables from environment variables using `env.`. In addition, using `.env` file defined in the same directory with `installer.yaml` also works for this case.
|
142
144
|
|
143
145
|
```
|
144
146
|
- name: harbor
|
@@ -154,3 +156,63 @@ You can also reference variables from environment variables using `env.`.
|
|
154
156
|
- `dir`: Directory where the manifest files reside.
|
155
157
|
- `files`: List of files in the directory that will be executed in order.
|
156
158
|
- `vars`: Variables for dynamic values inside the manifest file.
|
159
|
+
|
160
|
+
## Options
|
161
|
+
|
162
|
+
There are some options available with installing Kubernetes Resources using Kubeler.
|
163
|
+
|
164
|
+
### Exclude Steps
|
165
|
+
|
166
|
+
Use attributes `exclude=yes` or argument `--excludes` if you want to skip some steps from execution
|
167
|
+
|
168
|
+
```
|
169
|
+
steps:
|
170
|
+
- name: cluster
|
171
|
+
dir: ./cluster
|
172
|
+
exclude: yes
|
173
|
+
```
|
174
|
+
|
175
|
+
or
|
176
|
+
```
|
177
|
+
--installer=./../examples/installer.yaml --excludes=argocd,redis
|
178
|
+
```
|
179
|
+
|
180
|
+
### Define starting point
|
181
|
+
|
182
|
+
Use argument `--start-from` if you want to execute steps from a certain point
|
183
|
+
|
184
|
+
```
|
185
|
+
--installer=./../examples/installer.yaml --start-from=argocd
|
186
|
+
```
|
187
|
+
|
188
|
+
## Execute only some steps
|
189
|
+
|
190
|
+
Use argument `--steps` if you want to only execute some steps and exclude the other
|
191
|
+
|
192
|
+
```
|
193
|
+
--installer=./../examples/installer.yaml --steps=argocd,redis
|
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.2.dist-info/RECORD
DELETED
@@ -1,12 +0,0 @@
|
|
1
|
-
kubeler/__init__.py,sha256=xuYziAiR_MvvngneqgskzBPoVTHLwxRs6l--kORxv7s,7
|
2
|
-
kubeler/main.py,sha256=awZQX-gALoffxvgGHWqGKlcJ68jOv5goTWQvAebL9Ls,822
|
3
|
-
kubeler/scripts/__init__.py,sha256=xuYziAiR_MvvngneqgskzBPoVTHLwxRs6l--kORxv7s,7
|
4
|
-
kubeler/scripts/installer.py,sha256=21xTHLDQ24rshuJtw6NXbnnSVhPeNVcZDXUAVNQeV9I,5561
|
5
|
-
kubeler/scripts/models/__init__.py,sha256=xuYziAiR_MvvngneqgskzBPoVTHLwxRs6l--kORxv7s,7
|
6
|
-
kubeler/scripts/models/kubeler.py,sha256=0NMrxdrscikNLx3nS1097AInb39NQkbaVpBE17Dj8e4,1041
|
7
|
-
kubeler-0.1.2.dist-info/LICENSE,sha256=g8iEDGIIhDh0wHq4B7md4cWUJ69HC13i010cmASC9Lg,1059
|
8
|
-
kubeler-0.1.2.dist-info/METADATA,sha256=31vh65R7FZHI6CpHCDpZsTLJlC7DN_vIB3dYpDP_Y3I,3380
|
9
|
-
kubeler-0.1.2.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
10
|
-
kubeler-0.1.2.dist-info/entry_points.txt,sha256=-HM8L2j9zOFjn8DGCEqANNS-rSvA8dXlyzWhTZD_02Q,46
|
11
|
-
kubeler-0.1.2.dist-info/top_level.txt,sha256=Chw1LcDOq_cKEex9nHyQOMq8-C6QpLdJjK1s_1MnDEk,8
|
12
|
-
kubeler-0.1.2.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|