Skip to content

Commit

Permalink
allow-specifying-base-image
Browse files Browse the repository at this point in the history
  • Loading branch information
samhotep committed Jul 15, 2024
1 parent 409a527 commit a37668f
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 50 deletions.
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ $ dotrun -s {script} # Run {script} but skip installing dependencies
$ dotrun --env FOO=bar {script} # Run {script} with FOO environment variable
$ dotrun -m "/path/to/mount":"localname" # Mount additional directory and run `dotrun`
$ dotrun serve -m "/path/to/mount":"localname" # Mount additional directory and run `dotrun serve`
$ dotrun refresh image # Download the latest version of dotrun-image
$ dotrun --release {release-version} # Use a specific image tag for dotrun. Useful for switching versions
$ dotrun --image {image-name} # Use a specific image for dotrun. Useful for running dotrun off local images
```

## Installation
Expand Down Expand Up @@ -105,3 +108,33 @@ docker buildx create --name mybuilder
docker buildx use mybuilder
docker buildx build --push --platform linux/arm/v7,linux/arm64/v8,linux/amd64 --tag canonicalwebteam/dotrun-image:latest .
```

## Hacking

To quickly build and test changes to the package, first install poetry:
```bash
pip install poetry
poetry install
chmod +x build.sh
```

Then you can build and install .whl packages using:
```bash
./build.sh
```

To run dotrun off alternative base images such as local images, you can use the `--image` flag.
```bash
dotrun --image "localimage" exec echo hello
```

To run dotrun off alternative releases, besides the `:latest` release, you can use the `--release` flag.
```bash
dotrun --release "latest" serve
```

Note that before changing the base image you should run
```bash
dotrun clean
```
To get rid of the old virtualenv and
4 changes: 4 additions & 0 deletions build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/bash
poetry build
pip uninstall -y dotrun
pip install --no-index --find-links dist/ dotrun
99 changes: 88 additions & 11 deletions dotrun.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,16 @@
# Packages
import docker
import dockerpty
import requests
from dotenv import dotenv_values
from slugify import slugify

__version__ = metadata.version("dotrun")


class Dotrun:
base_image_name = "canonicalwebteam/dotrun-image:latest"

def __init__(self):
self.cwd = os.getcwd()
self.project_name = slugify(os.path.basename(self.cwd))
Expand All @@ -29,9 +32,27 @@ def __init__(self):
sys.platform.startswith("linux")
and "microsoft" not in platform.platform()
)

self._get_docker_client()
self._check_image_updates()
self._create_cache_volume()

def _get_release_image_name(self, image_tag="latest"):
"""
Return the image name with the tag in the format
canonicalwebteam/dotrun-image:<release_tag>
"""
base_image = self.base_image_name.split(":")[0]
return f"{base_image}:{image_tag}"

def _get_image_name(self, image_name):
"""
Return a fully qualified image name from a given image
name, defaulting to the :latest tag if none is provided.
"""
if ":" not in image_name:
return image_name + ":latest"
return image_name

def _get_docker_client(self):
try:
Expand All @@ -48,7 +69,7 @@ def _get_docker_client(self):
def _check_image_updates(self):
try:
self.docker_client.images.get(
"canonicalwebteam/dotrun-image:latest"
self.base_image_name
)
# Pull the image in the background
print("Checking for dotrun image updates...")
Expand All @@ -57,11 +78,21 @@ def _check_image_updates(self):
print("Getting the dotrun image...")
self._pull_image()

def _pull_image(self):
def _pull_image(self, image_name, no_exit=False):
"""Pull the dotrun image (if updated) from Docker Hub"""
self.docker_client.images.pull(
repository="canonicalwebteam/dotrun-image", tag="latest"
)
image_uri = self._get_image_name(image_name)
repository, tag = image_uri.split(":")
try:
self.docker_client.images.pull(
repository=repository, tag=tag
)
except (docker.errors.APIError, docker.errors.ImageNotFound) as e:
print(f"Unable to download image: {image_name}")
# Optionally quit if image download fails
if not no_exit:
print(e)
sys.exit(1)
print(f"Attempting to use local image: {image_name}")

def _create_cache_volume(self):
try:
Expand All @@ -71,7 +102,7 @@ def _create_cache_volume(self):

# We need to fix the volume ownership
self.docker_client.containers.run(
"canonicalwebteam/dotrun-image",
self.base_image_name,
f"chown -R ubuntu:ubuntu {self.container_home}.cache",
user="root",
mounts=self._prepare_mounts([]),
Expand Down Expand Up @@ -154,7 +185,9 @@ def get_mount(command, mounts):

return get_mount(command, [])

def create_container(self, command):
def create_container(self, command, image_name=None):
if not image_name:
image_name = self.base_image_name
ports = {self.project_port: self.project_port}
# Run on the same network mode as the host
network_mode = None
Expand All @@ -173,9 +206,9 @@ def create_container(self, command):
# network_mode host is incompatible with ports option
ports = None
network_mode = "host"

return self.docker_client.containers.create(
image="canonicalwebteam/dotrun-image",
image=image_name,
name=name,
hostname=name,
mounts=self._prepare_mounts(command),
Expand All @@ -188,6 +221,35 @@ def create_container(self, command):
network_mode=network_mode,
)

def _start_container_with_image(dotrun, command_list, command_match, format="tag"):
"""
Utility function to start dotrun using a specified
image.
"""
# Extract the argument from the cli arg
image_command = command_match.group(0)
try:
image_data = image_command.split(' ')[1]
except IndexError:
print("Image name not supplied.")
sys.exit(1)

# Determine the image name
if format == "release":
image_uri = dotrun._get_release_image_name(image_data)
else:
image_uri = dotrun._get_image_name(image_data)
print(f"Using image: {image_uri}")

# Download the image
dotrun._pull_image(image_uri, no_exit=True)

# Remove the image command from command list
new_command_list = ' '.join(command_list).replace(image_command, '').replace(' ', ' ')
command_list = new_command_list.split(' ')

# Start dotrun from the supplied base image
return dotrun.create_container(command_list, image_name=image_uri)

def cli():
dotrun = Dotrun()
Expand All @@ -196,8 +258,20 @@ def cli():

if command[-1] == "version":
print(f"dotrun v{__version__}")

container = dotrun.create_container(command)
sys.exit(1)

if command[-1] == "refresh":
dotrun._pull_image()
print("Latest image pulled successfully.")
sys.exit(1)

# Options for starting the container on different base images
if match := re.search(r'--image [^\s]+', ' '.join(command)):
container = _start_container_with_image(dotrun, command, match)
elif match := re.search(r'--release [^\s]+', ' '.join(command)):
container = _start_container_with_image(dotrun, command, match, format="release")
else:
container = dotrun.create_container(command)

# 1 by default
status_code = 1
Expand All @@ -210,3 +284,6 @@ def cli():
container.remove()

return status_code

if __name__ == "__main__":
cli()
Loading

0 comments on commit a37668f

Please sign in to comment.