Skip to content

Commit

Permalink
add "d8s run" subcommand
Browse files Browse the repository at this point in the history
  • Loading branch information
damoon committed Jan 11, 2022
1 parent d376592 commit b6dd771
Show file tree
Hide file tree
Showing 7 changed files with 260 additions and 172 deletions.
15 changes: 6 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,16 @@

D8s is spoken dates.

## Example usage
## Example

`d8s up tilt up`

`d8s -h`
This will
1. deploys a `Docker in Docker` pod into kubernetes
2. creates a port-forward to `dind`
3. execute the given command (`tilt up`) and sets `DOCKER_HOST=tcp://127.0.0.1:[random_port]` and `DOCKER_BUILDKIT=1` as environment variables

## How it works

1. It deploys a `Docker in Docker` pod into kubernetes
2. It wait for `dind` to start and creates a port-forward
3. It executes the given command and sets `DOCKER_HOST=tcp://127.0.0.1:[random_port]` and `DOCKER_BUILDKIT=1` as environment variables

### Pod
### Docker in docker Pod

To keep `dind` healthy the pod runs docker and [Nurse](https://github.com/turbine-kreuzberg/dind-nurse).

Expand Down
104 changes: 74 additions & 30 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,43 +15,24 @@ var (
Usage: "Allowed Kubernetes context name.",
EnvVars: []string{"TILT_ALLOW_CONTEXT"},
}
)

func main() {
up := func(c *cli.Context) error {
ctx := c.Context
allowContext := c.String(allowContext.Name)
args := c.Args()
if !args.Present() {
return fmt.Errorf("command missing")
}
command := args.Slice()

return d8s.Up(ctx, allowContext, command)
}
down := func(c *cli.Context) error {
ctx := c.Context
allowContext := c.String(allowContext.Name)

return d8s.Down(ctx, allowContext)
}
version := func(c *cli.Context) error {
return Version()
}

app := &cli.App{
Name: "D8s (dates).",
Usage: "A wrapper for docker in docker doing port-forward.",
app = &cli.App{
Name: "D8s (dates).",
Usage: "A wrapper for docker in docker doing port-forward.",
Description: "Example: d8s up docker run hello-world",
Flags: []cli.Flag{
allowContext,
},
Action: up,
Commands: []*cli.Command{
{
Name: "up",
Usage: "Connect to docker in docker and set DOCKER_HOST for started process.",
Usage: "Deploy and connect to docker in docker and set DOCKER_HOST for started process.",
Action: up,
},
{
Name: "run",
Usage: "Connect to docker in docker and set DOCKER_HOST for started process.",
Action: run,
},
{
Name: "down",
Usage: "Deletes docker in docker deployment.",
Expand All @@ -60,13 +41,76 @@ func main() {
{
Name: "version",
Usage: "Show the version",
Action: version,
Action: Version,
},
},
}
)

func main() {
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}

func verifyContext(c *cli.Context) error {
allowContext := c.String(allowContext.Name)

allowed, err := d8s.ContextAllowed(allowContext)
if err != nil {
return fmt.Errorf("verify kubernetes context: %v", err)
}

if !allowed {
return fmt.Errorf("kubernetes context not allowed")
}

return nil
}

func up(c *cli.Context) error {
err := verifyContext(c)
if err != nil {
return err
}

ctx := c.Context
allowContext := c.String(allowContext.Name)
args := c.Args()
if !args.Present() {
return fmt.Errorf("command missing")
}
command := args.Slice()

return d8s.Up(ctx, allowContext, command)
}

func run(c *cli.Context) error {
err := verifyContext(c)
if err != nil {
return err
}

ctx := c.Context
allowContext := c.String(allowContext.Name)
args := c.Args()
if !args.Present() {
return fmt.Errorf("command missing")
}
command := args.Slice()

return d8s.Run(ctx, allowContext, command)
}

func down(c *cli.Context) error {
err := verifyContext(c)
if err != nil {
return err
}

ctx := c.Context
allowContext := c.String(allowContext.Name)

return d8s.Down(ctx, allowContext)
}
2 changes: 1 addition & 1 deletion pkg/down.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (

func Down(ctx context.Context, allowContext string) error {
// verify kubernetes context in use
allowed, err := contextAllowed(allowContext)
allowed, err := ContextAllowed(allowContext)
if err != nil {
return fmt.Errorf("verify kubernetes context: %v", err)
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/k8s-context.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ var (
manifest string
)

func contextAllowed(envVar string) (bool, error) {
func ContextAllowed(envVar string) (bool, error) {
contextName, err := kubectlContext()
if err != nil {
return false, err
Expand Down
173 changes: 173 additions & 0 deletions pkg/run.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
package d8s

import (
"context"
"fmt"
"log"
"net"
"os"
"os/exec"
"strconv"
"time"
)

func Run(ctx context.Context, allowContext string, command []string) error {
// verify kubernetes context in use
allowed, err := ContextAllowed(allowContext)
if err != nil {
return fmt.Errorf("verify kubernetes context: %v", err)
}
if !allowed {
return fmt.Errorf("kubernetes context not allowed")
}

// port forward
err = awaitDind()
if err != nil {
return fmt.Errorf("wait for dind to start: %v", err)
}

localPort, err := freePort()
if err != nil {
return fmt.Errorf("select free local port: %v", err)
}

go portForwardForever(ctx, localPort, dindPort)

err = awaitPortOpen(ctx, localPort)
if err != nil {
return fmt.Errorf("wait for port forward to start: %v", err)
}

// execute command
err = executeCommand(command, fmt.Sprintf("tcp://127.0.0.1:%d", localPort))
if err != nil {
return fmt.Errorf("command failed with %s", err)
}

return nil
}

func awaitDind() error {
cmd := exec.Command(
"kubectl",
"wait",
"--for=condition=available",
"--timeout=600s",
"deployment/dind",
)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
cmd.Env = os.Environ()

err := cmd.Run()
if err != nil {
return err
}

return nil
}

func freePort() (int, error) {
addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
if err != nil {
return 0, err
}

l, err := net.ListenTCP("tcp", addr)
if err != nil {
return 0, err
}
defer l.Close()

return l.Addr().(*net.TCPAddr).Port, nil
}

func portForwardForever(ctx context.Context, localPort, dindPort int) {
err := portForward(ctx, localPort, dindPort)
if err != nil {
log.Printf("port forward failed: %v", err)
}

for {
select {
case <-ctx.Done():
return
case <-time.After(100 * time.Millisecond):
err := portForward(ctx, localPort, dindPort)
if err != nil {
log.Printf("port forward failed: %v", err)
}
}
}
}

func portForward(ctx context.Context, localPort, dinnerPort int) error {
cmd := exec.Command(
"kubectl",
"port-forward",
"deployment/dind",
fmt.Sprintf("%d:%d", localPort, dinnerPort),
)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
cmd.Env = os.Environ()

err := cmd.Run()
if err != nil {
return err
}

return nil
}

func awaitPortOpen(ctx context.Context, localPort int) error {
for {
select {
case <-ctx.Done():
return fmt.Errorf("port did not open: %v", ctx.Err())
case <-time.After(1 * time.Second):
timeout, cancel := context.WithTimeout(ctx, time.Second)
defer cancel()

open := portOpen(timeout, "127.0.0.1", strconv.Itoa(localPort))
if open {
return nil
}
}
}
}

func portOpen(ctx context.Context, host string, port string) bool {

d := net.Dialer{Timeout: time.Second}

conn, err := d.DialContext(ctx, "tcp", net.JoinHostPort(host, port))
if err != nil {
return false
}
defer conn.Close()

return true
}

func executeCommand(command []string, dockerAddr string) error {
cmd := exec.Command(command[0], command[1:]...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
cmd.Env = os.Environ()
cmd.Env = append(cmd.Env, "DOCKER_HOST="+dockerAddr)
cmd.Env = append(cmd.Env, "DOCKER_BUILDKIT=1")

fmt.Printf("Execute command %s\n", cmd.String())

err := cmd.Run()
if err != nil {
return err
}

return nil
}
Loading

0 comments on commit b6dd771

Please sign in to comment.