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 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__":
@@ -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
- for command in commands:
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.2
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,,
@@ -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,,