Skip to content

Commit

Permalink
Add minio enterprise (#8)
Browse files Browse the repository at this point in the history
* test ci

* make sure if docker daemon does not start any containers, we fail

* fix image

* add minio enterprise

* test perms

* update lib funcs

* named arsg

* fix indents

* imports

* fix identation

* check if db is needed

* add todo

* one omre

* add dep

* fix

* add macros

* auto perms

* remove dummy file

* fix values

* updates

* minio perms

* abstract away

* cleaner

* safer,cleaner

* new storage funcs

* storage options

* use built ins

* rename

* spelling

* rename

* add vol suport

* test vol

* manually add container name

* volumes

* cleaner vols

* do some renames

* renames

* squash

* add todo

* move few things to the lib, currently broken

* more dynamic container names

* fix message

* cleanup

* update app

* spelling

* remove port

* more fixes

* fix pg test

* hm

* thats better

* fmt

* back to jinja

* duh

* wait for it

* typo

* typo

* ui

* fix checks

* todo

* items is a builtin

* fixes

* resources

* resources

* -.-

* ...

* use lib

* add rough migration paths

* Update docker-compose.yaml

* Update docker-compose.yaml

* move usages under value

* update usages

* cleanup

* fix url

* order

* another url

* fix

* fix hc too

* update app.yaml

* update lib

* update lib

* update lib

* update lib

* update lib

* update lib

* update lib

* update lib

* lint

* update lib

* fix ixvol

* update lib

* update lib

* update lib

* remove logsearch/postgres

* fmt

* replace set _

* update lib

* adapt to lib changes

* update lib

* update lib

* update lib

* update lib

* update lib

* update lib

* sync library

* updates

* update lib

* update compose

* typo

* update ci checks

* now actually fix app

* add migration

* fix migration and update error message

* order

* remove test data

* update readme

* fix ui
  • Loading branch information
stavros-k authored Aug 15, 2024
1 parent 26d4886 commit 67ef963
Show file tree
Hide file tree
Showing 36 changed files with 2,276 additions and 14 deletions.
78 changes: 66 additions & 12 deletions .github/scripts/ci.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,25 +229,79 @@ def get_parsed_containers():
return parsed_containers


def status_indicates_healthcheck_existence(container):
"""Assumes healthcheck exists if status contains "health" """
# eg "health: starting". This happens right after a container is started or restarted
return "health" in container.get("Status", "")


def state_indicates_restarting(container):
"""Assumes restarting if state is "restarting" """
return container.get("State", "") == "restarting"


def exit_code_indicates_normal_exit(container):
"""Assumes normal exit if there is no exit code or if it is 0"""
return container.get("ExitCode", 0) == 0


def health_indicates_healthy(container):
"""Assumes healthy if there is no health status or if it is "healthy" """
health = container.get("Health", "")
if health in ["healthy", ""]:
return True
return False


def is_considered_healthy(container):
message = [
f"Skipping container [{container['Name']}({container['ID']})] with status [{container.get('State')}]"
+ " for the following reasons:"
]
reasons = []

if health_indicates_healthy(container):
reasons.append("\t- Container is healthy")

if exit_code_indicates_normal_exit(container):
reasons.append(f"\t- Exit code is [{container.get('ExitCode', 0)}]")

if not state_indicates_restarting(container):
reasons.append("\t- Container is not restarting")

if not status_indicates_healthcheck_existence(container):
reasons.append("\t- Status does not indicate a healthcheck exists")

# Mark it as healthy if ALL of the following are true:
# 1. It is healthy
# 2. Its exit code is normal
# 3. Its not restarting
# 4. It does not indicate a healthcheck exists

# For #4, there was some cases where the container was restarting and at the time of check,
# the "Health" was empty and "State" was "running" (similar to init containers). This check
# added to try to catch those cases, by inspecting the "Status" field which if there is a healthcheck
# it will contain the word "health".
result = (
health_indicates_healthy(container)
and exit_code_indicates_normal_exit(container)
and not state_indicates_restarting(container)
and not status_indicates_healthcheck_existence(container)
)

return {"result": result, "reasons": "\n".join(message + reasons)}


def get_failed_containers():
parsed_containers = get_parsed_containers()

failed = []
for container in parsed_containers:
# Skip containers that are exited with 0 (eg init containers),
# but not restarting (during a restart exit code is 0)
if (
(
container.get("Health", "") == ""
or container.get("Health", "") == "healthy"
)
and container.get("ExitCode", 0) == 0
and (not container.get("State", "") == "restarting")
):
print_stderr(
f"Skipping container [{container['Name']}({container['ID']})] with status [{container.get('State')}]"
+ " because it exited with 0 and has no health status"
)
is_healthy = is_considered_healthy(container)
if is_healthy["result"]:
print_stderr(is_healthy["reasons"])
continue
failed.append(container)

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,5 +120,5 @@ Thank you for your understanding.
| whoogle | community || - |
| wordpress | community | - | - |
| zerotier | community || - |
| minio | enterprise | - | - |
| minio | enterprise | | |
| syncthing | enterprise | - | - |
8 changes: 7 additions & 1 deletion cspell.config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ words:
- consts
- cooldown
- cpus
- creds
- cuda
- ddns
- ddnss
Expand Down Expand Up @@ -48,6 +49,8 @@ words:
- goip
- goipde
- graalvm
- healthcheck
- healthchecks
- hetzner
- hexparrot
- homarr
Expand All @@ -59,6 +62,7 @@ words:
- inwx
- ionos
- ipaddr
- ipam
- ipfs
- ipify
- ipinfo
Expand All @@ -73,7 +77,6 @@ words:
- komga
- libsmbclient
- lidarr
- logsearch
- logseq
- luadns
- mangas
Expand Down Expand Up @@ -122,6 +125,9 @@ words:
- rclone
- rcon
- readarr
- rprivate
- rshared
- rslave
- scandir
- seeip
- selfhost
Expand Down
5 changes: 5 additions & 0 deletions ix-dev/enterprise/minio/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# MinIO

[MinIO](https://min.io) is a High Performance Object Storage released under Apache License v2.0.
It is API compatible with Amazon S3 cloud storage service. Use MinIO to build high performance infrastructure
for machine learning, analytics and application data workloads.
34 changes: 34 additions & 0 deletions ix-dev/enterprise/minio/app.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
app_version: '2023-12-07'
capabilities: []
categories:
- networking
description: High Performance, Kubernetes Native Object Storage
home: https://min.io
host_mounts: []
icon: https://media.sys.truenas.net/apps/minio/icons/icon.png
keywords:
- object storage
- minio
- cloud
- s3
lib_version: 1.0.0
lib_version_hash: 317726af1e56541666942aeebcc7543e6f0946f96c322d35b612c0f2f7189a88
maintainers:
- email: [email protected]
name: truenas
url: https://www.truenas.com/
name: minio
run_as_context:
- description: MinIO runs as any non-root user.
gid: 568
group_name: minio
uid: 568
user_name: minio
screenshots:
- https://media.sys.truenas.net/apps/adguard-home/screenshots/screenshot1.png
- https://media.sys.truenas.net/apps/adguard-home/screenshots/screenshot2.png
sources:
- https://github.com/minio/minio
title: MinIO
train: enterprise
version: 1.0.0
11 changes: 11 additions & 0 deletions ix-dev/enterprise/minio/item.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
categories:
- networking
icon_url: https://media.sys.truenas.net/apps/minio/icons/icon.png
screenshots:
- https://media.sys.truenas.net/apps/adguard-home/screenshots/screenshot1.png
- https://media.sys.truenas.net/apps/adguard-home/screenshots/screenshot2.png
tags:
- object storage
- minio
- cloud
- s3
8 changes: 8 additions & 0 deletions ix-dev/enterprise/minio/ix_values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
images:
image:
repository: minio/minio
tag: RELEASE.2023-12-07T04-16-00Z

consts:
minio_container_name: minio
perms_container_name: permissions
58 changes: 58 additions & 0 deletions ix-dev/enterprise/minio/migrations/migrate_from_kubernetes
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/usr/bin/python3

import os
import sys
import yaml

from migration_helpers.resources import migrate_resources
from migration_helpers.storage import migrate_storage_item


def migrate(values):
config = values.get("helm_secret", {}).get("config", {})
if not config:
raise ValueError("No config found in values")

new_values = {
"minio": {
"credentials": {
"access_key": config["minioCreds"]["rootUser"],
"secret_key": config["minioCreds"]["rootPass"],
},
"logging": {
"quiet": config["minioLogging"]["quiet"],
"anonymous": config["minioLogging"]["anonymous"],
},
"multi_mode": {
"enabled": config["enableMultiMode"],
"entries": config.get("minioMultiMode", []),
},
},
"run_as": {
"user": config["minioRunAs"].get("user", 568),
"group": config["minioRunAs"].get("group", 568),
},
"network": {
"api_port": config["minioNetwork"]["apiPort"],
"console_port": config["minioNetwork"]["webPort"],
"host_network": config["minioNetwork"]["hostNetwork"],
"certificate_id": config["minioNetwork"].get("certificateID", None),
"server_url": config["minioNetwork"]["serverUrl"],
"console_url": config["minioNetwork"]["consoleUrl"],
},
"storage": {
"data_dirs": [migrate_storage_item(item) for item in config["minioStorage"]]
},
"resources": migrate_resources(config["resources"]),
}

return new_values


if __name__ == "__main__":
if len(sys.argv) != 2:
exit(1)

if os.path.exists(sys.argv[1]):
with open(sys.argv[1], "r") as f:
print(yaml.dump(migrate(yaml.safe_load(f.read()))))
Empty file.
27 changes: 27 additions & 0 deletions ix-dev/enterprise/minio/migrations/migration_helpers/cpu.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import math
import re
import os

CPU_COUNT = os.cpu_count()

NUMBER_REGEX = re.compile(r"^[1-9][0-9]$")
FLOAT_REGEX = re.compile(r"^[0-9]+\.[0-9]+$")
MILI_CPU_REGEX = re.compile(r"^[0-9]+m$")


def transform_cpu(cpu) -> int:
result = 2
if NUMBER_REGEX.match(cpu):
result = int(cpu)
elif FLOAT_REGEX.match(cpu):
result = int(math.ceil(float(cpu)))
elif MILI_CPU_REGEX.match(cpu):
num = int(cpu[:-1])
num = num / 1000
result = int(math.ceil(num))

if CPU_COUNT is not None:
# Do not exceed the actual CPU count
result = min(result, CPU_COUNT)

return result
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
def migrate_dns_config(dns_config):
if not dns_config:
return []

dns_opts = []
for opt in dns_config.get("options", []):
dns_opts.append(f"{opt['name']}:{opt['value']}")

return dns_opts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
def get_value_from_secret(secrets={}, secret_name="", key=""):
if not secrets or not secret_name or not key:
raise ValueError("Expected [secrets], [secret_name] and [key] to be set")
for secret in secrets.items():
curr_secret_name = secret[0]
curr_data = secret[1]

if curr_secret_name.endswith(secret_name):
if not curr_data.get(key, None):
raise ValueError(
f"Expected [{key}] to be set in secret [{curr_secret_name}]"
)
return curr_data[key]

raise ValueError(f"Secret [{secret_name}] not found")
49 changes: 49 additions & 0 deletions ix-dev/enterprise/minio/migrations/migration_helpers/memory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import re
import math
import psutil

TOTAL_MEM = psutil.virtual_memory().total

SINGLE_SUFFIX_REGEX = re.compile(r"^[1-9][0-9]*([EPTGMK])$")
DOUBLE_SUFFIX_REGEX = re.compile(r"^[1-9][0-9]*([EPTGMK])i$")
BYTES_INTEGER_REGEX = re.compile(r"^[1-9][0-9]*$")
EXPONENT_REGEX = re.compile(r"^[1-9][0-9]*e[0-9]+$")

SUFFIX_MULTIPLIERS = {
"K": 10**3,
"M": 10**6,
"G": 10**9,
"T": 10**12,
"P": 10**15,
"E": 10**18,
}

DOUBLE_SUFFIX_MULTIPLIERS = {
"Ki": 2**10,
"Mi": 2**20,
"Gi": 2**30,
"Ti": 2**40,
"Pi": 2**50,
"Ei": 2**60,
}


def transform_memory(memory):
result = 4096 # Default to 4GB

if re.match(SINGLE_SUFFIX_REGEX, memory):
suffix = memory[-1]
result = int(memory[:-1]) * SUFFIX_MULTIPLIERS[suffix]
elif re.match(DOUBLE_SUFFIX_REGEX, memory):
suffix = memory[-2:]
result = int(memory[:-2]) * DOUBLE_SUFFIX_MULTIPLIERS[suffix]
elif re.match(BYTES_INTEGER_REGEX, memory):
result = int(memory)
elif re.match(EXPONENT_REGEX, memory):
result = int(float(memory))

result = math.ceil(result)
result = min(result, TOTAL_MEM)
# Convert to Megabytes
result = result / 1024 / 1024
return int(result)
Loading

0 comments on commit 67ef963

Please sign in to comment.