feat(provider): add fission_mq_trigger, fission_cron_trigger, fission_iot_device + weather-demo example
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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."
|
||||
}
|
||||
@@ -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"
|
||||
Reference in New Issue
Block a user