feat(provider): add fission_mq_trigger, fission_cron_trigger, fission_iot_device + weather-demo example

This commit is contained in:
“Naeel”
2026-05-11 09:37:20 +04:00
parent fe8f6a871b
commit fbd565651a
12 changed files with 1203 additions and 0 deletions
+45
View File
@@ -0,0 +1,45 @@
import os
import json
import psycopg2
def main(event, context):
pg_dsn = os.environ["PG_DSN"]
# Извлекаем тело сообщения из SQS (POST от sqs-consumer)
body = getattr(event, "body", event)
if isinstance(body, (bytes, bytearray)):
body = body.decode("utf-8")
if isinstance(body, str):
data = json.loads(body)
else:
data = body
conn = psycopg2.connect(pg_dsn)
try:
cur = conn.cursor()
cur.execute(
"""
INSERT INTO weather_metrics
(city, country, temperature, feels_like, humidity,
pressure, wind_speed, description, recorded_at)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, to_timestamp(%s))
""",
(
data["city"],
data["country"],
data["temperature"],
data["feels_like"],
data["humidity"],
data["pressure"],
data["wind_speed"],
data["description"],
data["owm_timestamp"],
),
)
conn.commit()
cur.close()
finally:
conn.close()
return {"status": "ok", "city": data["city"], "temp": data["temperature"]}
@@ -0,0 +1 @@
psycopg2-binary==2.9.9
+68
View File
@@ -0,0 +1,68 @@
import os
import json
import requests
import boto3
from botocore.config import Config
CITIES = [
"Moscow,RU",
"London,GB",
"Paphos,CY",
"Ulyanovsk,RU",
"Santiago,CL",
]
def main(event, context):
api_key = os.environ["OWM_API_KEY"]
sqs_endpoint = os.environ.get(
"SQS_ENDPOINT", "http://shared-sqs.shared-sqs.svc.cluster.local:4100"
)
access_key = os.environ["SQS_ACCESS_KEY"]
secret_key = os.environ["SQS_SECRET_KEY"]
queue_name = os.environ.get("SQS_QUEUE_NAME", "weather-data")
cfg = Config(signature_version="s3v4", s3={"addressing_style": "path"})
sqs = boto3.client(
"sqs",
endpoint_url=sqs_endpoint,
aws_access_key_id=access_key,
aws_secret_access_key=secret_key,
region_name="us-east-1",
config=cfg,
)
# Создаём очередь если не существует
try:
queue_url = sqs.get_queue_url(QueueName=queue_name)["QueueUrl"]
except Exception:
queue_url = sqs.create_queue(QueueName=queue_name)["QueueUrl"]
results = []
for city in CITIES:
try:
resp = requests.get(
"https://api.openweathermap.org/data/2.5/weather",
params={"q": city, "appid": api_key, "units": "metric"},
timeout=10,
)
resp.raise_for_status()
data = resp.json()
msg = {
"city": data["name"],
"country": data["sys"]["country"],
"temperature": data["main"]["temp"],
"feels_like": data["main"]["feels_like"],
"humidity": data["main"]["humidity"],
"pressure": data["main"]["pressure"],
"wind_speed": data["wind"]["speed"],
"description": data["weather"][0]["description"],
"owm_timestamp": data["dt"],
}
sqs.send_message(QueueUrl=queue_url, MessageBody=json.dumps(msg))
results.append({"city": msg["city"], "temp": msg["temperature"]})
except Exception as e:
results.append({"city": city, "error": str(e)})
return {"status": "ok", "sent": len(results), "results": results}
@@ -0,0 +1,2 @@
requests==2.31.0
boto3==1.34.0
+149
View File
@@ -0,0 +1,149 @@
terraform {
required_providers {
fission = {
source = "nail/fission"
version = "~> 0.2.0"
}
}
}
provider "fission" {
kubeconfig_path = var.kubeconfig_path
namespace = var.namespace
}
# ── Переменные ───────────────────────────────────────────────────────
variable "kubeconfig_path" {
default = "/home/naeel/.kube/config"
description = "Путь к kubeconfig."
}
variable "namespace" {
default = "fission-weather"
description = "Namespace для функций и триггеров."
}
variable "owm_api_key" {
sensitive = true
description = "OpenWeatherMap API key."
}
variable "sqs_access_key" {
sensitive = true
description = "SQS Access Key."
}
variable "sqs_secret_key" {
sensitive = true
description = "SQS Secret Key."
}
variable "pg_dsn" {
sensitive = true
description = "PostgreSQL DSN для записи метрик. Пример: postgresql://user:pass@host:5432/db?sslmode=disable"
}
# ── IoT-устройство — виртуальная метеостанция ────────────────────────
resource "fission_iot_device" "weather_station" {
name = "weather-station"
device_id = "weather-station-01"
namespace = "sless"
metadata = {
type = "weather-station"
location = "multi-city"
cities = "Moscow,London,Paphos,Ulyanovsk,Santiago"
}
}
# ── Python environment ───────────────────────────────────────────────
resource "fission_environment" "python" {
name = "weather-python"
image = "naeel/fission-python-env:v1.0"
version = 2
namespace = var.namespace
}
# ── Пакет: fetcher ───────────────────────────────────────────────────
resource "fission_package" "fetcher" {
name = "weather-fetcher-pkg"
environment = fission_environment.python.name
namespace = var.namespace
source_dir = "${path.module}/fetcher"
deploy_type = "source"
}
# ── Пакет: consumer ──────────────────────────────────────────────────
resource "fission_package" "consumer" {
name = "weather-consumer-pkg"
environment = fission_environment.python.name
namespace = var.namespace
source_dir = "${path.module}/consumer"
deploy_type = "source"
}
# ── Функция: fetcher (читает OWM, пишет в SQS) ───────────────────────
resource "fission_function" "fetcher" {
name = "weather-fetcher"
environment = fission_environment.python.name
namespace = var.namespace
package_name = fission_package.fetcher.name
entrypoint = "main"
# Env vars с секретами задаются через K8s Secret вне Terraform.
# Имя секрета: weather-fetcher-env (namespace: var.namespace)
# Ключи: OWM_API_KEY, SQS_ACCESS_KEY, SQS_SECRET_KEY
}
# ── CRON trigger: каждые 10 минут ────────────────────────────────────
resource "fission_cron_trigger" "fetcher" {
name = "weather-cron"
function = fission_function.fetcher.name
namespace = var.namespace
cron = "*/10 * * * *"
}
# ── Функция: consumer (читает из SQS, пишет в PG) ────────────────────
resource "fission_function" "consumer" {
name = "weather-consumer"
environment = fission_environment.python.name
namespace = var.namespace
package_name = fission_package.consumer.name
entrypoint = "main"
# Env vars: PG_DSN задаётся через K8s Secret weather-consumer-env
}
# ── MQ trigger: SQS queue → consumer function ────────────────────────
resource "fission_mq_trigger" "weather" {
name = "weather-mq"
function = fission_function.consumer.name
namespace = var.namespace
queue = "weather-data"
access_key = var.sqs_access_key
secret_key = var.sqs_secret_key
sqs_endpoint = "http://shared-sqs.shared-sqs.svc.cluster.local:4100"
}
# ── Outputs ──────────────────────────────────────────────────────────
output "iot_device_phase" {
value = fission_iot_device.weather_station.phase
description = "Статус IoT-устройства weather-station."
}
output "iot_mqtt_username" {
value = fission_iot_device.weather_station.mqtt_username
description = "MQTT username для weather-station."
}
output "iot_topic_prefix" {
value = fission_iot_device.weather_station.topic_prefix
description = "MQTT topic prefix для weather-station."
}
+18
View File
@@ -0,0 +1,18 @@
-- Миграция: таблица метрик погоды для weather-demo
-- Применять: psql $PG_DSN -f migration.sql
CREATE TABLE IF NOT EXISTS weather_metrics (
id BIGSERIAL PRIMARY KEY,
city TEXT NOT NULL,
country TEXT NOT NULL,
temperature NUMERIC(5,2),
feels_like NUMERIC(5,2),
humidity INTEGER,
pressure INTEGER,
wind_speed NUMERIC(6,2),
description TEXT,
recorded_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_weather_city_time
ON weather_metrics (city, recorded_at DESC);
@@ -0,0 +1,10 @@
# Скопируй в terraform.tfvars и заполни значения.
# НЕ коммитить файл с реальными секретами!
kubeconfig_path = "/home/naeel/.kube/config"
namespace = "fission-weather"
owm_api_key = "YOUR_OPENWEATHERMAP_API_KEY"
sqs_access_key = "SSAK-a9964f2723bc6d347f48d153"
sqs_secret_key = "YOUR_SQS_SECRET_KEY"
pg_dsn = "postgresql://super:PASSWORD@postgresqlk8s-master.dc5db45d-f8b4-4fd0-ad33-ec4dd017f2d5.svc.cluster.local:5432/sqsdb?sslmode=disable"