Skip to content

Instantly share code, notes, and snippets.

@vito
Last active March 9, 2023 18:04
Show Gist options
  • Save vito/c9374b3452262fdb38baf85764ede9fe to your computer and use it in GitHub Desktop.
Save vito/c9374b3452262fdb38baf85764ede9fe to your computer and use it in GitHub Desktop.
2023-03-09 Dagger community call Services demos
package main
import (
"context"
"log"
"os"
"dagger.io/dagger"
)
func main() {
ctx := context.Background()
c, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stderr))
if err != nil {
log.Fatal(err)
}
defer c.Close()
web, worker := concourse(c.Pipeline("concourse"))
tests := c.Pipeline("smoke tests")
concSrc := tests.Git("https://github.com/concourse/concourse").
Branch("master").
Tree()
smoke := tests.
Container().
From("concourse/unit").
WithMountedDirectory("/src", concSrc).
WithWorkdir("/src").
WithExec([]string{"go", "install", "./fly"}).
WithWorkdir("/src/web/wats").
WithServiceBinding("web", web).
WithServiceBinding("worker", worker).
WithEnvVariable("ATC_URL", "http://web:8080").
WithExec([]string{"yarn", "install"}).
WithExec([]string{"yarn", "test", "-v", "--color", "test/smoke.js"})
_, err = smoke.ExitCode(ctx)
if err != nil {
log.Fatal(err)
}
}
func concourse(c *dagger.Client) (web *dagger.Container, worker *dagger.Container) {
db := c.Pipeline("db").
Container().
From("postgres").
WithEnvVariable("POSTGRES_DB", "concourse").
WithEnvVariable("POSTGRES_USER", "dev").
WithEnvVariable("POSTGRES_PASSWORD", "dev").
WithExposedPort(5432).
WithExec(nil)
conc := c.Container().
From("concourse/concourse")
keys := conc.Pipeline("keygen").
WithWorkdir("/keys").
WithExec([]string{"generate-key", "-t", "rsa", "-f", "./session_signing_key"}).
WithExec([]string{"generate-key", "-t", "ssh", "-f", "./tsa_host_key"}).
WithExec([]string{"generate-key", "-t", "ssh", "-f", "./worker_key"}).
Directory(".")
web = conc.Pipeline("web").
WithMountedFile("/concourse-keys/session_signing_key", keys.File("session_signing_key")).
WithMountedFile("/concourse-keys/authorized_worker_keys", keys.File("worker_key.pub")).
WithMountedFile("/concourse-keys/tsa_host_key", keys.File("tsa_host_key")).
WithEnvVariable("CONCOURSE_SESSION_SIGNING_KEY", "/concourse-keys/session_signing_key").
WithEnvVariable("CONCOURSE_TSA_AUTHORIZED_KEYS", "/concourse-keys/authorized_worker_keys").
WithEnvVariable("CONCOURSE_TSA_HOST_KEY", "/concourse-keys/tsa_host_key").
WithEnvVariable("CONCOURSE_POSTGRES_HOST", "db").
WithEnvVariable("CONCOURSE_POSTGRES_USER", "dev").
WithEnvVariable("CONCOURSE_POSTGRES_PASSWORD", "dev").
WithEnvVariable("CONCOURSE_POSTGRES_DATABASE", "concourse").
WithEnvVariable("CONCOURSE_EXTERNAL_URL", "http://web:8080").
WithEnvVariable("CONCOURSE_ADD_LOCAL_USER", "test:test,guest:guest").
WithEnvVariable("CONCOURSE_MAIN_TEAM_LOCAL_USER", "test").
WithEnvVariable("CONCOURSE_CLUSTER_NAME", "dev").
WithServiceBinding("db", db).
WithExposedPort(8080).
WithExec([]string{"web"})
worker = conc.Pipeline("worker").
WithMountedFile("/concourse-keys/worker_key", keys.File("worker_key")).
WithMountedFile("/concourse-keys/tsa_host_key.pub", keys.File("tsa_host_key.pub")).
WithEnvVariable("CONCOURSE_RUNTIME", "containerd").
WithEnvVariable("CONCOURSE_TSA_PUBLIC_KEY", "/concourse-keys/tsa_host_key.pub").
WithEnvVariable("CONCOURSE_TSA_WORKER_PRIVATE_KEY", "/concourse-keys/worker_key").
WithEnvVariable("CONCOURSE_TSA_HOST", "web:2222").
WithEnvVariable("CONCOURSE_BIND_IP", "0.0.0.0").
WithEnvVariable("CONCOURSE_BAGGAGECLAIM_BIND_IP", "0.0.0.0").
WithEnvVariable("CONCOURSE_BAGGAGECLAIM_DRIVER", "overlay").
WithEnvVariable("CONCOURSE_CONTAINERD_DNS_SERVER", "1.1.1.1,8.8.8.8").
WithServiceBinding("web", web).
WithExposedPort(7777).
WithExposedPort(7788).
WithMountedCache("/worker-state", c.CacheVolume("concourse-worker")).
WithExec([]string{"worker"}, dagger.ContainerWithExecOpts{
InsecureRootCapabilities: true,
})
return
}
package main
import (
"context"
"fmt"
"os"
"sort"
"strconv"
"dagger.io/dagger"
"github.com/compose-spec/compose-go/cli"
"github.com/compose-spec/compose-go/types"
"golang.org/x/sync/errgroup"
)
func main() {
ctx := context.Background()
opts, err := cli.NewProjectOptions(os.Args[1:],
cli.WithWorkingDirectory("."),
cli.WithDefaultConfigPath,
cli.WithOsEnv,
cli.WithConfigFileEnv,
)
if err != nil {
panic(err)
}
project, err := cli.ProjectFromOptions(opts)
if err != nil {
panic(err)
}
c, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stderr))
if err != nil {
panic(err)
}
defer c.Close()
eg, ctx := errgroup.WithContext(ctx)
for _, svc := range project.Services {
ctr, err := serviceContainer(c, project, svc)
if err != nil {
panic(err)
}
eg.Go(func() error {
return ctr.Run(ctx)
})
}
err = eg.Wait()
if err != nil {
panic(err)
}
}
func serviceContainer(c *dagger.Client, project *types.Project, svc types.ServiceConfig) (*dagger.Container, error) {
ctr := c.Pipeline(svc.Name).Container()
if svc.Image != "" {
ctr = ctr.From(svc.Image)
} else if svc.Build != nil {
args := []dagger.BuildArg{}
for name, val := range svc.Build.Args {
if val != nil {
args = append(args, dagger.BuildArg{
Name: name,
Value: *val,
})
}
}
ctr = ctr.Build(c.Host().Directory(svc.Build.Context), dagger.ContainerBuildOpts{
Dockerfile: svc.Build.Dockerfile,
BuildArgs: args,
Target: svc.Build.Target,
})
}
// sort env to ensure same container
type env struct{ name, value string }
envs := []env{}
for name, val := range svc.Environment {
if val != nil {
envs = append(envs, env{name, *val})
}
}
sort.Slice(envs, func(i, j int) bool {
return envs[i].name < envs[j].name
})
for _, env := range envs {
ctr = ctr.WithEnvVariable(env.name, env.value)
}
for _, port := range svc.Ports {
switch port.Mode {
case "ingress":
publishedPort, err := strconv.Atoi(port.Published)
if err != nil {
return nil, err
}
ctr = ctr.WithExposedPort(int(port.Target), dagger.ContainerWithExposedPortOpts{
// NB: totally made up non-final API, proof-of-concept only
Publish: publishedPort,
})
default:
return nil, fmt.Errorf("port mode %s not supported", port.Mode)
}
}
for _, expose := range svc.Expose {
port, err := strconv.Atoi(expose)
if err != nil {
return nil, err
}
ctr = ctr.WithExposedPort(port)
}
for _, vol := range svc.Volumes {
switch vol.Type {
case types.VolumeTypeBind:
ctr = ctr.WithMountedDirectory(vol.Target, c.Host().Directory(vol.Source))
case types.VolumeTypeVolume:
ctr = ctr.WithMountedCache(vol.Target, c.CacheVolume(vol.Source))
default:
return nil, fmt.Errorf("volume type %s not supported", vol.Type)
}
}
for depName := range svc.DependsOn {
cfg, err := project.GetService(depName)
if err != nil {
return nil, err
}
svcCtr, err := serviceContainer(c, project, cfg)
if err != nil {
return nil, err
}
ctr = ctr.WithServiceBinding(depName, svcCtr)
}
var opts dagger.ContainerWithExecOpts
if svc.Privileged {
opts.InsecureRootCapabilities = true
}
ctr = ctr.WithExec(svc.Command, opts)
return ctr, nil
}
package main
import (
"context"
"fmt"
"os"
"time"
"dagger.io/dagger"
)
func main() {
ctx := context.Background()
if len(os.Args) < 3 {
fatal(fmt.Errorf("usage: %s <dbname> <command ...>", os.Args[0]))
return
}
dbName := os.Args[1]
cmd := os.Args[2:]
client, err := dagger.Connect(ctx)
if err != nil {
fatal(err)
return
}
defer client.Close()
redis := client.Container().From("redis")
// a redis service with a persistent cache
redisSrv := redis.
WithExposedPort(6379).
WithMountedCache("/data", client.CacheVolume(dbName)).
WithWorkdir("/data").
WithExec(nil)
// a redis-cli container that runs against the service
redisCLI := redis.
WithServiceBinding("redis", redisSrv).
WithEntrypoint([]string{"redis-cli", "-h", "redis"})
// the user's command, which we avoid caching via an env var
redisCmd := redisCLI.
WithEnvVariable("AT", time.Now().String()).
WithExec(cmd)
// first run the command and immediately save
_, err = redisCmd.WithExec([]string{"save"}).ExitCode(ctx)
if err != nil {
fatal(err)
return
}
// then print the output of the (cached) command
out, err := redisCmd.Stdout(ctx)
if err != nil {
fatal(err)
return
}
fmt.Print(out)
}
func fatal(err error) {
fmt.Fprintf(os.Stderr, "error: %s\n", err)
os.Exit(1)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment