Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make Temporal Worker as a Rock #246

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
__pycache__
.env
tools/workflow-engine/charms/temporal-worker/config.yml
tools/workflow-engine/charms/temporal-worker/environment.yml
tools/workflow-engine/charms/temporal-worker/dist
*.rock
.terraform
*terraform.tfstate*
*.tfvars*
.vscode
venv
venv
20 changes: 10 additions & 10 deletions oci/mock-rock/_releases.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@
},
"1.0-22.04": {
"candidate": {
"target": "565"
"target": "568"
},
"beta": {
"target": "565"
"target": "568"
},
"edge": {
"target": "565"
"target": "568"
},
"end-of-life": "2025-05-01T00:00:00Z"
},
Expand All @@ -35,31 +35,31 @@
"1.1-22.04": {
"end-of-life": "2025-05-01T00:00:00Z",
"candidate": {
"target": "566"
"target": "569"
},
"beta": {
"target": "566"
"target": "569"
},
"edge": {
"target": "566"
"target": "569"
}
},
"1-22.04": {
"end-of-life": "2025-05-01T00:00:00Z",
"candidate": {
"target": "566"
"target": "569"
},
"beta": {
"target": "566"
"target": "569"
},
"edge": {
"target": "566"
"target": "569"
}
},
"1.2-22.04": {
"end-of-life": "2025-05-01T00:00:00Z",
"beta": {
"target": "567"
"target": "570"
},
"edge": {
"target": "1.2-22.04_beta"
Expand Down
104 changes: 104 additions & 0 deletions tools/workflow-engine/charms/temporal-worker/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Copyright 2023 Canonical Ltd.
# See LICENSE file for licensing details.

# Makefile to help automate tasks

# The name of the python package/project
PY_PACKAGE := resource_sample

# ROCK build parameters
ROCK_NAME := temporal-worker_1.0_amd64.rock
IMAGE_NAME := temporal-worker-rock:latest

# build and dist folders
BUILD := build
DIST := dist

# Paths to venv executables
POETRY := poetry
PY := python3
PYTEST := pytest
BANDIT := bandit
BLACK := black
FLAKE8 := flake8
ISORT := isort
MYPY := mypy
PYLINT := pylint
PYDOCSTYLE := pydocstyle

.PHONY: help
help: ## Print help about available targets
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

.PHONY: run
run: ## Run the application
$(POETRY) run $(PY) -m $(PY_PACKAGE)

.PHONY: install
install:
$(POETRY) install --only main --no-root

# Development tools.
.PHONY: install-dev
install-dev:
$(POETRY) install --with dev --no-root

.PHONY: lint
lint: ## Run linter
$(POETRY) run $(FLAKE8) $(PY_PACKAGE) tests
$(POETRY) run $(ISORT) --check $(PY_PACKAGE) tests
$(POETRY) run $(BLACK) --check $(PY_PACKAGE) tests
$(POETRY) run $(BANDIT) --configfile pyproject.toml --quiet --recursive $(PY_PACKAGE) tests

.PHONY: fmt
fmt: ## Reformat code for linter
$(POETRY) run $(ISORT) $(PY_PACKAGE) tests
$(POETRY) run $(BLACK) $(PY_PACKAGE) tests

.PHONY: test
test: ## Run tests
$(POETRY) run $(PYTEST) --cov=$(PY_PACKAGE) tests

.PHONY: check
check: clean install-dev lint test ## Runs linter and tests from a clean directory


# Release management.

.PHONY: changelog
changelog: ## Add a new entry to the Changelog and bump the package version
./scripts/update_changelog.sh

.PHONY: build
build: ## Create a Python source distribution and a wheel in dist
$(POETRY) build

.PHONY: build_rock
build_rock:
rockcraft pack
rockcraft.skopeo --insecure-policy copy --dest-tls-verify=false oci-archive:$(ROCK_NAME) docker://localhost:32000/$(IMAGE_NAME)

.PHONY: publish
publish: ## Publish the package to PYPI
$(POETRY) publish

# Cleaning up.

.PHONY: clean-dist
clean-dist:
rm -rf $(BUILD) $(DIST)

.PHONY: clean-pyc
clean-pyc:
find . -name '*.pyc' -exec rm -f {} +
find . -name '*.pyo' -exec rm -f {} +
find . -name '*~' -exec rm -f {} +
rm -rf .pytest_cache .coverage .mypy_cache

.PHONY: clean-venv
clean-venv:
$(POETRY) env remove --all

.PHONY: clean
clean: clean-dist clean-pyc clean-venv ## Clean up the virtualenv, Python bytecode and docs
rm -rf *.egg-info
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,3 @@ temporal-worker-k8s:
mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
-----END CERTIFICATE-----
# Only needed if deploying under a proxy
# http-proxy: "http://squid.internal:3128"
# https-proxy: "http://squid.internal:3128"
# no-proxy: ".canonical.com"

# Charmed workflows
# The "workflows-file-name" must match the wheel file created with poetry
workflows-file-name: "oci_factory_workflows-0.0.1-py3-none-any.whl"
# To support all defined workflows and activities, use the 'all' keyword
supported-workflows: "all"
supported-activities: "all"
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
env:
- name: ROCKS_EVENTBUS_USERNAME
value:
- name: ROCKS_EVENTBUS_PASSWORD
value:
- name: ROCKS_EVENTBUS_KAFKA_ADDRESS
value:
- name: ROCKS_EVENTBUS_KARAPACE_URL
value:
- name: OS_AUTH_URL
value:
- name: OS_USERNAME
value:
- name: OS_PASSWORD
value:
- name: OS_PROJECT_NAME
value:
- name: OS_STORAGE_URL
value:
- name: GITHUB_TOKEN
value:
- name: MATTERMOST_TOKEN
value:
- name: MATTERMOST_SERVER
value: https://chat.canonical.com
- name: MATTERMOST_CHANNEL_ID
value:

This file was deleted.

125 changes: 125 additions & 0 deletions tools/workflow-engine/charms/temporal-worker/oci_factory/worker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
#!/usr/bin/env python3
# Copyright 2023 Canonical Ltd.
# See LICENSE file for licensing details.


"""Temporal client worker."""

import asyncio
import logging
import os

from activities.activity_consume_events import consume
from temporalio.runtime import PrometheusConfig, Runtime, TelemetryConfig
from temporallib.auth import (
AuthOptions,
GoogleAuthOptions,
KeyPair,
MacaroonAuthOptions,
)
from temporallib.client import Client, Options
from temporallib.encryption import EncryptionOptions
from temporallib.worker import SentryOptions, Worker, WorkerOptions
from workflows.consume_events_workflow import ConsumeEventsWorkflow

logger = logging.getLogger(__name__)


def _get_auth_header():
"""Get auth options based on provider.

Returns:
AuthOptions object.
"""
if os.getenv("TWC_AUTH_PROVIDER") == "candid":
return MacaroonAuthOptions(
keys=KeyPair(
private=os.getenv("TWC_CANDID_PRIVATE_KEY"),
public=os.getenv("TWC_CANDID_PUBLIC_KEY"),
),
macaroon_url=os.getenv("TWC_CANDID_URL"),
username=os.getenv("TWC_CANDID_USERNAME"),
)

if os.getenv("TWC_AUTH_PROVIDER") == "google":
return GoogleAuthOptions(
type="service_account",
project_id=os.getenv("TWC_OIDC_PROJECT_ID"),
private_key_id=os.getenv("TWC_OIDC_PRIVATE_KEY_ID"),
private_key=os.getenv("TWC_OIDC_PRIVATE_KEY"),
client_email=os.getenv("TWC_OIDC_CLIENT_EMAIL"),
client_id=os.getenv("TWC_OIDC_CLIENT_ID"),
auth_uri=os.getenv("TWC_OIDC_AUTH_URI"),
token_uri=os.getenv("TWC_OIDC_TOKEN_URI"),
auth_provider_x509_cert_url=os.getenv("TWC_OIDC_AUTH_CERT_URL"),
client_x509_cert_url=os.getenv("TWC_OIDC_CLIENT_CERT_URL"),
)

return None


def _init_runtime_with_prometheus(port: int) -> Runtime:
"""Create runtime for use with Prometheus metrics.

Args:
port: Port of prometheus.

Returns:
Runtime for temporalio with prometheus.
"""
return Runtime(
telemetry=TelemetryConfig(
metrics=PrometheusConfig(bind_address=f"0.0.0.0:{port}")
)
)


async def run_worker():
"""Connect Temporal worker to Temporal server."""
client_config = Options(
host=os.getenv("TWC_HOST"),
namespace=os.getenv("TWC_NAMESPACE"),
queue=os.getenv("TWC_QUEUE"),
)

if os.getenv("TWC_TLS_ROOT_CAS", "").strip() != "":
client_config.tls_root_cas = os.getenv("TWC_TLS_ROOT_CAS")

if os.getenv("TWC_AUTH_PROVIDER", "").strip() != "":
client_config.auth = AuthOptions(
provider=os.getenv("TWC_AUTH_PROVIDER"), config=_get_auth_header()
)

if os.getenv("TWC_ENCRYPTION_KEY", "").strip() != "":
client_config.encryption = EncryptionOptions(
key=os.getenv("TWC_ENCRYPTION_KEY"), compress=True
)

worker_opt = None
dsn = os.getenv("TWC_SENTRY_DSN", "").strip()
if dsn != "":
sentry = SentryOptions(
dsn=dsn,
release=os.getenv("TWC_SENTRY_RELEASE", "").strip() or None,
environment=os.getenv("TWC_SENTRY_ENVIRONMENT", "").strip() or None,
redact_params=os.getenv("TWC_SENTRY_REDACT_PARAMS", False),
sample_rate=os.getenv("TWC_SENTRY_SAMPLE_RATE", 1.0),
)

worker_opt = WorkerOptions(sentry=sentry)

client = await Client.connect(client_config)

worker = Worker(
client=client,
task_queue=os.getenv("TWC_QUEUE"),
workflows=[ConsumeEventsWorkflow],
activities=[consume],
worker_opt=worker_opt,
)

await worker.run()


if __name__ == "__main__": # pragma: nocover
asyncio.run(run_worker())
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@ description = "Temporal workflows for supporting the OCI Factory processes"
authors = ["cjdc <[email protected]>"]
packages = [
{ include = "**/*.py", from = "." },
{ include = "**/ca.crt", from = "." }
]
include = ["**/*.avsc"]
include = ["**/*.avsc", "**/*.crt"]

[tool.poetry.dependencies]
python = "^3.8"
Expand Down
Loading