Skip to content

Commit

Permalink
get_obj: add new extension (tilt-dev#483)
Browse files Browse the repository at this point in the history
* feat: add remote_development

Signed-off-by: Kobi Carmeli <[email protected]>

* add to root readme file

Signed-off-by: Kobi Carmeli <[email protected]>

* refactor expose_ports

Signed-off-by: Kobi Carmeli <[email protected]>

* remove port exposure

Signed-off-by: Kobi Carmeli <[email protected]>

* delete call to expose_ports

Signed-off-by: Kobi Carmeli <[email protected]>

* rename to get_obj

Signed-off-by: Kobi Carmeli <[email protected]>

* fix test

Signed-off-by: Kobi Carmeli <[email protected]>

* fix test

Signed-off-by: Kobi Carmeli <[email protected]>

* fix test

Signed-off-by: Kobi Carmeli <[email protected]>

* fix test

Signed-off-by: Kobi Carmeli <[email protected]>

* fix test

Signed-off-by: Kobi Carmeli <[email protected]>

* fix test

Signed-off-by: Kobi Carmeli <[email protected]>

* debug test

Signed-off-by: Kobi Carmeli <[email protected]>

* update readme

Signed-off-by: Kobi Carmeli <[email protected]>

---------

Signed-off-by: Kobi Carmeli <[email protected]>
  • Loading branch information
kobik authored Mar 13, 2023
1 parent 5a8037e commit d16db1f
Show file tree
Hide file tree
Showing 14 changed files with 1,134 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ All extensions have been vetted and approved by the Tilt team.
- [`earthly`](/earthly): Build container images using [earthly](https://earthly.dev)
- [`execute_in_pod`](/execute_in_pod): Execute a command on a pod container.
- [`file_sync_only`](/file_sync_only): No-build, no-push, file sync-only development. Useful when you want to live-reload a single config file into an existing public image, like nginx.
- [`get_obj`](/get_obj): Get object yaml and the container's registry and image from an existing k8s resource such as deployment or statefulset
- [`git_resource`](/git_resource): Deploy a dockerfile from a remote repository -- or specify the path to a local checkout for local development.
- [`hasura`](/hasura): Deploys [Hasura GraphQL Engine](https://hasura.io/) and monitors metadata/migrations changes locally.
- [`hello_world`](/hello_world): Print "Hello world!". Used in [Extensions](https://docs.tilt.dev/extensions.html).
Expand Down
64 changes: 64 additions & 0 deletions get_obj/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# get obj

Author: [Kobi Carmeli](https://github.com/kobik)

Get object's yaml and image from an existing k8s resource such as deployment or statefulset.
Develop, live update and debug against applications that were already deployed to Kubernetes from CI/CD or other tools.

## `get_obj(name: str, kind: str, container_selector: str, namespace: str, disable_probes: bool)`
- **name**: The object's name as you would express it to kubectl. e.g my-deployment.
- **kind**: The object's kind. e.g 'deployment' or 'statefulset'
- **container_selector**: The container name in the pod that will be used for remote development.
- **namespace**?: The namespace of the object. If not specified, uses the current namespace.
- **disable_probes**?: Disable liveness and readiness probes by removing them from the deployment, which is useful when debugging node applications. Default: False.

### Example Usage
```
# Dockerfile.dev
FROM node:18-alpine
WORKDIR /app
COPY ./app ./
RUN npm ci
CMD [ \
"npx", "nodemon", \
"--verbose", \
"--watch", ".", \
"--ext", "js" , \
"--signal", "SIGHUP", \
"--inspect", \
"index.js" \
]
```

```
# Tiltfile
load('ext://get_obj', 'get_obj')
# obj=struct(yaml, image, registry)
obj=get_obj(
name='example-nodejs',
kind='deployment',
container_selector='my-container',
disable_probes=True
)
docker_build(obj.registry, '.',
live_update=[
sync('./app', '/app'),
],
...
)
k8s_yaml(yaml)
k8s_resource(
name,
port_forwards=[
"8080:8080",
"9229:9229"
],
...
)
```
51 changes: 51 additions & 0 deletions get_obj/Tiltfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
def get_container(decoded_yaml, container_selector):
containers=decoded_yaml["spec"]["template"]["spec"]["containers"]
found_containers=[]

for container in containers:
if container["name"] == container_selector:
return container

fail("failed to find container %s in the manifest %s" % (container_selector, containers))

def get_image(container):
return container["image"]

def remove_probe(container, probe):
if container.get(probe):
container.pop(probe)

def get_obj(name, kind, container_selector = None, namespace = None, disable_probes = False):
"""
Enable remote developement and debugging environment on an exiting deployment.
It will redeploy the app using the given development Dockerfile, enable port-forwarding and disable healthcheck in order to enable remote debugging.
Both docker image build and port-forwarding can be replaced by your own docker_build and k8s_resource
Args:
name: The object's name as you would express it to kubectl. e.g my-deployment.
kind: The object's kind. e.g 'deployment' or 'statefulset'
container_selector: The container name in the pod that will be used for remote development.
namespace?: The namespace of the object. If not specified, uses the current namespace.
disable_probes?: Disable liveness and readiness probes by removing them from the deployment, which is useful when debugging node applications. Default: False.
"""
args = ["kubectl", "get", "-o=yaml", '{}/{}'.format(kind, name)]
if namespace:
args.extend(["-n", namespace])

yaml=local(args, quiet=True)
decoded_yaml=decode_yaml(yaml)

if container_selector:
container=get_container(decoded_yaml, container_selector)
image=get_image(container)
registry=image.rsplit(":", 1)[0]
else:
image=None
registry=None

# disable probes
if container_selector and disable_probes:
remove_probe(container, "livenessProbe")
remove_probe(container, "readinessProbe")
yaml=encode_yaml(decoded_yaml)

return struct(yaml=yaml, image=image, registry=registry)
2 changes: 2 additions & 0 deletions get_obj/test/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*
!app
7 changes: 7 additions & 0 deletions get_obj/test/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
FROM node:18-alpine
WORKDIR /app

COPY ./app/ ./
RUN npm ci

CMD [ "node", "index.js" ]
16 changes: 16 additions & 0 deletions get_obj/test/Dockerfile.dev
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
FROM node:18-alpine
WORKDIR /app

COPY ./app ./
RUN npm ci
RUN echo "new image"

CMD [ \
"npx", "nodemon", \
"--verbose", \
"--watch", ".", \
"--ext", "js" , \
"--signal", "SIGHUP", \
"--inspect", \
"index.js" \
]
26 changes: 26 additions & 0 deletions get_obj/test/Tiltfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
load('../Tiltfile', 'get_obj')

name='example-nodejs'
obj=get_obj(
name=name,
kind='deployment',
container_selector='my-container',
disable_probes=True
)

docker_build(obj.registry, '.',
dockerfile='Dockerfile.dev',
live_update=[
sync('./app', '/app'),
]
)

k8s_yaml(obj.yaml)

k8s_resource(
name,
port_forwards=[
"8080:8080",
"9229:9229"
]
)
14 changes: 14 additions & 0 deletions get_obj/test/app/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const express = require('express');

const app = express();

app.get('/', function(req, res, next) {
res.send("cool!!!")
});

// catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404));
});

module.exports = app;
13 changes: 13 additions & 0 deletions get_obj/test/app/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const app = require('./app.js');

const port = 8080;

process.on('SIGHUP', () => {
process.exit()
})
process.on('SIGINT', () => {
process.exit()
})
app.listen(port, () => {
console.info('listening on port', 8080);
})
Loading

0 comments on commit d16db1f

Please sign in to comment.