Browse Source

Updated examples for release of Compose support.

This commit restructures and updates the examples directory for the new
Compose support arriving in Mutagen v0.12.

Signed-off-by: Jacob Howard <jacob@havoc.io>
tags/v0.12.0-beta2
Jacob Howard 2 months ago
parent
commit
07d874a84a
75 changed files with 4262 additions and 88 deletions
  1. 0
    7
      docker-compose.yml
  2. 7
    30
      examples/README.md
  3. 8
    0
      examples/compose/README.md
  4. 62
    0
      examples/compose/data-science/Commits.ipynb
  5. 19
    0
      examples/compose/data-science/README.md
  6. 0
    0
      examples/compose/data-science/analysis/__init__.py
  7. 0
    0
      examples/compose/data-science/analysis/example.py
  8. 42
    0
      examples/compose/data-science/docker-compose.yml
  9. 8
    0
      examples/compose/data-science/jupyter/Dockerfile
  10. 19
    0
      examples/compose/web-go/README.md
  11. 0
    0
      examples/compose/web-go/api/Dockerfile
  12. 0
    0
      examples/compose/web-go/api/entrypoint.sh
  13. 10
    0
      examples/compose/web-go/api/go.mod
  14. 0
    0
      examples/compose/web-go/api/go.sum
  15. 0
    0
      examples/compose/web-go/api/server.go
  16. 6
    0
      examples/compose/web-go/database/Dockerfile
  17. 0
    0
      examples/compose/web-go/database/schema.sql
  18. 69
    0
      examples/compose/web-go/docker-compose.yml
  19. 9
    0
      examples/compose/web-go/frontend/Dockerfile
  20. 0
    0
      examples/compose/web-go/frontend/entrypoint.sh
  21. 0
    0
      examples/compose/web-go/frontend/gulpfile.js
  22. 0
    0
      examples/compose/web-go/frontend/index.html
  23. 0
    0
      examples/compose/web-go/frontend/package-lock.json
  24. 0
    0
      examples/compose/web-go/frontend/package.json
  25. 0
    0
      examples/compose/web-go/web/Dockerfile
  26. 0
    0
      examples/compose/web-go/web/entrypoint.sh
  27. 3
    0
      examples/compose/web-go/web/go.mod
  28. 0
    0
      examples/compose/web-go/web/server.go
  29. 0
    5
      examples/docker/README.md
  30. 8
    0
      examples/projects/README.md
  31. 8
    0
      examples/projects/docker/README.md
  32. 0
    0
      examples/projects/docker/data-science/.gitignore
  33. 2
    2
      examples/projects/docker/data-science/Commits.ipynb
  34. 7
    0
      examples/projects/docker/data-science/README.md
  35. 0
    0
      examples/projects/docker/data-science/analysis/__init__.py
  36. 0
    0
      examples/projects/docker/data-science/analysis/example.py
  37. 0
    0
      examples/projects/docker/data-science/containers/jupyter/Dockerfile
  38. 1
    1
      examples/projects/docker/data-science/docker-compose.yml
  39. 1
    1
      examples/projects/docker/data-science/mutagen.yml
  40. 0
    0
      examples/projects/docker/web-go/.gitignore
  41. 7
    0
      examples/projects/docker/web-go/README.md
  42. 9
    0
      examples/projects/docker/web-go/api/Dockerfile
  43. 13
    0
      examples/projects/docker/web-go/api/entrypoint.sh
  44. 0
    0
      examples/projects/docker/web-go/api/go.mod
  45. 8
    0
      examples/projects/docker/web-go/api/go.sum
  46. 176
    0
      examples/projects/docker/web-go/api/server.go
  47. 0
    0
      examples/projects/docker/web-go/database/Dockerfile
  48. 11
    0
      examples/projects/docker/web-go/database/schema.sql
  49. 0
    0
      examples/projects/docker/web-go/docker-compose.yml
  50. 0
    0
      examples/projects/docker/web-go/frontend/Dockerfile
  51. 16
    0
      examples/projects/docker/web-go/frontend/entrypoint.sh
  52. 36
    0
      examples/projects/docker/web-go/frontend/gulpfile.js
  53. 118
    0
      examples/projects/docker/web-go/frontend/index.html
  54. 3493
    0
      examples/projects/docker/web-go/frontend/package-lock.json
  55. 11
    0
      examples/projects/docker/web-go/frontend/package.json
  56. 0
    0
      examples/projects/docker/web-go/frontend/wait-for-initial-build.sh
  57. 1
    1
      examples/projects/docker/web-go/mutagen.yml
  58. 0
    0
      examples/projects/docker/web-go/mutagen/Dockerfile
  59. 9
    0
      examples/projects/docker/web-go/web/Dockerfile
  60. 13
    0
      examples/projects/docker/web-go/web/entrypoint.sh
  61. 0
    0
      examples/projects/docker/web-go/web/go.mod
  62. 26
    0
      examples/projects/docker/web-go/web/server.go
  63. 1
    1
      examples/projects/tunnels/README.md
  64. 0
    0
      examples/projects/tunnels/data-science/.gitignore
  65. 2
    2
      examples/projects/tunnels/data-science/Commits.ipynb
  66. 0
    0
      examples/projects/tunnels/data-science/README.md
  67. 0
    0
      examples/projects/tunnels/data-science/analysis/__init__.py
  68. 22
    0
      examples/projects/tunnels/data-science/analysis/example.py
  69. 0
    0
      examples/projects/tunnels/data-science/containers/jupyter/Dockerfile
  70. 0
    0
      examples/projects/tunnels/data-science/containers/tunnel/Dockerfile
  71. 0
    0
      examples/projects/tunnels/data-science/docker-compose.yml
  72. 1
    1
      examples/projects/tunnels/data-science/mutagen.yml
  73. 0
    14
      mutagen.yml
  74. 0
    4
      scripts/environments/README.md
  75. 0
    19
      scripts/environments/development/Dockerfile

+ 0
- 7
docker-compose.yml View File

@@ -1,7 +0,0 @@
version: "3.7"

services:
development:
build: ./scripts/environments/development
container_name: mutagen-development
stop_grace_period: 0s

+ 7
- 30
examples/README.md View File

@@ -1,32 +1,9 @@
# Examples

This directory contains example setups that show how to use Mutagen (in
conjunction with container orchestration tools) to create remote development
environments that work with your local tools. These examples are designed to be
used as templates for more complex setups, so they aim for simplicity while
trying to cover the most common cases.

More advanced behaviors can be achieved using Mutagen's extensive
[synchronization](https://mutagen.io/documentation/synchronization) and
[forwarding](https://mutagen.io/documentation/forwarding) configuration options,
as well as by combining multiple sessions for a single project or codebase. It's
important to remember that Mutagen works between any pair of endpoints
(local/local, local/remote, remote/local, and remote/remote), with any
combination of [transports](https://mutagen.io/documentation/transports), and
can synchronize files and forward network traffic in arbitrary directions. The
examples given here only demonstrate a very limited (but pragmatic) subset of
these behaviors.

If you need help brainstorming the right setup for your particular use case,
check out the Mutagen community chat:

[![Join the community on Spectrum](https://withspectrum.github.io/badge/badge.svg)](https://spectrum.chat/mutagen)


## Contributing

If you'd like to contribute an example of using Mutagen with your technology
stack of choice, we'd love for you to
[open a Pull Request](../CONTRIBUTING.md#pull-requests)! The easiest way to do
this might be to fork one of the existing examples and adjust certain parts of
it to meet your needs, but variety is also very welcome and helpful!
This directory contains example projects that use Mutagen's
[orchestration mechanisms](https://mutagen.io/documentation/orchestration) to
automate the process of using synchronization and forwarding with containerized
development environments. There are two types of examples: those using
[Mutagen's Docker Compose integration](https://mutagen.io/documentation/orchestration/compose)
and those using Mutagen's more generic
[project](https://mutagen.io/documentation/orchestration/projects) mechanism.

+ 8
- 0
examples/compose/README.md View File

@@ -0,0 +1,8 @@
# Project examples

This directory contains examples that use
[Mutagen's Docker Compose integration](https://mutagen.io/documentation/orchestration/compose)
to automate the creation of Mutagen synchronization and forwarding sessions with
Compose-based projects. The examples in this directory can be run using Docker
Desktop or using a cloud-based Docker host. In both cases, you'll be able to
edit code and access applications locally.

+ 62
- 0
examples/compose/data-science/Commits.ipynb View File

@@ -0,0 +1,62 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Import analysis functions\n",
"from analysis.example import load_commit_times"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Load commit times.\n",
"times = load_commit_times()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Group and plot commits by day of the week\n",
"times.groupby(times.dt.day_name()).count().plot(kind='bar')"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.4"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

+ 19
- 0
examples/compose/data-science/README.md View File

@@ -0,0 +1,19 @@
# Data science

This directory contains an example [Jupyter](https://jupyter.org/)-based data
science environment designed to be run on a cloud-based container host (though
it can also be run locally, e.g. via
[Docker Desktop](https://www.docker.com/products/docker-desktop)). It uses
[Mutagen's support for Docker Compose](https://mutagen.io/documentation/orchestration/compose)
to synchronize code from the local filesystem to a shared container volume and
to forward network traffic to the containerized Jupyter notebook server,
allowing you to edit code and access the environment locally regardless of
where the project is running.


## Usage

This example behaves like any other Composed-based project—you'll just need to
replace any `docker-compose` command with `mutagen compose`. For more
information and setup, please see the
[documentation](https://mutagen.io/documentation/orchestration/compose).

examples/docker/data-science/analysis/__init__.py → examples/compose/data-science/analysis/__init__.py View File


examples/docker/data-science/analysis/example.py → examples/compose/data-science/analysis/example.py View File


+ 42
- 0
examples/compose/data-science/docker-compose.yml View File

@@ -0,0 +1,42 @@
# Set the Docker Compose file format.
version: "3.7"

# Create a volume to store and share synchronized code.
volumes:
code:

# Define the service(s) that constitute the remote environment.
services:
jupyter:
build: ./jupyter
stop_grace_period: 0s
volumes:
- code:/code

# Set up Mutagen synchronization and forwarding. We configure synchronization to
# use UID 1000 and GID 100 for creating files, since these IDs correspond to
# jovyan and users, respectively, inside the jupyter container. We target a
# subdirectory of the code volume because we can't control the ownership of the
# volume itself.
x-mutagen:
sync:
analysis:
alpha: "."
beta: "volume://code/data-science"
mode: "two-way-safe"
ignore:
vcs: true
paths:
- "/*"
- "!/*.ipynb"
- "!/analysis/"
- "*.py[cod]"
- "__pycache__"
configurationBeta:
permissions:
defaultOwner: "id:1000"
defaultGroup: "id:100"
forward:
jupyter:
source: "tcp:localhost:8888"
destination: "network://default:tcp:jupyter:8888"

+ 8
- 0
examples/compose/data-science/jupyter/Dockerfile View File

@@ -0,0 +1,8 @@
# Use the Jupyter Data Science Notebook image as our base.
FROM jupyter/datascience-notebook:latest

# Set the working directory to the location where we'll synchronize code.
WORKDIR /code/data-science

# Override the default notebook command to set a password ('mutagen').
CMD ["start-notebook.sh", "--NotebookApp.password=sha1:3e166aeb6462:ec7d7f843690ef8284105bf66797fc8ae270063f"]

+ 19
- 0
examples/compose/web-go/README.md View File

@@ -0,0 +1,19 @@
# Go-based web application

This directory contains an example [Go](https://golang.org/)-based web
application designed to be developed and run on a cloud-based container host
(though it can also be run locally, e.g. via
[Docker Desktop](https://www.docker.com/products/docker-desktop)). It uses
[Mutagen's support for Docker Compose](https://mutagen.io/documentation/orchestration/compose)
to synchronize code from the local filesystem to a shared container volume and
to forward network traffic to the various containerized services, allowing you
to edit code and access the application locally regardless of where the project
is running.


## Usage

This example behaves like any other Composed-based project—you'll just need to
replace any `docker-compose` command with `mutagen compose`. For more
information and setup, please see the
[documentation](https://mutagen.io/documentation/orchestration/compose).

examples/docker/web-go/api/Dockerfile → examples/compose/web-go/api/Dockerfile View File


examples/docker/web-go/api/entrypoint.sh → examples/compose/web-go/api/entrypoint.sh View File


+ 10
- 0
examples/compose/web-go/api/go.mod View File

@@ -0,0 +1,10 @@
module github.com/mutagen-io/mutagen/examples/compose/web-go/api

go 1.13

require (
github.com/gorilla/schema v1.1.0
github.com/julienschmidt/httprouter v1.3.0
github.com/lib/pq v1.3.0
github.com/rs/cors v1.7.0
)

examples/docker/web-go/api/go.sum → examples/compose/web-go/api/go.sum View File


examples/docker/web-go/api/server.go → examples/compose/web-go/api/server.go View File


+ 6
- 0
examples/compose/web-go/database/Dockerfile View File

@@ -0,0 +1,6 @@
# Use a recent Postgres base image.
FROM postgres:latest

# Copy in the schema initialization script. The Postgres image will pick up on
# the presence of this script and run it when the container is first created.
COPY ["schema.sql", "/docker-entrypoint-initdb.d/schema.sql"]

examples/docker/web-go/database/schema.sql → examples/compose/web-go/database/schema.sql View File


+ 69
- 0
examples/compose/web-go/docker-compose.yml View File

@@ -0,0 +1,69 @@
# Set the Docker Compose file format.
version: "3.7"

# Create a volume to store and share synchronized code.
volumes:
code:

# Define the services (containers) that make up the project. For those services
# with an entry point not designed to function as PID 1, we tell Docker to use
# its own init process.
services:
database:
build: ./database
environment:
POSTGRES_DB: "messagesdb"
POSTGRES_USER: "messagessvc"
POSTGRES_PASSWORD: "InSeCUr3Dem0PasS"
api:
build: ./api
environment:
DATABASE_URL: "postgres://messagessvc:InSeCUr3Dem0PasS@database:5432/messagesdb?sslmode=disable"
SERVER_BIND: ":8081"
CORS_ORIGIN: "http://localhost:8080"
volumes:
- code:/code
frontend:
build: ./frontend
init: true
environment:
OUTPUT_PATH: "/code/build"
volumes:
- code:/code
web:
build: ./web
environment:
SERVER_ROOT: "/code/build"
SERVER_BIND: ":8080"
volumes:
- code:/code

# Set up Mutagen synchronization and forwarding.
x-mutagen:
sync:
defaults:
ignore:
vcs: true
code:
alpha: "."
beta: "volume://code"
mode: "two-way-resolved"
ignore:
paths:
- "/*"
- "!/api/"
- "/api/*"
- "!/api/{entrypoint.sh,go.mod,go.sum,server.go}"
- "!/frontend/"
- "/frontend/*"
- "!/frontend/{entrypoint.sh,gulpfile.js,index.html,package*.json}"
- "!/web/"
- "/web/*"
- "!/web/{entrypoint.sh,go.mod,go.sum,server.go}"
forward:
api:
source: "tcp:localhost:8081"
destination: "network://default:tcp:api:8081"
web:
source: "tcp:localhost:8080"
destination: "network://default:tcp:web:8080"

+ 9
- 0
examples/compose/web-go/frontend/Dockerfile View File

@@ -0,0 +1,9 @@
# Use a minimal base image with Node.js support.
FROM node:alpine

# Copy in the entry point script and ensure that it's executable.
COPY ["entrypoint.sh", "/entrypoint.sh"]
RUN ["chmod", "+x", "/entrypoint.sh"]

# Set the entrypoint.
ENTRYPOINT ["/entrypoint.sh"]

examples/docker/web-go/frontend/entrypoint.sh → examples/compose/web-go/frontend/entrypoint.sh View File


examples/docker/web-go/frontend/gulpfile.js → examples/compose/web-go/frontend/gulpfile.js View File


examples/docker/web-go/frontend/index.html → examples/compose/web-go/frontend/index.html View File


examples/docker/web-go/frontend/package-lock.json → examples/compose/web-go/frontend/package-lock.json View File


examples/docker/web-go/frontend/package.json → examples/compose/web-go/frontend/package.json View File


examples/docker/web-go/web/Dockerfile → examples/compose/web-go/web/Dockerfile View File


examples/docker/web-go/web/entrypoint.sh → examples/compose/web-go/web/entrypoint.sh View File


+ 3
- 0
examples/compose/web-go/web/go.mod View File

@@ -0,0 +1,3 @@
module github.com/mutagen-io/mutagen/examples/compose/web-go/web

go 1.13

examples/docker/web-go/web/server.go → examples/compose/web-go/web/server.go View File


+ 0
- 5
examples/docker/README.md View File

@@ -1,5 +0,0 @@
# Docker examples

This directory contains example development environments that demonstrate
Mutagen's support for synchronizing code and forwarding network traffic into and
out of [Docker containers](https://mutagen.io/documentation/transports/docker).

+ 8
- 0
examples/projects/README.md View File

@@ -0,0 +1,8 @@
# Project examples

This directory contains examples that usage Mutagen's generic
[project](https://mutagen.io/documentation/orchestration/projects) mechanism to
automate the creation of Mutagen synchronization and forwarding sessions over
various transports. If you're using a Compose-based project, then you'll likely
be more interested in
[Mutagen's Docker Compose integration](https://mutagen.io/documentation/orchestration/compose).

+ 8
- 0
examples/projects/docker/README.md View File

@@ -0,0 +1,8 @@
# Docker project examples

This directory contains example development environments that use Mutagen's
[Docker container transport](https://mutagen.io/documentation/transports/docker)
for code synchronization and network forwarding. If you're using a Compose-based
project, you'll likely be more interested in
[Mutagen's Docker Compose integration](https://mutagen.io/documentation/orchestration/compose)
than using Mutagen projects and the Docker transport directly.

examples/docker/data-science/.gitignore → examples/projects/docker/data-science/.gitignore View File


examples/tunnels/data-science/Commits.ipynb → examples/projects/docker/data-science/Commits.ipynb View File

@@ -27,7 +27,7 @@
"outputs": [],
"source": [
"# Group and plot commits by day of the week\n",
"times.groupby(times.dt.weekday_name).count().plot(kind='bar')"
"times.groupby(times.dt.day_name()).count().plot(kind='bar')"
]
},
{
@@ -54,7 +54,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.4"
"version": "3.8.4"
}
},
"nbformat": 4,

examples/docker/data-science/README.md → examples/projects/docker/data-science/README.md View File

@@ -1,5 +1,12 @@
# Data science

**NOTE:** This example only exists to demonstrate Mutagen's generic
[project](https://mutagen.io/documentation/orchestration/projects) mechanism. In
real-world usage, you'll probably want to use
[Mutagen's support for Docker Compose](https://mutagen.io/documentation/orchestration/compose),
and you can find a corresponding example of this demo
[here](https://github.com/mutagen-io/mutagen/tree/master/examples/compose/data-science).

This directory contains an example [Jupyter](https://jupyter.org/)-based data
science environment designed to be run on a cloud-based container host (though
it can also be run locally, e.g. via

examples/tunnels/data-science/analysis/__init__.py → examples/projects/docker/data-science/analysis/__init__.py View File


examples/tunnels/data-science/analysis/example.py → examples/projects/docker/data-science/analysis/example.py View File


examples/docker/data-science/containers/jupyter/Dockerfile → examples/projects/docker/data-science/containers/jupyter/Dockerfile View File


examples/docker/data-science/docker-compose.yml → examples/projects/docker/data-science/docker-compose.yml View File

@@ -1,7 +1,7 @@
# Set the Docker Compose file format.
version: "3.7"

# Define the service that constitutes the remote environment.
# Define the service(s) that constitute the remote environment.
services:
jupyter:
build: ./containers/jupyter

examples/docker/data-science/mutagen.yml → examples/projects/docker/data-science/mutagen.yml View File

@@ -4,7 +4,7 @@ beforeCreate:

# Tear down the Docker Compose services after terminating sessions.
afterTerminate:
- docker-compose down --rmi=all --volumes
- docker-compose down --rmi=local --volumes

# Define common utility commands.
commands:

examples/docker/web-go/.gitignore → examples/projects/docker/web-go/.gitignore View File


examples/docker/web-go/README.md → examples/projects/docker/web-go/README.md View File

@@ -1,5 +1,12 @@
# Go-based web application

**NOTE:** This example only exists to demonstrate Mutagen's generic
[project](https://mutagen.io/documentation/orchestration/projects) mechanism. In
real-world usage, you'll probably want to use
[Mutagen's support for Docker Compose](https://mutagen.io/documentation/orchestration/compose),
and you can find a corresponding example of this demo
[here](https://github.com/mutagen-io/mutagen/tree/master/examples/compose/web-go).

This directory contains an example [Go](https://golang.org/)-based web
application designed to be developed and run on a cloud-based container host
(though it can also be run locally, e.g. via

+ 9
- 0
examples/projects/docker/web-go/api/Dockerfile View File

@@ -0,0 +1,9 @@
# Use a minimal base image with Go support.
FROM golang:alpine

# Copy in the entry point script and ensure that it's executable.
COPY ["entrypoint.sh", "/entrypoint.sh"]
RUN ["chmod", "+x", "/entrypoint.sh"]

# Set the entrypoint.
ENTRYPOINT ["/entrypoint.sh"]

+ 13
- 0
examples/projects/docker/web-go/api/entrypoint.sh View File

@@ -0,0 +1,13 @@
#!/bin/sh

# Switch to the API server source directory.
cd /code/api

# Build the API server.
echo "Building API server..."
go build -o api-server .

# Run the API server. We use exec to replace the shell process so that the
# server receives termination signals.
echo "Starting API server..."
exec ./api-server

examples/docker/web-go/api/go.mod → examples/projects/docker/web-go/api/go.mod View File


+ 8
- 0
examples/projects/docker/web-go/api/go.sum View File

@@ -0,0 +1,8 @@
github.com/gorilla/schema v1.1.0 h1:CamqUDOFUBqzrvxuz2vEwo8+SUdwsluFh7IlzJh30LY=
github.com/gorilla/schema v1.1.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU=
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=

+ 176
- 0
examples/projects/docker/web-go/api/server.go View File

@@ -0,0 +1,176 @@
package main

import (
"database/sql"
"encoding/json"
"errors"
"fmt"
"log"
"net/http"
"os"
"time"

_ "github.com/lib/pq"

"github.com/julienschmidt/httprouter"

"github.com/rs/cors"

"github.com/gorilla/schema"
)

const (
// queryInsertMessage is the query used to insert a message.
queryInsertMessage = "INSERT INTO messages(name, message) VALUES ($1, $2)"
// queryGetRecentMessages is the query used to read recent messages.
queryGetRecentMessages = "SELECT submitted_at, name, message FROM messages ORDER BY submitted_at DESC LIMIT 5"
)

// api is the API being served.
type api struct {
// database is the underlying database.
database *sql.DB
}

// messageForm represents a submitted message in a POST request.
type messageForm struct {
// Name is the name of the message submitter.
Name string `schema:"name"`
// Message is the message.
Message string `schema:"message"`
}

// insertMessage inserts a new message.
func (a *api) insertMessage(w http.ResponseWriter, r *http.Request) {
// Parse the request's form data.
if err := r.ParseForm(); err != nil {
http.Error(w, fmt.Sprintf("Error: unable to parse form data: %v", err), http.StatusBadRequest)
return
}

// Decode form data.
var form messageForm
decoder := schema.NewDecoder()
if err := decoder.Decode(&form, r.PostForm); err != nil {
http.Error(w, fmt.Sprintf("Error: unable to decode form data: %v", err), http.StatusBadRequest)
return
}

// Validate form data.
if form.Name == "" {
http.Error(w, "Error: empty submitter name", http.StatusBadRequest)
return
} else if form.Message == "" {
http.Error(w, "Error: empty message", http.StatusBadRequest)
return
}

// Insert the message to the database.
if _, err := a.database.ExecContext(r.Context(), queryInsertMessage, form.Name, form.Message); err != nil {
http.Error(w, fmt.Sprintf("Error: unable to record message: %v", err), http.StatusInternalServerError)
return
}

// Success.
w.WriteHeader(http.StatusCreated)
fmt.Fprint(w, "Message successfully recorded!")
}

// message represents a message returned in a JSON array response.
type message struct {
// Time is the time that the message was submitted.
Time time.Time `json:"time"`
// Name is the name of the message submitter.
Name string `json:"name"`
// Message is the message.
Message string `json:"message"`
}

// getRecentMessages returns a list of the most recent messages.
func (a *api) getRecentMessages(w http.ResponseWriter, r *http.Request) {
// Perform the query and defer closure of the iterator.
rows, err := a.database.QueryContext(r.Context(), queryGetRecentMessages)
if err != nil {
http.Error(w, fmt.Sprintf("Error: unable to perform database query: %v", err), http.StatusInternalServerError)
return
}
defer rows.Close()

// Create the results. We always start with an empty but non-nil slice
// to ensure the resulting JSON object is an empty array and not null if
// there are no results.
results := make([]message, 0)
for rows.Next() {
var row message
if err := rows.Scan(&row.Time, &row.Name, &row.Message); err != nil {
http.Error(w, fmt.Sprintf("Error: unable to load message: %v", err), http.StatusInternalServerError)
return
}
results = append(results, row)
}

// Check that iteration didn't fail.
if err := rows.Err(); err != nil {
http.Error(w, fmt.Sprintf("Error: Processing messages failed: %v", err), http.StatusInternalServerError)
return
}

// Set the content type to JSON. We have to do this manually since Go's
// built-in content sniffing doesn't know about JSON (golang/go#10630).
w.Header().Set("Content-Type", "application/json")

// Encode the results as JSON.
json.NewEncoder(w).Encode(results)
}

// serve is the primary entry point.
func serve() error {
// Grab and validate configuration parameters from the environment.
databaseURL := os.Getenv("DATABASE_URL")
if databaseURL == "" {
return errors.New("invalid or unspecified database URL")
}
bind := os.Getenv("SERVER_BIND")
if bind == "" {
return errors.New("invalid or unspecified server bind")
}
corsOrigin := os.Getenv("CORS_ORIGIN")
if corsOrigin == "" {
return errors.New("invalid or unspecified CORS origin")
}

// Connect to the database and defer its closure.
database, err := sql.Open("postgres", databaseURL)
if err != nil {
return fmt.Errorf("unable to connect to database: %w", err)
}
defer database.Close()

// Create an API instance.
api := &api{database: database}

// Create the request router.
router := httprouter.New()

// Set up handlers.
router.HandlerFunc(http.MethodPost, "/api/messages", api.insertMessage)
router.HandlerFunc(http.MethodGet, "/api/messages", api.getRecentMessages)

// Take the router as our root handler.
handler := http.Handler(router)

// Set up CORS middleware to allow cross-origin requests.
corsMiddleware := cors.New(cors.Options{
AllowedOrigins: []string{corsOrigin},
AllowedMethods: []string{http.MethodGet, http.MethodPost},
})
handler = corsMiddleware.Handler(handler)

// Serve files.
return http.ListenAndServe(bind, handler)
}

func main() {
// Run the server and log any error.
log.Fatal(serve())
}

examples/docker/web-go/database/Dockerfile → examples/projects/docker/web-go/database/Dockerfile View File


+ 11
- 0
examples/projects/docker/web-go/database/schema.sql View File

@@ -0,0 +1,11 @@
-- Create the messages table if it doesn't already exist.
CREATE TABLE IF NOT EXISTS messages (
-- id is the message id.
id BIGSERIAL PRIMARY KEY,
-- submitted_at is the submission time for the message.
submitted_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
-- name is the message submitter's name.
name VARCHAR(50) NOT NULL,
-- message is the message itself.
message VARCHAR(200) NOT NULL
);

examples/docker/web-go/docker-compose.yml → examples/projects/docker/web-go/docker-compose.yml View File


examples/docker/web-go/frontend/Dockerfile → examples/projects/docker/web-go/frontend/Dockerfile View File


+ 16
- 0
examples/projects/docker/web-go/frontend/entrypoint.sh View File

@@ -0,0 +1,16 @@
#!/bin/sh

# Switch to the frontend directory.
cd /code/frontend

# Perform a global gulp installation and an npm install operation if needed.
if [ ! -d node_modules ]; then
echo "Installing npm modules..."
npm install gulp-cli -g
npm install || exit 1
echo "npm install complete"
fi

# Run gulp. We use exec to replace the shell process so that gulp receives
# termination signals.
exec gulp

+ 36
- 0
examples/projects/docker/web-go/frontend/gulpfile.js View File

@@ -0,0 +1,36 @@
// Pull in dependencies.
const { src, dest, watch, parallel } = require('gulp');

// Compute the output path.
const output = process.env.OUTPUT_PATH || "./public";

// Create build rules.
function jqueryJS() {
return src("node_modules/jquery/dist/jquery.min.*")
.pipe(dest(output));
}
function popperJS() {
return src("node_modules/popper.js/dist/umd/popper.min.js*")
.pipe(dest(output));
}
function bootstrapJS() {
return src("node_modules/bootstrap/dist/js/bootstrap.min.js*")
.pipe(dest(output));
}
function bootstrapCSS() {
return src("node_modules/bootstrap/dist/css/bootstrap.min.css*")
.pipe(dest(output));
}
function html() {
return src("index.html")
.pipe(dest(output));
}
function watchHTML() {
// We use a watch on the directory rather than the HTML file because gulp's
// watching can only detect in-place file modifications, not atomic
// rename-based updates.
watch(".", html);
}

// Set the default target.
exports.default = parallel(jqueryJS, popperJS, bootstrapJS, bootstrapCSS, html, watchHTML);

+ 118
- 0
examples/projects/docker/web-go/frontend/index.html View File

@@ -0,0 +1,118 @@
<!DOCTYPE HTML>
<html lang="en" class="h-100">
<head>
<!-- Set page title -->
<title>Message demo</title>

<!-- Set encoding -->
<meta charset="utf-8">

<!-- Disable shrink-to-fit on mobile -->
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

<!-- JavaScript imports -->
<script src="jquery.min.js"></script>
<script src="popper.min.js"></script>
<script src="bootstrap.min.js"></script>

<!-- CSS imports -->
<link rel="stylesheet" href="bootstrap.min.css">
</head>
<body>
<div class="container">
<div class="row mt-5">
<div class="col offset-md-2 col-md-8 offset-lg-3 col-lg-6">
<h2 class="text-center mb-4">Super Advanced Message Board</h2>
<div class="card">
<div class="card-header">
<ul class="nav nav-tabs card-header-tabs">
<li class="nav-item">
<a id="write-tab-link" class="nav-link active" data-toggle="tab" href="#write-tab" role="tab" aria-controls="write-tab" aria-selected="true">Write message</a>
</li>
<li class="nav-item">
<a id="read-tab-link" class="nav-link" data-toggle="tab" href="#read-tab" role="tab" aria-controls="read-tab" aria-selected="false">Recent messages</a>
</li>
</ul>
</div>
<div class="card-body">
<div id="messages-tab-content" class="tab-content">
<div id="write-tab" class="tab-pane fade show active" role="tabpanel" aria-labelledby="write-tab-link">
<h5>Write a message</h5>
<form id="write-form" method="POST" action="http://localhost:8081/api/messages" accept-charset="UTF-8">
<div class="form-group">
<label for="name">Name</label>
<input id="name" name="name" type="text" class="form-control" required>
</div>
<div class="form-group">
<label for="message">Message</label>
<input id="message" name="message" type="text" class="form-control" required>
</div>
<input class="btn btn-primary" type="submit" value="Submit">
</form>
<script type="text/javascript">
$("#write-form").submit(function(e) {
// Grab the form object and its target URL.
var form = $(this);

// Prevent the default submission.
e.preventDefault();

// Submit the form using AJAX and
// display the result from the API.
$.ajax({
type: "POST",
url: form.attr("action"),
data: form.serialize(),
complete: function(result) {
alert(result.responseText);
}
});
});
</script>
</div>
<div id="read-tab" class="tab-pane fade" role="tabpanel" aria-labelledby="read-tab-link">
<h5>Recent messages</h5>
<ul id="messages">
</ul>
<script type="text/javascript">
// Load recent messages when we switch
// to the recent messages tab.
$('#read-tab-link').on('shown.bs.tab', function(e) {
// Grab the message list.
var messageList = $("#messages");

// Clear any previously rendered
// messages.
messageList.empty();

// Load new messages.
$.ajax({
type: "GET",
url: "http://localhost:8081/api/messages",
complete: function(result) {
if (result.status == 200) {
for (var i = 0; i < result.responseJSON.length; i++) {
var message = result.responseJSON[i];
messageList.append(
"<li class=\"mt-2\"><strong>" + message.name +
":</strong> " + message.message +
"<span class=\"text-muted\"><br />@ " +
message.time + "</span></li>"
);
}
} else {
alert(result.responseText);
}
}
});
});
</script>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

+ 3493
- 0
examples/projects/docker/web-go/frontend/package-lock.json
File diff suppressed because it is too large
View File


+ 11
- 0
examples/projects/docker/web-go/frontend/package.json View File

@@ -0,0 +1,11 @@
{
"private": true,
"dependencies": {
"bootstrap": "^4.4.1",
"jquery": "^3.5.0",
"popper.js": "^1.16.0"
},
"devDependencies": {
"gulp": "^4.0.2"
}
}

examples/docker/web-go/frontend/wait-for-initial-build.sh → examples/projects/docker/web-go/frontend/wait-for-initial-build.sh View File


examples/docker/web-go/mutagen.yml → examples/projects/docker/web-go/mutagen.yml View File

@@ -18,7 +18,7 @@ beforeResume:

# Tear down all services and remove the code volume after terminating sessions.
afterTerminate:
- docker-compose down --rmi=all --volumes
- docker-compose down --rmi=local --volumes

# Define common utility commands.
commands:

examples/docker/web-go/mutagen/Dockerfile → examples/projects/docker/web-go/mutagen/Dockerfile View File


+ 9
- 0
examples/projects/docker/web-go/web/Dockerfile View File

@@ -0,0 +1,9 @@
# Use a minimal base image with Go support.
FROM golang:alpine

# Copy in the entry point script and ensure that it's executable.
COPY ["entrypoint.sh", "/entrypoint.sh"]
RUN ["chmod", "+x", "/entrypoint.sh"]

# Set the entrypoint.
ENTRYPOINT ["/entrypoint.sh"]

+ 13
- 0
examples/projects/docker/web-go/web/entrypoint.sh View File

@@ -0,0 +1,13 @@
#!/bin/sh

# Switch to the web server source directory.
cd /code/web

# Build the web server.
echo "Building web server..."
go build -o web-server .

# Run the web server. We use exec to replace the shell process so that the
# server receives termination signals.
echo "Starting web server..."
exec ./web-server

examples/docker/web-go/web/go.mod → examples/projects/docker/web-go/web/go.mod View File


+ 26
- 0
examples/projects/docker/web-go/web/server.go View File

@@ -0,0 +1,26 @@
package main

import (
"errors"
"log"
"net/http"
"os"
)

func main() {
// Grab and validate configuration parameters from the environment.
root := os.Getenv("SERVER_ROOT")
if root == "" {
log.Fatal(errors.New("invalid or unspecified server root"))
}
bind := os.Getenv("SERVER_BIND")
if bind == "" {
log.Fatal(errors.New("invalid or unspecified server bind"))
}

// Set up file serving.
http.Handle("/", http.FileServer(http.Dir(root)))

// Serve files.
log.Fatal(http.ListenAndServe(bind, nil))
}

examples/tunnels/README.md → examples/projects/tunnels/README.md View File

@@ -1,4 +1,4 @@
# Tunnel examples
# Tunnel project examples

This directory contains example development environments that use Mutagen's
[tunnel transport](https://mutagen.io/documentation/transports/tunnels) for code

examples/tunnels/data-science/.gitignore → examples/projects/tunnels/data-science/.gitignore View File


examples/docker/data-science/Commits.ipynb → examples/projects/tunnels/data-science/Commits.ipynb View File

@@ -27,7 +27,7 @@
"outputs": [],
"source": [
"# Group and plot commits by day of the week\n",
"times.groupby(times.dt.weekday_name).count().plot(kind='bar')"
"times.groupby(times.dt.day_name()).count().plot(kind='bar')"
]
},
{
@@ -54,7 +54,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.4"
"version": "3.8.4"
}
},
"nbformat": 4,

examples/tunnels/data-science/README.md → examples/projects/tunnels/data-science/README.md View File


+ 0
- 0
examples/projects/tunnels/data-science/analysis/__init__.py View File


+ 22
- 0
examples/projects/tunnels/data-science/analysis/example.py View File

@@ -0,0 +1,22 @@
# Request imports
import requests

# Pandas imports
import pandas as pd


# _COMMITS_URL is the GitHub API endpoint with recent Mutagen commit data.
_COMMITS_URL = 'https://api.github.com/repos/mutagen-io/mutagen/commits'


def load_commit_times():
"""Loads recent Mutagen commit times as a Pandas Series.
"""
# Request commit data.
commits = requests.get(_COMMITS_URL).json()

# Extract commit times.
times = [c['commit']['author']['date'] for c in commits]

# Convert times to a Pandas series.
return pd.Series(times).astype('datetime64')

examples/tunnels/data-science/containers/jupyter/Dockerfile → examples/projects/tunnels/data-science/containers/jupyter/Dockerfile View File


examples/tunnels/data-science/containers/tunnel/Dockerfile → examples/projects/tunnels/data-science/containers/tunnel/Dockerfile View File


examples/tunnels/data-science/docker-compose.yml → examples/projects/tunnels/data-science/docker-compose.yml View File


examples/tunnels/data-science/mutagen.yml → examples/projects/tunnels/data-science/mutagen.yml View File

@@ -4,7 +4,7 @@ beforeCreate:

# Tear down the Docker Compose services after terminating sessions.
afterTerminate:
- docker-compose down --rmi=all --volumes
- docker-compose down --rmi=local --volumes

# Define common utility commands.
commands:

+ 0
- 14
mutagen.yml View File

@@ -1,14 +0,0 @@
forward:
godoc:
source: "tcp:localhost:6060"
destination: "docker://mutagen-development:tcp:localhost:6060"

sync:
development:
alpha: "."
beta: "docker://mutagen-development/root/mutagen"
mode: "one-way-safe"
ignore:
vcs: true
paths:
- "/build/"

+ 0
- 4
scripts/environments/README.md View File

@@ -1,4 +0,0 @@
# Environments

This directory contains Docker container definitions for Mutagen development
environments that can be run remotely.

+ 0
- 19
scripts/environments/development/Dockerfile View File

@@ -1,19 +0,0 @@
# Use a minimal base image with Go support.
FROM golang:alpine

# Install supplementary tools.
RUN ["apk", "add", "git", "openssh-client"]
RUN ["go", "get", "golang.org/x/tools/cmd/godoc"]

# Disable cgo.
ENV CGO_ENABLED=0

# Create a directory that will serve as our working directory, as well as a
# synchronization root.
RUN ["mkdir", "/root/mutagen"]

# Set the working directory.
WORKDIR /root/mutagen

# Use a null entry point to just idle.
ENTRYPOINT ["tail", "-f", "/dev/null"]

Loading…
Cancel
Save