Skip to content

Commit

Permalink
Merge pull request #18 from ocadotechnology/code-review
Browse files Browse the repository at this point in the history
Make docker certificate secret name required.
  • Loading branch information
blerko authored Jun 5, 2018
2 parents 57bde5c + 861445a commit 1f52e8b
Show file tree
Hide file tree
Showing 6 changed files with 388 additions and 121 deletions.
58 changes: 58 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@ The following environment variables can be set:

Name | description | default
--- | --- | ---
DOCKER_CERTIFICATE_SECRET | You **must** provide a certificate to enable TLS between the docker daemon and the registry and create a secret from it, this variable is the name of the secret | None
NAMESPACE | The namespace in which the resources should be created. This should be the same namespace as where the container is running | default
SECONDS_BETWEEN_STREAMS | Time to sleep between calls to the API. The operator will occasionally lose connection or else fail to run if the Custom Resource Definition does not exist. | 30
HOSTESS_DOCKER_REGISTRY | The docker registry where mirror-hostess is to be pulled from. | docker.io
HOSTESS_DOCKER_IMAGE | The name of the docker image for mirror-hostess. | ocadotechnology/mirror-hostess
HOSTESS_DOCKER_TAG | The tag for the mirror-hostess docker image. | 1.1.0
IMAGE_PULL_SECRETS | (Optional) Secret to pull images from the upstream registry | None
CA_CERTIFICATE_BUNDLE | (Optional) Certificate bundle for the registry host | None

## Usage
In order to have the operator deploy a new mirror, the cluster needs to have the custom resource defined:
Expand Down
14 changes: 8 additions & 6 deletions mirroroperator/operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@ def __init__(self, env_vars):
hostess_docker_image (used in RegistryMirror),
hostess_docker_tag (used in RegistryMirror),
image_pull_secrets(used in RegistryMirror, optional),
secret_name(optional),
cert_name(optional)
docker_certificate_secret(used in RegistryMirror),
ca_certificate_bundle(optional)
"""
if not env_vars.get("docker_certificate_secret"):
raise TypeError("Missing docker certificate secret")
self.registry_mirror_vars = env_vars
kubernetes.config.load_incluster_config()
self.crd_api = kubernetes.client.ExtensionsV1beta1Api()
Expand Down Expand Up @@ -62,10 +64,10 @@ def watch_registry_mirrors(self):
hostess_docker_tag=os.environ.get("HOSTESS_DOCKER_TAG", "1.1.0"),
# optional in V1PodSpec secrets split with comma
image_pull_secrets=os.environ.get("IMAGE_PULL_SECRETS"),
# get secret name:
secret_name=os.environ.get("SECRET_NAME"),
# cert_name - needed in clusters
cert_name=os.environ.get("CERT_NAME"),
# get the docker certificate:
docker_certificate_secret=os.environ.get("DOCKER_CERTIFICATE_SECRET"),
# get ca certificate
ca_certificate_bundle=os.environ.get("CA_CERTIFICATE_BUNDLE"),
)
operator = MirrorOperator(env_vars)

Expand Down
136 changes: 80 additions & 56 deletions mirroroperator/registrymirror.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,25 @@

class RegistryMirror(object):
def __init__(self, event_type, namespace, hostess_docker_registry,
hostess_docker_image, hostess_docker_tag, **kwargs):
hostess_docker_image, hostess_docker_tag,
docker_certificate_secret, **kwargs):
self.event_type = event_type
self.namespace = namespace
self.hostess_docker_registry = hostess_docker_registry
self.hostess_docker_image = hostess_docker_image
self.hostess_docker_tag = hostess_docker_tag
self.docker_certificate_secret = docker_certificate_secret
self.kind = kwargs.get("kind")
self.name = kwargs.get("metadata", {}).get("name")
self.uid = kwargs.get("metadata", {}).get("uid")
self.full_name = "registry-mirror-{}".format(self.name)
self.daemon_set_name = self.full_name + "-utils"
self.apiVersion = kwargs.get("apiVersion")
self.upstreamUrl = kwargs.get("spec", {}).get("upstreamUrl")
self.credentials_secret_name = kwargs.get("spec", {}).get("credentialsSecret")
self.credentials_secret_name = kwargs.get(
"spec", {}).get("credentialsSecret")
self.image_pull_secrets = kwargs["image_pull_secrets"] or ""
self.secret_name = kwargs["secret_name"]
self.cert_name = kwargs["cert_name"]
self.ca_certificate_bundle = kwargs["ca_certificate_bundle"]

self.labels = {
"app": "docker-registry",
Expand Down Expand Up @@ -83,8 +85,8 @@ def apply(self):
self.update_daemon_set(daemon_set)

def run_action_and_parse_error(self, func, *args, **kwargs):
"""
Helper method to avoid try/excepts all over the place + does the exception handling and parsing
""" Helper method to avoid try/excepts all over the place + does
the exception handling and parsing.
(Kubernetes python client tends to just dump the details in .body)
Args:
func: A function which can raise ApiException.
Expand All @@ -101,7 +103,9 @@ def run_action_and_parse_error(self, func, *args, **kwargs):
try:
json_error = json.loads(api_exception.body)
code = HTTPStatus(int(json_error['code']))
LOGGER.exception("API returned status: %s, msg: %s, method: %s", code, json_error['message'], func)
LOGGER.exception(
"API returned status: %s, msg: %s, method: %s",
code, json_error['message'], func)

except json.decoder.JSONDecodeError as e:
LOGGER.error("Decoder exception loading error msg: %s;"
Expand All @@ -124,23 +128,34 @@ def generate_daemon_set(self, daemon_set):
client.V1Container(
name="mirror-hostess",
env=[
client.V1EnvVar(name="LOCK_FILE",
value="/var/lock/hostess/mirror-hostess"),
client.V1EnvVar(name="SERVICE_NAME",
value=self.full_name),
client.V1EnvVar(name="SERVICE_NAMESPACE",
value=self.namespace),
client.V1EnvVar(name="SHADOW_FQDN",
value="mirror-"+self.upstreamUrl),
client.V1EnvVar(name="HOSTS_FILE",
value="/etc/hosts_from_host"),
client.V1EnvVar(name="HOSTS_FILE_BACKUP",
value="/etc/hosts.backup/hosts")
client.V1EnvVar(
name="LOCK_FILE",
value="/var/lock/hostess/mirror-hostess"),
client.V1EnvVar(
name="SERVICE_NAME",
value=self.full_name),
client.V1EnvVar(
name="SERVICE_NAMESPACE",
value=self.namespace),
client.V1EnvVar(
name="SHADOW_FQDN",
value="mirror-"+self.upstreamUrl),
client.V1EnvVar(
name="HOSTS_FILE",
value="/etc/hosts_from_host"),
client.V1EnvVar(
name="HOSTS_FILE_BACKUP",
value="/etc/hosts.backup/hosts")
],
image="{}/{}:{}".format(self.hostess_docker_registry, self.hostess_docker_image, self.hostess_docker_tag),
image="{}/{}:{}".format(
self.hostess_docker_registry,
self.hostess_docker_image,
self.hostess_docker_tag),
image_pull_policy="Always",
resources=client.V1ResourceRequirements(
requests={"memory": "32Mi", "cpu": "0.001"},
requests={
"memory": "32Mi", "cpu": "0.001"
},
limits={"memory": "128Mi", "cpu": "0.1"},
),
volume_mounts=[
Expand All @@ -160,7 +175,9 @@ def generate_daemon_set(self, daemon_set):
),
client.V1Container(
name="certificate-installation",
args=["cp /source/tls.crt /target/tls.crt; while :; do sleep 2073600; done"],
args=[
"cp /source/tls.crt /target/tls.crt; while :; do sleep 2073600; done"
],
command=[
"/bin/sh",
"-c",
Expand Down Expand Up @@ -214,7 +231,7 @@ def generate_daemon_set(self, daemon_set):
client.V1Volume(
name="tls",
secret=client.V1SecretVolumeSource(
secret_name=self.secret_name
secret_name=self.docker_certificate_secret
)
)
]
Expand Down Expand Up @@ -313,7 +330,9 @@ def handle_secrets(self, keypair):
return keypair

def generate_stateful_set(self, stateful_set):
keypair = client.V1EnvVar(name="REGISTRY_PROXY_REMOTEURL", value="https://" + self.upstreamUrl)
keypair = client.V1EnvVar(
name="REGISTRY_PROXY_REMOTEURL",
value="https://" + self.upstreamUrl)
if self.credentials_secret_name:
keypair = self.handle_secrets(keypair)

Expand All @@ -336,22 +355,41 @@ def generate_stateful_set(self, stateful_set):
pod_labels = {'component': 'registry'}
pod_labels.update(self.labels)
volumes = []
if self.cert_name:
if self.ca_certificate_bundle:
volumes = [
client.V1Volume(
name=self.cert_name,
name=self.ca_certificate_bundle,
config_map=client.V1ConfigMapVolumeSource(
name=self.cert_name
name=self.ca_certificate_bundle
)
)
]
if self.secret_name:
volumes.append(
client.V1Volume(
name="tls",
secret=client.V1SecretVolumeSource(
secret_name=self.secret_name
),
volumes.append(
client.V1Volume(
name="tls",
secret=client.V1SecretVolumeSource(
secret_name=self.docker_certificate_secret
),
)
)

volumes_to_mount = [
client.V1VolumeMount(
name="image-store",
mount_path="/var/lib/registry"
),
client.V1VolumeMount(
name="tls",
mount_path="/etc/registry-certs",
read_only=True
)
]
if self.ca_certificate_bundle:
volumes_to_mount.append(
client.V1VolumeMount(
name=self.ca_certificate_bundle,
mount_path="/etc/ssl/certs",
read_only=True
)
)
stateful_set.spec.template = client.V1PodTemplateSpec(
Expand Down Expand Up @@ -411,31 +449,14 @@ def generate_stateful_set(self, stateful_set):
limits={"cpu": "0.5",
"memory": "500Mi"}
),
volume_mounts=[
client.V1VolumeMount(
name="image-store",
mount_path="/var/lib/registry"
),
client.V1VolumeMount(
name=self.cert_name,
mount_path="/etc/ssl/certs",
read_only=True
),
client.V1VolumeMount(
name="tls",
mount_path="/etc/registry-certs",
read_only=True
)
],
volume_mounts=volumes_to_mount,
)
],
termination_grace_period_seconds=10,
volumes=volumes or None,
volumes=volumes,
)
)
stateful_set.spec.update_strategy = client.V1beta1StatefulSetUpdateStrategy(
type="RollingUpdate",
)
stateful_set.spec.update_strategy = client.V1beta1StatefulSetUpdateStrategy(type="RollingUpdate",)
return stateful_set

def update_services(self, service, service_headless):
Expand Down Expand Up @@ -498,8 +519,11 @@ def update_stateful_set(self, stateful_set):
)
if not stateful_set:
stateful_set = self.generate_stateful_set(empty_stateful_set)
self.run_action_and_parse_error(self.apps_api.create_namespaced_stateful_set,
self.namespace, stateful_set)
self.run_action_and_parse_error(
self.apps_api.create_namespaced_stateful_set,
self.namespace,
stateful_set
)
LOGGER.info("Stateful set created")
else:
stateful_set = self.generate_stateful_set(stateful_set)
Expand Down
4 changes: 2 additions & 2 deletions tests/test_operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ def setUp(self):
"hostess_docker_image": "ocadotechnology/mirror-hostess",
"hostess_docker_tag": None,
"image_pull_secrets": None,
"secret_name": None,
"cert_name": None,
"docker_certificate_secret": VALID_SECRET,
"ca_certificate_bundle": None,
}
self.operator = MirrorOperator(env_var_dict)

Expand Down
Loading

0 comments on commit 1f52e8b

Please sign in to comment.