Get container running on kubernetes / tilt
This commit is contained in:
parent
a77e2d7671
commit
bc25cf1eba
3
.gitignore
vendored
3
.gitignore
vendored
@ -16,3 +16,6 @@ htmlcov
|
||||
|
||||
# don't commit cache
|
||||
cache
|
||||
|
||||
# k8s
|
||||
.helm
|
||||
|
@ -6,14 +6,16 @@ RUN pip install -U pip \
|
||||
|
||||
ENV PATH="${PATH}:/root/.poetry/bin"
|
||||
|
||||
COPY . /app
|
||||
COPY ./pyproject.toml /app/pyproject.toml
|
||||
COPY ./poetry.lock /app/poetry.lock
|
||||
WORKDIR /app/
|
||||
|
||||
# poetry uses virtual env by default, turn this off inside container
|
||||
RUN poetry config virtualenvs.create false && \
|
||||
poetry install
|
||||
|
||||
COPY . /app
|
||||
|
||||
# easter eggs 😝
|
||||
RUN echo "PS1='🕵️:\[\033[1;36m\]\h \[\033[1;34m\]\W\[\033[0;35m\]\[\033[1;36m\]$ \[\033[0m\]'" >> ~/.bashrc
|
||||
|
||||
CMD /bin/bash
|
||||
CMD ["/bin/bash"]
|
||||
|
16
Tiltfile
Normal file
16
Tiltfile
Normal file
@ -0,0 +1,16 @@
|
||||
load('ext://helm_remote', 'helm_remote')
|
||||
helm_remote("postgresql",
|
||||
repo_name='bitnami',
|
||||
repo_url='https://charts.bitnami.com/bitnami',
|
||||
values=["k8s/postgresql/values_dev.yaml"]
|
||||
)
|
||||
|
||||
docker_build('mev-inspect', '.',
|
||||
live_update=[
|
||||
sync('.', '/app'),
|
||||
run('cd /app && poetry install',
|
||||
trigger='./pyproject.toml'),
|
||||
],
|
||||
)
|
||||
|
||||
k8s_yaml("k8s/app.yaml")
|
28
k8s/app.yaml
Normal file
28
k8s/app.yaml
Normal file
@ -0,0 +1,28 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: mev-inspect
|
||||
labels:
|
||||
app: mev-inspect
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: mev-inspect
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: mev-inspect
|
||||
spec:
|
||||
containers:
|
||||
- name: mev-inspect
|
||||
image: mev-inspect:latest
|
||||
command: [ "/bin/bash", "-c", "--" ]
|
||||
args: [ "while true; do sleep 30; done;" ]
|
||||
livenessProbe:
|
||||
exec:
|
||||
command:
|
||||
- ls
|
||||
- /
|
||||
initialDelaySeconds: 20
|
||||
periodSeconds: 5
|
5
k8s/postgresql/values_dev.yaml
Normal file
5
k8s/postgresql/values_dev.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
global:
|
||||
postgresql:
|
||||
postgresqlDatabase: "mev_inspect"
|
||||
postgresqlUsername: "postgres"
|
||||
postgresqlPassword: "password"
|
14
tilt_modules/extensions.json
Normal file
14
tilt_modules/extensions.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"Extensions": [
|
||||
{
|
||||
"Name": "helm_remote",
|
||||
"ExtensionRegistry": "https://github.com/tilt-dev/tilt-extensions",
|
||||
"TimeFetched": "2021-09-03T08:56:46.938205-04:00"
|
||||
},
|
||||
{
|
||||
"Name": "global_vars",
|
||||
"ExtensionRegistry": "https://github.com/tilt-dev/tilt-extensions",
|
||||
"TimeFetched": "2021-09-03T08:56:48.751933-04:00"
|
||||
}
|
||||
]
|
||||
}
|
13
tilt_modules/global_vars/README.md
Normal file
13
tilt_modules/global_vars/README.md
Normal file
@ -0,0 +1,13 @@
|
||||
# Git Resource
|
||||
|
||||
Author: [Bob Jackman](https://github.com/kogi)
|
||||
|
||||
An extension for reading/writing global variable values
|
||||
|
||||
## Usage
|
||||
|
||||
```python
|
||||
set_global('foo', some_value)
|
||||
print(get_global('foo'))
|
||||
unset_global('foo')
|
||||
```
|
11
tilt_modules/global_vars/Tiltfile
Normal file
11
tilt_modules/global_vars/Tiltfile
Normal file
@ -0,0 +1,11 @@
|
||||
def get_global(name):
|
||||
return os.getenv(_get_env_name(name))
|
||||
|
||||
def set_global(name, value):
|
||||
os.putenv(_get_env_name(name), value)
|
||||
|
||||
def unset_global(name):
|
||||
os.unsetenv(_get_env_name(name))
|
||||
|
||||
def _get_env_name(name):
|
||||
return 'TILT_GLOBAL_VAR_%s' % name.upper()
|
41
tilt_modules/helm_remote/README.md
Normal file
41
tilt_modules/helm_remote/README.md
Normal file
@ -0,0 +1,41 @@
|
||||
# Helm Remote
|
||||
|
||||
Author: [Bob Jackman](https://github.com/kogi)
|
||||
|
||||
Install a remotely hosted Helm chart in a way that it will be properly uninstalled when running `tilt down`
|
||||
|
||||
## Usage
|
||||
|
||||
#### Install a Remote Chart
|
||||
|
||||
```py
|
||||
load('ext://helm_remote', 'helm_remote')
|
||||
helm_remote('myChartName')
|
||||
```
|
||||
|
||||
##### Additional Parameters
|
||||
|
||||
```
|
||||
helm_remote(chart, repo_url='', repo_name='', release_name='', namespace='', version='', username='', password='', values=[], set=[])
|
||||
```
|
||||
|
||||
* `chart` ( str ) – the name of the chart to install
|
||||
* `repo_name` ( str ) – the name of the repo within which to find the chart (assuming the repo is already added locally)
|
||||
<br> if omitted, defaults to the same value as `chart_name`
|
||||
* `repo_url` ( str ) – the URL of the repo within which to find the chart (equivalent to `helm repo add <repo_name> <repo_url>`)
|
||||
* `release_name` (str) - the name of the helm release
|
||||
<br> if omitted, defaults to the same value as `chart_name`
|
||||
* `namespace` ( str ) – the namespace to deploy the chart to (equivalent to helm's `--namespace <namespace>` flags)
|
||||
* `version` ( str ) – the version of the chart to install. If omitted, defaults to latest version (equivalent to helm's `--version` flag)
|
||||
* `username` ( str ) – repository authentication username, if needed (equivalent to helm's `--username` flag)
|
||||
* `password` ( str ) – repository authentication password, if needed (equivalent to helm's `--password` flag)
|
||||
* `values` ( Union [ str , List [ str ]]) – Specify one or more values files (in addition to the values.yaml file in the chart). Equivalent to the helm's `--values` or `-f` flags
|
||||
* `set` ( Union [ str , List [ str ]]) – Directly specify one or more values (equivalent to helm's `--set` flag)
|
||||
* `allow_duplicates` ( bool ) - Allow duplicate resources. Usually duplicate resources indicate a programmer error.
|
||||
But some charts specify resources twice.
|
||||
* `create_namespace` ( bool ) - Create the namespace specified in `namespace` ( equivalent to helm's `--create_namespace` flag)
|
||||
|
||||
#### Change the Cache Location
|
||||
|
||||
By default `helm_remote` will store retrieved helm charts in the `.helm` directory at your workspace's root.
|
||||
This location can be customized by calling `os.putenv('TILT_HELM_REMOTE_CACHE_DIR', new_directory)` before loading the module.
|
214
tilt_modules/helm_remote/Tiltfile
Normal file
214
tilt_modules/helm_remote/Tiltfile
Normal file
@ -0,0 +1,214 @@
|
||||
load('ext://global_vars', 'get_global', 'set_global')
|
||||
|
||||
def _get_skip():
|
||||
return get_global(_get_skip_name())
|
||||
|
||||
|
||||
def _set_skip(value):
|
||||
set_global(_get_skip_name(), value)
|
||||
|
||||
|
||||
def _get_skip_name():
|
||||
return 'HELM_REMOTE_SKIP_UPDATES'
|
||||
|
||||
|
||||
if _get_skip() == None:
|
||||
_set_skip('False') # Gets set to true after the first update, preventing further updates within the same build/instance/up
|
||||
|
||||
|
||||
def _find_root_tiltfile_dir():
|
||||
# Find top-level Tilt path
|
||||
current = os.path.abspath('./')
|
||||
while current != '/':
|
||||
if os.path.exists(os.path.join(current, 'tilt_modules')):
|
||||
return current
|
||||
|
||||
current = os.path.dirname(current)
|
||||
|
||||
fail('Could not find root Tiltfile')
|
||||
|
||||
def _find_cache_dir():
|
||||
from_env = os.getenv('TILT_HELM_REMOTE_CACHE_DIR', '')
|
||||
if from_env != '':
|
||||
return from_env
|
||||
return os.path.join(_find_root_tiltfile_dir(), '.helm')
|
||||
|
||||
# this is the root directory into which remote helm charts will be pulled/cloned/untar'd
|
||||
# use `os.putenv('TILT_HELM_REMOTE_CACHE_DIR', new_dir)` to change
|
||||
helm_remote_cache_dir = _find_cache_dir()
|
||||
watch_settings(ignore=helm_remote_cache_dir)
|
||||
|
||||
# TODO: =====================================
|
||||
# if it ever becomes possible for loaded files to also load their own extensions
|
||||
# this method can be replaced by `load('ext://namespace', 'namespace_create')
|
||||
def namespace_create(name):
|
||||
"""Returns YAML for a namespace
|
||||
Args: name: The namespace name. Currently not validated.
|
||||
"""
|
||||
k8s_yaml(blob("""apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: %s
|
||||
""" % name))
|
||||
# TODO: end TODO
|
||||
# =====================================
|
||||
|
||||
|
||||
def helm_remote(chart, repo_url='', repo_name='', release_name='', values=[], set=[], namespace='', version='', username='', password='', allow_duplicates=False, create_namespace=False):
|
||||
# ======== Helper methods
|
||||
def get_local_repo(repo_name, repo_url):
|
||||
# if no repos are present, helm exit code is >0 and stderr output buffered
|
||||
added_helm_repos = decode_yaml(local('helm repo list --output yaml 2>/dev/null || true', command_bat='helm repo list --output yaml 2> nul || ver>nul', quiet=True))
|
||||
repo = [item for item in added_helm_repos if item['name'] == chart and item['url'] == repo_url] if added_helm_repos != None else []
|
||||
|
||||
return repo[0] if len(repo) > 0 else None
|
||||
|
||||
# Command string builder with common argument logic
|
||||
def build_helm_command(command, auth=None, version=None):
|
||||
command = 'helm ' + command
|
||||
|
||||
if auth != None:
|
||||
username, password = auth
|
||||
if username != '':
|
||||
command += ' --username %s' % shlex.quote(username)
|
||||
if password != '':
|
||||
command += ' --password %s' % shlex.quote(password)
|
||||
|
||||
if version != None and version != 'latest':
|
||||
command += ' --version %s' % shlex.quote(version)
|
||||
|
||||
return command
|
||||
|
||||
def fetch_chart_details(chart, repo_name, auth, version):
|
||||
command = build_helm_command('search repo %s/%s --output yaml' % (shlex.quote(repo_name), shlex.quote(chart)), None, version)
|
||||
results = decode_yaml(local(command, quiet=True))
|
||||
|
||||
return results[0] if len(results) > 0 else None
|
||||
|
||||
|
||||
# ======== Condition Incoming Arguments
|
||||
if repo_name == '':
|
||||
repo_name = chart
|
||||
if release_name == '':
|
||||
release_name = chart
|
||||
if namespace == '':
|
||||
namespace = 'default'
|
||||
if version == '':
|
||||
version = 'latest'
|
||||
|
||||
# ======== Validate before we start trusting chart/repo names
|
||||
|
||||
# Based on helm chart conventions, and the fact we don't want anyone traversing directories
|
||||
# validate is to essentially ensure there's no special characters aside from '-' being used
|
||||
# str.isalnum accepts dots, which is only dangerous when slashes are allowed
|
||||
# https://helm.sh/docs/chart_best_practices/conventions/#chart-names
|
||||
|
||||
if chart.replace('-', '').isalnum() == False or chart != chart.replace('.', ''):
|
||||
# https://helm.sh/docs/chart_best_practices/conventions/#chart-names
|
||||
fail('Chart name is not valid')
|
||||
|
||||
if repo_name != chart and repo_name.replace('-', '').isalnum() == False or repo_name != repo_name.replace('.', ''):
|
||||
# https://helm.sh/docs/chart_best_practices/conventions/#chart-names
|
||||
fail('Repo name is not valid')
|
||||
|
||||
if version != 'latest' and version != version.replace('/', '').replace('\\', ''):
|
||||
fail('Version cannot contain a forward slash')
|
||||
|
||||
|
||||
# ======== Determine state of existing helm repo
|
||||
if repo_url != '':
|
||||
local_helm_repo = get_local_repo(repo_name, repo_url)
|
||||
|
||||
if local_helm_repo == None:
|
||||
# Unaware of repo, add it
|
||||
repo_command = 'repo add %s %s' % (shlex.quote(repo_name), shlex.quote(repo_url))
|
||||
# Add authentication for adding the repository if credentials are provided
|
||||
output = str(local(build_helm_command(repo_command, (username, password)), quiet=True)).rstrip('\n')
|
||||
if 'already exists' not in output: # repo was added
|
||||
_set_skip('False')
|
||||
else:
|
||||
# Helm is already aware of the chart, update repo (unfortunately you cannot specify a single repo)
|
||||
if _get_skip() != 'True':
|
||||
repo_command = 'repo update'
|
||||
local(build_helm_command(repo_command), quiet=True)
|
||||
_set_skip('True')
|
||||
|
||||
# ======== Create Namespace
|
||||
if create_namespace and namespace != '' and namespace != 'default':
|
||||
# avoid a namespace not found error
|
||||
namespace_create(namespace) # do this early so it manages to register before we attempt to install into it
|
||||
|
||||
# ======== Initialize
|
||||
# -------- targets
|
||||
pull_target = os.path.join(helm_remote_cache_dir, repo_name, version)
|
||||
chart_target = os.path.join(pull_target, chart)
|
||||
|
||||
cached_chart_exists = os.path.exists(chart_target)
|
||||
|
||||
needs_pull = True
|
||||
|
||||
if cached_chart_exists:
|
||||
# Helm chart structure is concrete, we can trust this YAML file to exist
|
||||
cached_chart_details = read_yaml(os.path.join(chart_target, 'Chart.yaml'))
|
||||
|
||||
# check if our local cached chart matches latest remote
|
||||
remote_chart_details = fetch_chart_details(chart, repo_name, (username, password), version)
|
||||
|
||||
# pull when version mismatch
|
||||
needs_pull = cached_chart_details['version'] != remote_chart_details['version']
|
||||
|
||||
|
||||
if needs_pull:
|
||||
# -------- commands
|
||||
pull_command = 'pull %s/%s --untar --destination %s' % (repo_name, chart, pull_target)
|
||||
|
||||
# ======== Perform Installation
|
||||
if cached_chart_exists:
|
||||
local('rm -rf %s' % chart_target, command_bat='if exist %s ( rd /s /q %s )' % (chart_target, chart_target), quiet=True)
|
||||
|
||||
local(build_helm_command(pull_command, (username, password), version), quiet=True)
|
||||
|
||||
install_crds(chart, chart_target)
|
||||
|
||||
# TODO: since neither `k8s_yaml()` nor `helm()` accept resource_deps,
|
||||
# sometimes the crds haven't yet finished installing before the below tries
|
||||
# to run
|
||||
yaml = helm(chart_target, name=release_name, namespace=namespace, values=values, set=set)
|
||||
|
||||
# The allow_duplicates API is only available in 0.17.1+
|
||||
if allow_duplicates and _version_tuple() >= [0, 17, 1]:
|
||||
k8s_yaml(yaml, allow_duplicates=allow_duplicates)
|
||||
else:
|
||||
k8s_yaml(yaml)
|
||||
|
||||
return yaml
|
||||
|
||||
def _version_tuple():
|
||||
ver_string = str(local('tilt version', quiet=True))
|
||||
versions = ver_string.split(', ')
|
||||
# pull first string and remove the `v` and `-dev`
|
||||
version = versions[0].replace('-dev', '').replace('v', '')
|
||||
return [int(str_num) for str_num in version.split(".")]
|
||||
|
||||
# install CRDs as a separate resource and wait for them to be ready
|
||||
def install_crds(name, directory):
|
||||
name += '-crds'
|
||||
files = str(local(r"grep --include='*.yaml' --include='*.yml' -rEil '\bkind[^\w]+CustomResourceDefinition\s*$' %s || exit 0" % directory, quiet=True)).rstrip('\n')
|
||||
|
||||
if files == '':
|
||||
files = []
|
||||
else:
|
||||
files = files.split("\n")
|
||||
|
||||
# we're applying CRDs directly and not using helm preprocessing
|
||||
# this will cause errors!
|
||||
# since installing CRDs in this function is a nice-to-have, just skip
|
||||
# any that have preprocessing
|
||||
files = [f for f in files if str(read_file(f)).find('{{') == -1]
|
||||
|
||||
if len(files) != 0:
|
||||
local_resource(name+'-install', cmd='kubectl apply -f %s' % " -f ".join(files), deps=files) # we can wait/depend on this, but it won't cause a proper uninstall
|
||||
k8s_yaml(files) # this will cause a proper uninstall, but we can't wait/depend on it
|
||||
|
||||
# TODO: Figure out how to avoid another named resource showing up in the tilt HUD for this waiter
|
||||
local_resource(name+'-ready', resource_deps=[name+'-install'], cmd='kubectl wait --for=condition=Established crd --all') # now we can wait for those crds to finish establishing
|
7
tilt_modules/helm_remote/test/Dockerfile
Normal file
7
tilt_modules/helm_remote/test/Dockerfile
Normal file
@ -0,0 +1,7 @@
|
||||
FROM alpine
|
||||
|
||||
RUN apk update && apk add expect busybox-extras
|
||||
|
||||
ADD ./verify.exp ./verify.exp
|
||||
|
||||
ENTRYPOINT expect < verify.exp
|
17
tilt_modules/helm_remote/test/Tiltfile
Normal file
17
tilt_modules/helm_remote/test/Tiltfile
Normal file
@ -0,0 +1,17 @@
|
||||
os.putenv('TILT_HELM_REMOTE_CACHE_DIR', os.path.abspath('./.helm'))
|
||||
load('../Tiltfile', 'helm_remote')
|
||||
|
||||
# Note that .helm is in the .tiltignore!
|
||||
helm_remote('memcached', repo_url='https://charts.bitnami.com/bitnami')
|
||||
if not os.path.exists('./.helm/memcached'):
|
||||
fail('memcached failed to load in the right directory')
|
||||
|
||||
# This chart has a bunch of CRDs (including templated CRDs), so we can test the CRD init logic.
|
||||
helm_remote('gloo', repo_url='https://storage.googleapis.com/solo-public-helm',
|
||||
# The gloo chart has duplicate resources, see discussion here:
|
||||
# https://github.com/tilt-dev/tilt/issues/3656
|
||||
allow_duplicates=True)
|
||||
|
||||
docker_build('helm-remote-test-verify', '.')
|
||||
k8s_yaml('job.yaml')
|
||||
k8s_resource('helm-remote-test-verify', resource_deps=['memcached'])
|
12
tilt_modules/helm_remote/test/job.yaml
Normal file
12
tilt_modules/helm_remote/test/job.yaml
Normal file
@ -0,0 +1,12 @@
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: helm-remote-test-verify
|
||||
spec:
|
||||
backoffLimit: 1
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: helm-remote-test-verify
|
||||
image: helm-remote-test-verify
|
||||
restartPolicy: Never
|
7
tilt_modules/helm_remote/test/test.sh
Executable file
7
tilt_modules/helm_remote/test/test.sh
Executable file
@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
set -ex
|
||||
tilt ci
|
||||
tilt down --delete-namespaces
|
12
tilt_modules/helm_remote/test/verify.exp
Normal file
12
tilt_modules/helm_remote/test/verify.exp
Normal file
@ -0,0 +1,12 @@
|
||||
#!/usr/bin/expect
|
||||
|
||||
spawn telnet memcached 11211
|
||||
expect {
|
||||
timeout {exit 1}
|
||||
"Connected"
|
||||
}
|
||||
send "stats\r\n"
|
||||
expect {
|
||||
timeout {exit 1}
|
||||
"STAT pid 1"
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user