-
Notifications
You must be signed in to change notification settings - Fork 25
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
Review apps #62
Review apps #62
Conversation
hokusai/cli/review_app.py
Outdated
with open(original_yaml_file, 'r') as stream: | ||
try: | ||
yaml_content = list(yaml.load_all(stream)) | ||
# @TODO: replace namespace: default with new namespace name |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would not make any assumptions that the user configures their app in the "default"
namespace - it should support any given namespace. So will need to traverse keys and replace any namespace
keys with the new one
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe need to dig into the Kubernetes object specs a bit more here but AFAIK all objects support the key metadata.namespace
so it might just be enough to iterate through all top-level objects defined in the source yml and simply overwrite this key
In general I think |
hokusai/cli/review_app.py
Outdated
new_namespace = {"apiVersion": "v1", "kind": "Namespace", "meta": { "name": str(app_name) }} | ||
yaml_content = [new_namespace] + yaml_content | ||
with open("hokusai/%s.yml" % app_name, 'w') as output: | ||
yaml.dump_all(yaml_content, output) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if this will reorder keys in the yaml serializations of the k8s objects (as dict keys are un-sorted) but this is why we add a OrderedDict Yaml representer https://github.com/artsy/hokusai/blob/master/hokusai/lib/representers.py
hokusai/cli/review_app.py
Outdated
def create(app_name, verbose): | ||
set_verbosity(verbose) | ||
# copy staging yml file | ||
create_new_app_yaml('hokusai/staging.yml', app_name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Ceiling cat] Seems like if the review app is by definition based on the staging environment, then maybe the proposed cli syntax could drop that detail? So just hokusai staging review_app create?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good point, that actually makes it easier for later if we wanted to do things like hokusai review_app deploy <app name>
.
Made some changes in last commit:
Question 1: Should we actually create the deployment on k8s when we do Question 2: Should we (could we) copy |
We can have this command actually create the k8s resources if:
Given that we will want to spot-check the new resource config before applying I would tie this to state dependent on a local file / directory structure. I.e.
I think, given the process above - if the steps taken are:
then we can copy the configMap over from the source at apply / launch time... I would make it a point that we think of review apps as having a lifecycle. |
@joeyAghion I don't think passing the |
having trouble with having app with same name on different namespace, getting this error: > hokusai review_app apply test_app
Error from server (AlreadyExists): error when creating "/Users/ashkinas/src/volt/hokusai/test_app.yml": deployments.extensions "volt-web" already exists
Error from server (AlreadyExists): error when creating "/Users/ashkinas/src/volt/hokusai/test_app.yml": services "volt-web" already exists
ERROR: Command 'kubectl --context staging create --save-config -f /Users/ashkinas/src/volt/hokusai/test_app.yml' returned non-zero exit status 1 |
hokusai/cli/review_app.py
Outdated
print(exc) | ||
|
||
def update_namespace(yaml_section, destination_namespace): | ||
if 'namespace' in yaml_section: yaml_section['namespace'] = destination_namespace |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would just overwrite namespace here - i.e. drop the if
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but that way this will set namespace
in each yaml section, we want to only update if that yaml portion has namespace
, i kind of use this method recursively.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reading more here about what happens when a given namespace is defined in the kubectl
context verses a given yaml spec. This might be working for us in tests because we are using the default
namespace in the context
definition in our ~/.kube/config
file and operations against yaml
resource specs without defining the namespace work as intended. In any case it seems this is the behavior implemented:
If no namespace is specified in the schema, then I'd expect the one from kubeconfig to be used.
If a namespace is explicitly specified on the command line and that conflicts with the one in the schema, kubectl should raise an error.
This is why I am in favor of putting namespace
in the schema as it takes precedence over implicit schemas provided by context
hokusai/cli/review_app.py
Outdated
def apply(app_name, verbose): | ||
"""Create the Kubernetes resources defined in ./hokusai/%s.yml""" % app_name | ||
set_verbosity(verbose) | ||
hokusai.k8s_create(KUBE_CONTEXT, app_name, app_name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would use hokusai.k8s_update
here (also update the function k8s_update to accept yaml_file_name
as a keyword argument)- this will then use kubectl apply
so you can apply updates to the spec
hokusai/cli/review_app.py
Outdated
set_verbosity(verbose) | ||
# copy staging yml file | ||
if destination_namespace is None: destination_namespace = app_name | ||
create_new_app_yaml(source_file, app_name, destination_namespace) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps pause here (ask for confirmation?) and then call hokusai.k8s_create(KUBE_CONTEXT, app_name, app_name)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh so you are thinking of this immediately call create
? i was thinking of this just creating the yaml file and once everything is verified whoever creating this app needs to do apply
separately.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That works as well - but whereas kubectl create
will fail (as you saw) if given resources already exist - kubectl apply
will update existing resources - so the former is safer when you want to create
hokusai/cli/review_app.py
Outdated
with open("hokusai/%s.yml" % app_name, 'w') as output: | ||
yaml.safe_dump_all(yaml_content, output, default_flow_style=False) | ||
except yaml.YAMLError as exc: | ||
print(exc) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hokusai/cli/review_app.py
Outdated
yaml.safe_dump_all(yaml_content, output, default_flow_style=False) | ||
except yaml.YAMLError as exc: | ||
print(exc) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To dump the env configmap out you can use the ConfigMap
class and instead of calling save
on it - update it's struct
attribute serving as a cache, then follow what is defined in the save method to write out a new NamedTemporaryFile
then create it in the new namespace.
import os
from hokusai.services.kubectl import Kubectl
from hokusai.services.configmap import ConfigMap
kctl = Kubectl(context)
configmap = ConfigMap(context)
configmap.load()
configmap.struct['metadata']['namespace'] = review_app_namespace
f = configmap._to_file()
try:
shout(kctl.command("apply -f %s" % f))
finally:
os.unlink(f)
hokusai/cli/review_app.py
Outdated
if 'namespace' in yaml_section: yaml_section['namespace'] = destination_namespace | ||
for k, v in yaml_section.iteritems(): | ||
if isinstance(v, dict): | ||
update_namespace(v, destination_namespace) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I see what you mean - it doesn't have to be recursive - only operate at the top-level Kubernetes API objects. All top-level k8s API objects should have apiVersion
and kind
keys. metadata
may be present so for each apiObject
if it is ('metadata' in apiObject
) add metadata['namepsace'] = destination_namespace
... if not add `metadata = { 'namespace': destination_namespace }
For example ending up with something like this:
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
namespace: fooNamespace
Alright made some changes here in command structure, basically to follow commands for staging and production now review apps have:
|
alright, latest command format: hokusai review_app setup <name> # creates yaml file
hokusai review_app create <name> # creates namespace/service/deployment, at this stage it will fail till we copy ConfigMap
hokusai review_app copy_env <name> # copies staging ConfigMap from default namespace to new namespace
hokusai review_app update <name> # updates deployment/service/namespace based on yaml |
hokusai/cli/review_app.py
Outdated
@click.option('-v', '--verbose', type=click.BOOL, is_flag=True, help='Verbose output') | ||
@click.option('-sf', '--source-file', type=click.STRING, default='hokusai/staging.yml', help='Source deployment file') | ||
@click.option('-dn', '--destination-namespace', type=click.STRING, default=None, help='Target Namespace') | ||
def setup(app_name, verbose, source_file, destination_namespace): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor but the click library will automatically parse any @click.command
-wrapped function docstrings and display them as --help
:)
@@ -3,6 +3,7 @@ apiVersion: extensions/v1beta1 | |||
kind: Deployment | |||
metadata: | |||
name: {{ project_name }}-{{ component }} | |||
namespace: default |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
LGTM! Will get this merged and into a release! |
Problem
We want to be able to easily deploy different images of the same app on k8s.
Possible Solution
Adding following command:
What this command does is, it copies
staging.yaml
, adds a newnamespace
definition for this app instance and updates thenamespace
in yaml to point to newly created one and stores the result in<app_name>.yaml
.The idea is, we provide this
yaml
file and then we need to manually push the image with proper tag and create this deployment.