Skip to content

Commit

Permalink
Merge pull request #214 from camptocamp/fix-prod-int
Browse files Browse the repository at this point in the history
Fix multiple environment
  • Loading branch information
sbrunner authored Dec 23, 2022
2 parents 4006550 + ea1f829 commit 6bc5138
Show file tree
Hide file tree
Showing 6 changed files with 51 additions and 69 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ jobs:

- name: Install helm
uses: azure/setup-helm@v3
with:
version: 3.10.0
- run: helm lint .

- name: Setup k3s/k3d
Expand Down
2 changes: 1 addition & 1 deletion docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,4 @@ COPY * ./
FROM runtime as default

COPY shared_config_manager_operator.py ./
CMD ["kopf", "run", "shared_config_manager_operator.py"]
ENTRYPOINT [ "kopf", "run", "shared_config_manager_operator.py"]
80 changes: 23 additions & 57 deletions docker/shared_config_manager_operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,55 +6,39 @@
from typing import Any, Dict

import kopf
import kopf._cogs.structs.bodies
import kopf._core.actions.execution
import kubernetes # type: ignore
import yaml

LOCK: asyncio.Lock

ENVIRONMENT: str = os.environ["ENVIRONMENT"]

sharedconfigconfigs: Dict[str, kopf._cogs.structs.bodies.Body] = {}
sharedconfigsources: Dict[str, kopf._cogs.structs.bodies.Body] = {}
sharedconfigconfigs: Dict[str, kopf.Body] = {}
sharedconfigsources: Dict[str, kopf.Body] = {}


@kopf.on.startup()
async def startup(settings: kopf.OperatorSettings, **_) -> None:
async def startup(settings: kopf.OperatorSettings, logger: kopf.Logger, **_) -> None:
settings.posting.level = logging.getLevelName(os.environ.get("LOG_LEVEL", "INFO"))
if "KOPF_SERVER_TIMEOUT" in os.environ:
settings.watching.server_timeout = int(os.environ["KOPF_SERVER_TIMEOUT"])
if "KOPF_CLIENT_TIMEOUT" in os.environ:
settings.watching.client_timeout = int(os.environ["KOPF_CLIENT_TIMEOUT"])
global LOCK # pylint: disable=global-statement
LOCK = asyncio.Lock()
logger.info("Startup in environment %s", ENVIRONMENT)


@kopf.on.resume("camptocamp.com", "v2", "sharedconfigconfigs")
@kopf.on.create("camptocamp.com", "v2", "sharedconfigconfigs")
@kopf.on.update("camptocamp.com", "v2", "sharedconfigconfigs")
async def config_kopf(
body: kopf._cogs.structs.bodies.Body,
meta: kopf._cogs.structs.bodies.Meta,
spec: kopf._cogs.structs.bodies.Spec,
logger: kopf._cogs.helpers.typedefs.Logger,
**_,
) -> None:
if spec["environment"] != ENVIRONMENT:
return
@kopf.on.resume("camptocamp.com", "v2", "sharedconfigconfigs", field="spec.environment", value=ENVIRONMENT)
@kopf.on.create("camptocamp.com", "v2", "sharedconfigconfigs", field="spec.environment", value=ENVIRONMENT)
@kopf.on.update("camptocamp.com", "v2", "sharedconfigconfigs", field="spec.environment", value=ENVIRONMENT)
async def config_kopf(body: kopf.Body, meta: kopf.Meta, logger: kopf.Logger, **_) -> None:
sharedconfigconfigs[meta["name"]] = body
await update_config(body, logger)


@kopf.on.delete("camptocamp.com", "v2", "sharedconfigconfigs")
async def delete_config(
meta: kopf._cogs.structs.bodies.Meta,
spec: kopf._cogs.structs.bodies.Spec,
logger: kopf._cogs.helpers.typedefs.Logger,
**_,
) -> None:
if spec["environment"] != ENVIRONMENT:
return
@kopf.on.delete("camptocamp.com", "v2", "sharedconfigconfigs", field="spec.environment", value=ENVIRONMENT)
async def delete_config(meta: kopf.Meta, spec: kopf.Spec, logger: kopf.Logger, **_) -> None:
if meta["name"] in sharedconfigconfigs:
del sharedconfigconfigs[meta["name"]]
logger.info(
Expand All @@ -65,38 +49,24 @@ async def delete_config(
)


@kopf.on.resume("camptocamp.com", "v2", "sharedconfigsources")
@kopf.on.create("camptocamp.com", "v2", "sharedconfigsources")
@kopf.on.update("camptocamp.com", "v2", "sharedconfigsources")
async def source_kopf(
body: kopf._cogs.structs.bodies.Body,
meta: kopf._cogs.structs.bodies.Meta,
spec: kopf._cogs.structs.bodies.Spec,
logger: kopf._cogs.helpers.typedefs.Logger,
**_,
) -> None:
if spec["environment"] != ENVIRONMENT:
return
@kopf.on.resume("camptocamp.com", "v2", "sharedconfigsources", field="spec.environment", value=ENVIRONMENT)
@kopf.on.create("camptocamp.com", "v2", "sharedconfigsources", field="spec.environment", value=ENVIRONMENT)
@kopf.on.update("camptocamp.com", "v2", "sharedconfigsources", field="spec.environment", value=ENVIRONMENT)
async def source_kopf(body: kopf.Body, meta: kopf.Meta, logger: kopf.Logger, **_) -> None:
sharedconfigsources[meta["name"]] = body
await update_source(body, logger)


@kopf.on.delete("camptocamp.com", "v2", "sharedconfigsources")
async def delete_source(
body: kopf._cogs.structs.bodies.Body,
meta: kopf._cogs.structs.bodies.Meta,
spec: kopf._cogs.structs.bodies.Spec,
logger: kopf._cogs.helpers.typedefs.Logger,
**_,
) -> None:
if spec["environment"] != ENVIRONMENT:
return
@kopf.on.delete("camptocamp.com", "v2", "sharedconfigsources", field="spec.environment", value=ENVIRONMENT)
async def delete_source(body: kopf.Body, meta: kopf.Meta, logger: kopf.Logger, **_) -> None:
if meta["name"] in sharedconfigsources:
del sharedconfigsources[meta["name"]]
else:
kopf.info(body, reason="NotFound", message="Source not found")
await update_source(body, logger)


def match(source: kopf._cogs.structs.bodies.Body, config: kopf._cogs.structs.bodies.Body) -> bool:
def match(source: kopf.Body, config: kopf.Body) -> bool:
"""
Check if the source labels matches the config matchLables.
"""
Expand All @@ -108,31 +78,27 @@ def match(source: kopf._cogs.structs.bodies.Body, config: kopf._cogs.structs.bod
return True


async def update_source(
source: kopf._cogs.structs.bodies.Body, logger: kopf._cogs.helpers.typedefs.Logger
) -> None:
async def update_source(source: kopf.Body, logger: kopf.Logger) -> None:
for config in sharedconfigconfigs.values():
if match(source, config):
await update_config(config, logger)


async def update_config(
config: kopf._cogs.structs.bodies.Body, logger: kopf._cogs.helpers.typedefs.Logger
) -> None:
async def update_config(config: kopf.Body, logger: kopf.Logger) -> None:
global LOCK # pylint: disable=global-variable-not-assigned
async with LOCK:
configmap_content: Dict[str, Any] = {config.spec["property"]: {}}
for source in sharedconfigsources.values():
if match(source, config):
kopf.event(
source,
type="SharedConfigManager",
type="SharedConfigOperator",
reason="Used",
message="Used by SharedConfigConfig " f"{config.meta.namespace}:{config.meta.name}",
)
kopf.event(
config,
type="SharedConfigManager",
type="SharedConfigOperator",
reason="Use",
message=f"Use SharedConfigSource {source.meta.namespace}:{source.meta.name}",
)
Expand Down
4 changes: 4 additions & 0 deletions templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ spec:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.Version }}"
imagePullPolicy: {{ .Values.global.image.pullPolicy }}
{{- with .Values.args }}
args:
{{- toYaml . | nindent 12 }}
{{- end }}
env:
{{- range $name, $value := .Values.env }}
- name: {{ $name | quote }}
Expand Down
29 changes: 18 additions & 11 deletions tests/test_operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,14 @@ def install_operator(scope="session"):
"test",
"--namespace=default",
"--set=image.tag=latest",
'--set-json=args=["--debug"]',
"--set=env.ENVIRONMENT=test",
".",
],
stdout=operator_file,
check=True,
)
subprocess.run(["kubectl", "apply", "-f", "operator.yaml"], check=True)
subprocess.run(["kubectl", "apply", "--filename=operator.yaml"], check=True)
subprocess.run(["kubectl", "create", "namespace", "source"], check=True)
subprocess.run(["kubectl", "create", "namespace", "config"], check=True)

Expand All @@ -47,7 +48,7 @@ def install_operator(scope="session"):
yield
subprocess.run(["kubectl", "config", "set-context", "--current", "--namespace=default"], check=True)
# We should have the pod to be able to extract the logs
# subprocess.run(["kubectl", "delete", "-f", "operator.yaml"], check=True)
# subprocess.run(["kubectl", "delete", "--filename=operator.yaml"], check=True)
os.remove("operator.yaml")


Expand All @@ -56,18 +57,20 @@ def test_operator(install_operator):

# Initialize the source and the config
subprocess.run(["kubectl", "config", "set-context", "--current", "--namespace=source"], check=True)
subprocess.run(["kubectl", "apply", "-f", "tests/source.yaml"], check=True)
subprocess.run(["kubectl", "apply", "-f", "tests/source_other.yaml"], check=True)
subprocess.run(["kubectl", "apply", "--filename=tests/source.yaml"], check=True)
subprocess.run(["kubectl", "apply", "--filename=tests/source_other.yaml"], check=True)
subprocess.run(["kubectl", "config", "set-context", "--current", "--namespace=config"], check=True)
subprocess.run(["kubectl", "apply", "-f", "tests/config.yaml"], check=True)
subprocess.run(["kubectl", "apply", "--filename=tests/config.yaml"], check=True)

# Wait that the ConfigMap is correctly created
cm = None
for _ in range(10):
try:
cm = json.loads(
subprocess.run(
["kubectl", "get", "cm", "test2", "--output=json"], check=True, stdout=subprocess.PIPE
["kubectl", "get", "configmap", "test2", "--output=json"],
check=True,
stdout=subprocess.PIPE,
).stdout
)
break
Expand Down Expand Up @@ -95,7 +98,7 @@ def test_operator(install_operator):

# Remove the source
subprocess.run(["kubectl", "config", "set-context", "--current", "--namespace=source"], check=True)
subprocess.run(["kubectl", "delete", "-f", "tests/source.yaml"], check=True)
subprocess.run(["kubectl", "delete", "--filename=tests/source.yaml"], check=True)

# Wait that the ConfigMap is correctly updated
subprocess.run(["kubectl", "config", "set-context", "--current", "--namespace=config"], check=True)
Expand All @@ -105,7 +108,9 @@ def test_operator(install_operator):
try:
cm = json.loads(
subprocess.run(
["kubectl", "get", "cm", "test2", "--output=json"], check=True, stdout=subprocess.PIPE
["kubectl", "get", "configmap", "test2", "--output=json"],
check=True,
stdout=subprocess.PIPE,
).stdout
)
except:
Expand All @@ -120,14 +125,16 @@ def test_operator(install_operator):
assert success, data

# Remove the config
subprocess.run(["kubectl", "delete", "-f", "tests/config.yaml"], check=True)
subprocess.run(["kubectl", "delete", "--filename=tests/config.yaml"], check=True)
# Wait that the ConfigMap is correctly deleted
success = False
for _ in range(10):
try:
cm = json.loads(
subprocess.run(
["kubectl", "get", "cm", "test2", "--output=json"], check=True, stdout=subprocess.PIPE
["kubectl", "get", "configmap", "test2", "--output=json"],
check=True,
stdout=subprocess.PIPE,
).stdout
)
time.sleep(1)
Expand All @@ -138,4 +145,4 @@ def test_operator(install_operator):

# Remove the other source, to be cleaned
subprocess.run(["kubectl", "config", "set-context", "--current", "--namespace=source"], check=True)
subprocess.run(["kubectl", "delete", "-f", "tests/source_other.yaml"], check=True)
subprocess.run(["kubectl", "delete", "--filename=tests/source_other.yaml"], check=True)
3 changes: 3 additions & 0 deletions values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ clusterrole: true
clusterrolebinding: true
deployment: true

args: []
env: {}

serviceAccount:
# Annotations to add to the service account
annotations: {}
Expand Down

0 comments on commit 6bc5138

Please sign in to comment.