Compare commits

..

17 Commits

Author SHA1 Message Date
560d21c9fe Merge pull request 'fix' (#42) from master into dev
Reviewed-on: #42
2025-06-08 11:49:43 +03:00
4f0571ec02 Merge pull request 'fix' (#41) from master into dev
Reviewed-on: #41
2025-06-08 11:38:38 +03:00
ad5a22e985 Merge pull request 'fix' (#40) from master into dev
Reviewed-on: #40
2025-06-08 11:05:33 +03:00
d288a8a883 Merge pull request 'fix' (#39) from master into dev
Reviewed-on: #39
2025-06-08 11:02:19 +03:00
78eeea9e08 Merge pull request 'fix' (#38) from master into dev
Reviewed-on: #38
2025-06-08 10:59:33 +03:00
1f7cef1f4b Merge pull request 'fix' (#37) from master into dev
Reviewed-on: #37
2025-06-07 13:24:10 +03:00
6cedf34b8e Merge pull request 'master' (#35) from master into dev
Reviewed-on: #35
2025-06-04 21:08:09 +03:00
df0af634d6 Merge pull request 'fix' (#31) from master into dev
Reviewed-on: #31
2025-06-04 02:34:37 +03:00
41072ac03f Merge pull request 'fix' (#30) from master into dev
Reviewed-on: #30
2025-06-03 22:58:47 +03:00
43191f99a9 Merge pull request 'fix' (#29) from master into dev
Reviewed-on: #29
2025-06-03 22:44:39 +03:00
41dea63243 Merge pull request 'fix' (#28) from master into dev
Reviewed-on: #28
2025-06-02 00:19:28 +03:00
ee9c363b56 Merge pull request 'fix' (#27) from master into dev
Reviewed-on: #27
2025-06-01 20:04:53 +03:00
1ad823a301 Merge pull request 'fix' (#26) from master into dev
Reviewed-on: #26
2025-06-01 19:58:41 +03:00
ded08985b5 Merge pull request 'fix' (#25) from master into dev
Reviewed-on: #25
2025-06-01 19:55:24 +03:00
cb847f8b8f Merge pull request 'fix' (#24) from master into dev
Reviewed-on: #24
2025-06-01 19:53:30 +03:00
a7b082dc7a Merge pull request 'master' (#23) from master into dev
Reviewed-on: #23
2025-06-01 19:51:42 +03:00
741eed066c Merge pull request 'fix' (#22) from master into dev
Reviewed-on: #22
2025-06-01 19:48:51 +03:00
8 changed files with 189 additions and 127 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@@ -6,16 +6,15 @@ services:
image: mathwave/sprint-repo:certupdater image: mathwave/sprint-repo:certupdater
command: worker command: worker
environment: environment:
MINIO_HOST: "minio.develop.sprinthub.ru"
MINIO_SECRET_KEY: $MINIO_SECRET_KEY_DEV MINIO_SECRET_KEY: $MINIO_SECRET_KEY_DEV
MONGO_HOST: "mongo.develop.sprinthub.ru"
MONGO_PASSWORD: $MONGO_PASSWORD_DEV MONGO_PASSWORD: $MONGO_PASSWORD_DEV
STAGE: "development" STAGE: "development"
volumes: volumes:
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
networks: networks:
- configurator - configurator
- queues-development
- minio-development
- mongo-development
deploy: deploy:
mode: replicated mode: replicated
restart_policy: restart_policy:
@@ -29,9 +28,3 @@ services:
networks: networks:
configurator: configurator:
external: true external: true
queues-development:
external: true
minio-development:
external: true
mongo-development:
external: true

View File

@@ -6,16 +6,15 @@ services:
image: mathwave/sprint-repo:certupdater image: mathwave/sprint-repo:certupdater
command: worker command: worker
environment: environment:
MINIO_HOST: "minio.sprinthub.ru"
MINIO_SECRET_KEY: $MINIO_SECRET_KEY_PROD MINIO_SECRET_KEY: $MINIO_SECRET_KEY_PROD
MONGO_HOST: "mongo.sprinthub.ru"
MONGO_PASSWORD: $MONGO_PASSWORD_PROD MONGO_PASSWORD: $MONGO_PASSWORD_PROD
STAGE: "production" STAGE: "production"
volumes: volumes:
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
networks: networks:
- configurator - configurator
- queues
- minio
- mongo
deploy: deploy:
mode: replicated mode: replicated
restart_policy: restart_policy:
@@ -29,9 +28,3 @@ services:
networks: networks:
configurator: configurator:
external: true external: true
queues:
external: true
minio:
external: true
mongo:
external: true

View File

@@ -1,7 +1,7 @@
import os import os
from minio import Minio from minio import Minio
MINIO_HOST = "minio:9000" MINIO_HOST = os.getenv("MINIO_HOST", "localhost") + ":9000"
MINIO_ACCESS_KEY = os.getenv("MINIO_ACCESS_KEY", "serviceminioadmin") MINIO_ACCESS_KEY = os.getenv("MINIO_ACCESS_KEY", "serviceminioadmin")
MINIO_SECRET_KEY = os.getenv("MINIO_SECRET_KEY", "minioadmin") MINIO_SECRET_KEY = os.getenv("MINIO_SECRET_KEY", "minioadmin")

88
configurator.py Normal file
View File

@@ -0,0 +1,88 @@
import json
import os
import urllib.parse
from threading import Thread
from time import sleep
from requests import get
class ConfiguratorClient:
def __init__(self, app_name: str, stage: str, need_poll: bool = True):
self.app_name = app_name
self.stage = stage
self.endpoint = 'http://configurator/'
self.fetch_url = urllib.parse.urljoin(self.endpoint, '/api/v1/fetch')
self.config_storage = {}
self.experiment_storage = {}
self.staff_storage = {}
self.poll_data()
if need_poll:
self.poll_data_in_thread()
def poll_data_in_thread(self):
def inner():
while True:
sleep(30)
self.fetch()
Thread(target=inner, daemon=True).start()
def poll_data(self):
self.fetch(with_exception=True)
def request_with_retries(self, url, params, with_exception=False, retries_count=3):
exception_to_throw = None
for _ in range(retries_count):
try:
response = get(
url,
params=params
)
if response.status_code == 200:
return response.json()
print(f'Failed to request {url}, status_code={response.status_code}')
exception_to_throw = Exception('Not 200 status')
except Exception as exc:
print(exc)
exception_to_throw = exc
sleep(1)
print(f'Failed fetching with retries: {url}, {params}')
if with_exception:
raise exception_to_throw
def fetch(self, with_exception=False):
if self.stage == 'local':
local_platform = json.loads(open('local_platform.json', 'r').read())
self.config_storage = local_platform['configs']
self.experiment_storage = local_platform['experiments']
self.staff_storage = {
key: set(value)
for key, value in local_platform['platform_staff'].items()
}
return
response_data = self.request_with_retries(self.fetch_url, {
'project': self.app_name,
'stage': self.stage,
}, with_exception)
self.config_storage = response_data['configs']
self.experiment_storage = response_data['experiments']
self.staff_storage = {
key: set(value)
for key, value in response_data['platform_staff'].items()
}
def is_staff(self, **kwargs):
for key, value in kwargs.items():
if value in self.staff_storage[key]:
return True
return False
def get_config(self, name):
return self.config_storage[name]
def get_experiment(self, name):
return self.experiment_storage[name]
configurator = ConfiguratorClient("certupdater", os.getenv("STAGE"))

159
main.py
View File

@@ -3,8 +3,7 @@ import io
import os import os
import subprocess import subprocess
import time import time
from configurator import configurator
from requests import get, post
from mongo import mongo from mongo import mongo
from blob import minio from blob import minio
@@ -15,136 +14,82 @@ class Response:
err: str err: str
def send_notification(text: str):
post(
"http://queues:1239/api/v1/put",
headers={"queue": "botalka_mailbox"},
json={
"payload": {
"project": "notifications-bot",
"name": "telegram-bot",
"body": {
"text": text,
"chat_id": 84367486,
},
},
"seconds_to_execute": 1,
"delay": None,
},
)
def call(command: str) -> Response: def call(command: str) -> Response:
p = subprocess.Popen( p = subprocess.Popen(command, stderr=subprocess.PIPE, stdout=subprocess.PIPE, shell=True)
command, stderr=subprocess.PIPE, stdout=subprocess.PIPE, shell=True
)
resp = p.wait() resp = p.wait()
response = Response() response = Response()
response.code = resp response.code = resp
response.out, response.err = p.stdout.read().decode( response.out, response.err = p.stdout.read().decode('utf-8'), p.stderr.read().decode('utf-8')
"utf-8"
), p.stderr.read().decode("utf-8")
return response return response
def get_hosts() -> list[str]: def get_hosts() -> list[str]:
response = get(
f"http://configurator/api/v1/fetch?project=certupdater&stage={os.getenv("STAGE")}"
).json()
hosts = response["configs"]["hosts"]
return list(hosts)
def update_host(host: str) -> str | None:
if os.getenv("STAGE") == "development": if os.getenv("STAGE") == "development":
container_id_run = call("echo $(docker ps -q -f name=infra-development_nginx)") return list(set(list(configurator.get_config("hosts")) + ["platform.develop.sprinthub.ru"]))
else: else:
container_id_run = call("echo $(docker ps -q -f name=infra_nginx)") return list(set(list(configurator.get_config("hosts")) + ["platform.sprinthub.ru"]))
def update_host(host: str) -> bool:
if os.getenv("STAGE") == "development":
container_id_run = call(f"echo $(docker ps -q -f name=infra-development_nginx)")
else:
container_id_run = call(f"echo $(docker ps -q -f name=infra_nginx)")
if container_id_run.code != 0: if container_id_run.code != 0:
return container_id_run.err print(f"something wrong {container_id_run.err}")
return False
container_name = container_id_run.out.strip() container_name = container_id_run.out.strip()
if not container_name: if not container_name:
return "no nginx container" print("No nginx container")
return False
gen_command = f'docker exec {container_name} certbot --nginx --email emmtvv@gmail.com --agree-tos --non-interactive -d "{host}"' gen_command = f"docker exec {container_name} certbot --nginx --email emmtvv@gmail.com --agree-tos --non-interactive -d \"{host}\""
print(gen_command)
gen_cert = call(gen_command) gen_cert = call(gen_command)
if gen_cert.code != 0: if gen_cert.code != 0:
log = call( print(f"failed generating certificate: {gen_cert.err}")
f"docker exec {container_name} cat /var/log/letsencrypt/letsencrypt.log" print("Here is the log")
).out print(call(f"docker exec {container_name} cat /var/log/letsencrypt/letsencrypt.log").out)
return f"failed generating certificate: {log}" return False
fullchain_command = call( fullchain_command = call(f"docker exec {container_name} cat /etc/letsencrypt/live/{host}/fullchain.pem")
f"docker exec {container_name} cat /etc/letsencrypt/live/{host}/fullchain.pem"
)
if fullchain_command.code != 0: if fullchain_command.code != 0:
return f"failed getting fullchain: {fullchain_command.err}" print(f"failed getting fullchain: {fullchain_command.err}")
return True
privkey_command = call( privkey_command = call(f"docker exec {container_name} cat /etc/letsencrypt/live/{host}/privkey.pem")
f"docker exec {container_name} cat /etc/letsencrypt/live/{host}/privkey.pem"
)
if privkey_command.code != 0: if privkey_command.code != 0:
return f"failed getting fullchain: {privkey_command.err}" print(f"failed getting fullchain: {privkey_command.err}")
return True
fullchain = fullchain_command.out.encode("utf-8") fullchain = fullchain_command.out.encode("utf-8")
privkey = privkey_command.out.encode("utf-8") privkey = privkey_command.out.encode("utf-8")
minio.put_object( minio.put_object("certupdater", f"certificates/{host}/fullchain.pem", io.BytesIO(fullchain), len(fullchain))
"certupdater", minio.put_object("certupdater", f"certificates/{host}/privkey.pem", io.BytesIO(privkey), len(privkey))
f"certificates/{host}/fullchain.pem", return True
io.BytesIO(fullchain),
len(fullchain),
)
minio.put_object(
"certupdater",
f"certificates/{host}/privkey.pem",
io.BytesIO(privkey),
len(privkey),
)
return None
if __name__ == "__main__": while True:
while True: now = datetime.datetime.now()
now = datetime.datetime.now() mongo_hosts = mongo.hosts
mongo_hosts = mongo.hosts updated = False
hosts = get_hosts() for host in get_hosts():
print(f"got hosts {hosts}") if now + datetime.timedelta(days=14) > mongo_hosts.get(host, {"expire_time": datetime.datetime.fromtimestamp(1)})["expire_time"]:
updated = False success = update_host(host)
for host in hosts: if success:
if ( print(f"Host {host} updated")
now + datetime.timedelta(days=14) mongo.update_date(host)
> mongo_hosts.get( updated = True
host, {"expire_time": datetime.datetime.fromtimestamp(1)} if updated:
)["expire_time"] if os.getenv("STAGE") == "development":
): container_id_run = call("echo $(docker ps -q -f name=infra-development_nginx)")
success = update_host(host) else:
if success: container_id_run = call("echo $(docker ps -q -f name=infra_nginx)")
print(success)
send_notification( print(container_id_run.code, container_id_run.out, container_id_run.err)
f"host {host} was not updated with an error: {success}"
)
else:
mongo.update_date(host)
updated = True
send_notification(f"host {host} updated")
else:
print(f"Host {host} does not need to be updated")
if updated:
if os.getenv("STAGE") == "development":
container_id_run = call(
"echo $(docker ps -q -f name=infra-development_nginx)"
)
else:
container_id_run = call("echo $(docker ps -q -f name=infra_nginx)")
print(container_id_run.code, container_id_run.out, container_id_run.err) restart = call(f"docker exec {container_id_run} ./refre.sh")
print(restart.code, restart.out, restart.err)
command = f"docker exec {container_id_run.out.strip()} ./refre.sh" time.sleep(30)
print(command)
restart = call(command)
print(restart.code, restart.out, restart.err)
send_notification(f"Balancer for {os.getenv("STAGE")} was restarted")
time.sleep(30)

View File

@@ -4,11 +4,12 @@ import os
MONGO_USER = os.getenv("MONGO_USER", "mongo") MONGO_USER = os.getenv("MONGO_USER", "mongo")
MONGO_PASSWORD = os.getenv("MONGO_PASSWORD", "password") MONGO_PASSWORD = os.getenv("MONGO_PASSWORD", "password")
MONGO_HOST = os.getenv("MONGO_HOST", "localhost")
class Mongo: class Mongo:
def __init__(self): def __init__(self):
url = f"mongodb://{MONGO_USER}:{MONGO_PASSWORD}@mongo:27017/" url = f"mongodb://{MONGO_USER}:{MONGO_PASSWORD}@{MONGO_HOST}:27017/"
self.client: pymongo.MongoClient = pymongo.MongoClient(url) self.client: pymongo.MongoClient = pymongo.MongoClient(url)
self.database = self.client.get_database("certupdater") self.database = self.client.get_database("certupdater")
self.hosts_collection.create_index([ self.hosts_collection.create_index([

42
storage.py Normal file
View File

@@ -0,0 +1,42 @@
from cachetools import TTLCache
import os
from utils.mongo import mongo
CACHE_SIZE = int(os.getenv("CACHE_SIZE", 1000))
CACHE_TTL = int(os.getenv("CACHE_TTL", 3600))
cache = TTLCache(CACHE_SIZE, CACHE_TTL)
def get_chat_info(chat_id: int) -> dict:
cached_info = cache.get(chat_id)
if cached_info is not None:
return cached_info
mongo_info = mongo.chats_collection.find_one({"chat_id": chat_id})
if mongo_info is not None:
cache[chat_id] = mongo_info
return mongo_info
chat_info = {"chat_id": chat_id, "state": "default", "probability": 100}
mongo.chats_collection.insert_one(chat_info)
cache[chat_id] = chat_info
return chat_info
def set_values(chat_id: int, **values):
cached_info = cache.get(chat_id)
if cached_info is None:
mongo_info = mongo.chats_collection.find_one({"chat_id": chat_id})
if mongo_info is None:
chat_info = {"chat_id": chat_id, "state": "default", "probability": 100}
chat_info.update(values)
mongo.chats_collection.insert_one(chat_info)
cache[chat_id] = chat_info
else:
mongo.chats_collection.update_one({"chat_id": chat_id}, {"$set": values})
mongo_info = dict(mongo_info)
mongo_info.update(values)
cache[chat_id] = mongo_info
else:
cached_info.update(values)
mongo.chats_collection.update_one({"chat_id": chat_id}, {"$set": values})