Move prototype 69-dagger-archon to top-level
Signed-off-by: Solomon Hykes <sh.github.6811@hykes.org>
This commit is contained in:
commit
30f75da114
12
Dockerfile
Normal file
12
Dockerfile
Normal file
@ -0,0 +1,12 @@
|
||||
# syntax = docker/dockerfile-upstream:experimental@sha256:398a0a10f19875add7fe359a37f2f971c46746b064faf876776ae632a3472c37
|
||||
|
||||
FROM golang:1.14-alpine AS build
|
||||
WORKDIR /src
|
||||
RUN apk add --no-cache file
|
||||
RUN --mount=target=. --mount=target=/root/.cache,type=cache \
|
||||
CGO_ENABLED=0 go build -o /out/dagger ./cmd/dagger-frontend && file /out/dagger | grep "statically linked"
|
||||
|
||||
FROM scratch
|
||||
COPY --from=build /out/dagger /bin/dagger
|
||||
ENTRYPOINT ["/bin/dagger"]
|
||||
|
3
Makefile
Normal file
3
Makefile
Normal file
@ -0,0 +1,3 @@
|
||||
.PHONY: dagger
|
||||
dagger:
|
||||
go generate ./dagger && go build -o ./cmd/dagger/ ./cmd/dagger/
|
63
README.md
Normal file
63
README.md
Normal file
@ -0,0 +1,63 @@
|
||||
# Prototype 69-dagger-archon
|
||||
|
||||
This is an improved version of 64-blagger with the cuellb pattern, using
|
||||
everything we learned in 64,64,66.
|
||||
|
||||
This prototype marks a strong convergence between SH and AL on the high-level DX and architecture
|
||||
of the future OSS project. The next step is to get to a working POC implementation together,
|
||||
that can run a configuration written by SA, end-to-end, and make SA happy with the DX :)
|
||||
|
||||
## Points of convergence:
|
||||
|
||||
### 1. Cue DX: the cuellb pattern
|
||||
|
||||
The "cuellb" pattern is a promising DX for writing delivery workflows in Cue.
|
||||
On the one hand, the raw arrays are verbose, but they are easy to understand. And they
|
||||
can be abstracted away by the community as they wish, using the native features of Cue.
|
||||
TO quote AL: "It's easier to take something verbose and simple, and make it less verbose,
|
||||
than to take something concise and complex, and make it simple."
|
||||
|
||||
### 2. Integration of SAAS features: runtime+engine
|
||||
|
||||
The OSS project must combine features from 3 codebases: `bl-runtime`, `bl-api`, and `bl`.
|
||||
And it must combine them in a way that allows maximum code sharing between SAAS and OSS,
|
||||
so we don't have to do everything twice.
|
||||
|
||||
So in addition to defining a Cue DX and runtime, we also need to define how `dagger` will
|
||||
implement (or avoid implementing) saas features like settings, secrets, connectors, stacks,
|
||||
envs, slugs, job history, provider catalog, etc.
|
||||
|
||||
A promising approach to do this is to split the OSS project in 2 components:
|
||||
|
||||
- 1) the runtime which loads and executes cue confiurations, and
|
||||
- 2) the engine which orchestrates the runtime, and all its inputs and outputs: config repositories,
|
||||
settings, dependencies, previous state, execution history, etc.
|
||||
The engine is also responsible for end-user interaction.
|
||||
(see 64-blagger/README.md for more details on this architecture).
|
||||
|
||||
### 3. Possible performance gains
|
||||
|
||||
One consequence of the cuellb model + engine/runtime split, is that it becomes possible (we think)
|
||||
to compile a dagger job to a single LLB slug, and run it once.
|
||||
This has several benefits, including making the runtime simpler, and removing the hard dependency
|
||||
on a registry (which opens the door to new use cases in local development).
|
||||
|
||||
One possible benefit is performance. In theory, single-pass llb compilation is automatically faster
|
||||
because it removes the multiple round-trips between 1) cue eval 2) buildkit run 3) registry push 4) registry pull.
|
||||
The larger the configuration, the more round-trips, and the bigger the potential performance gain in dagger.
|
||||
|
||||
*[SH]* I have done lots of research work on this (prototypes 64-68) and the results are very encouraiging.
|
||||
I have strong conviction that the performance benefits are huge. But I only have bits and pieces of POC implementations,
|
||||
each focusing on a small aspect of the problem. The next step is to implement an end-to-end POC, and run a crude benchmark.
|
||||
|
||||
|
||||
### 4. Next step: working end-to-end prototype
|
||||
|
||||
We agree on a possible high-level architecture, DX and UX, built on several hypothesis:
|
||||
is cuellb really a good DX in practice? can dir refs really be eliminated?
|
||||
is single-pass llb jit really feasible, and if so does it solve our performance issues?
|
||||
how easy will it be to port Blocklayer to dagger? How much work to migrate beta users?
|
||||
|
||||
Now is the time to test these hypothesis with a working end-to-end implementation.
|
||||
There are many remaining questions but we have enough alignment to find the answers
|
||||
together.
|
14
cmd/dagger-frontend/main.go
Normal file
14
cmd/dagger-frontend/main.go
Normal file
@ -0,0 +1,14 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"dagger.cloud/go/dagger"
|
||||
"github.com/moby/buildkit/frontend/gateway/grpcclient"
|
||||
"github.com/moby/buildkit/util/appcontext"
|
||||
)
|
||||
|
||||
func main() {
|
||||
r := &dagger.Runtime{}
|
||||
if err := grpcclient.RunFromEnvironment(appcontext.Context(), r.BKFrontend); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
2
cmd/dagger/.gitignore
vendored
Normal file
2
cmd/dagger/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
dagger
|
||||
.dagger
|
50
cmd/dagger/cmd/compute.go
Normal file
50
cmd/dagger/cmd/compute.go
Normal file
@ -0,0 +1,50 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"dagger.cloud/go/dagger"
|
||||
"dagger.cloud/go/dagger/ui"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var computeCmd = &cobra.Command{
|
||||
Use: "compute CONFIG",
|
||||
Short: "Compute a configuration",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ctx := context.TODO()
|
||||
c, err := dagger.NewClient(ctx, "")
|
||||
if err != nil {
|
||||
ui.Fatal(err)
|
||||
}
|
||||
target := args[0]
|
||||
if target == "-" {
|
||||
ui.Info("Assembling config from stdin\n")
|
||||
// FIXME: include cue.mod/pkg from *somewhere* so stdin config can import
|
||||
if err := c.SetConfig(os.Stdin); err != nil {
|
||||
ui.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
ui.Info("Assembling config from %q\n", target)
|
||||
if err := c.SetConfig(target); err != nil {
|
||||
ui.Fatal(err)
|
||||
}
|
||||
}
|
||||
ui.Info("Running")
|
||||
output, err := c.Run(ctx, "compute")
|
||||
if err != nil {
|
||||
ui.Fatal(err)
|
||||
}
|
||||
ui.Info("Processing output")
|
||||
// output.Print(os.Stdout)
|
||||
fmt.Println(output.JSON())
|
||||
// FIXME: write computed values back to env dir
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
// computeCmd.Flags().StringP("catalog", "c", "", "Cue package catalog")
|
||||
}
|
35
cmd/dagger/cmd/create.go
Normal file
35
cmd/dagger/cmd/create.go
Normal file
@ -0,0 +1,35 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"dagger.cloud/go/dagger/ui"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var createCmd = &cobra.Command{
|
||||
Use: "create ENV BASE",
|
||||
Short: "Create an env",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
envname := args[0]
|
||||
base := args[1]
|
||||
envdir := ".dagger/env/" + envname
|
||||
if info, err := os.Stat(envdir); err == nil {
|
||||
if info.IsDir() {
|
||||
ui.Fatalf("env already exists: %s", envname)
|
||||
}
|
||||
}
|
||||
if err := os.MkdirAll(envdir, 0755); err != nil {
|
||||
ui.Fatal(err)
|
||||
}
|
||||
baseCue := fmt.Sprintf("package env\nimport base \"%s\"\nbase\n", base)
|
||||
err := ioutil.WriteFile(envdir+"/base.cue", []byte(baseCue), 0644)
|
||||
if err != nil {
|
||||
ui.Fatal(err)
|
||||
}
|
||||
ui.Info("created environment %q with base %q", envname, base)
|
||||
},
|
||||
}
|
37
cmd/dagger/cmd/root.go
Normal file
37
cmd/dagger/cmd/root.go
Normal file
@ -0,0 +1,37 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"dagger.cloud/go/dagger/ui"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "dagger",
|
||||
Short: "Open-source workflow engine",
|
||||
}
|
||||
|
||||
func init() {
|
||||
// --debug
|
||||
rootCmd.PersistentFlags().Bool("debug", false, "Enable debug mode")
|
||||
// --workspace
|
||||
rootCmd.PersistentFlags().StringP("workspace", "w", "", "Select workspace")
|
||||
rootCmd.AddCommand(
|
||||
// Create an env
|
||||
createCmd,
|
||||
computeCmd,
|
||||
// Change settings on an env
|
||||
// View or edit env serti
|
||||
// settingsCmd,
|
||||
// Query the state of an env
|
||||
// getCmd,
|
||||
// unsetCmd,
|
||||
// computeCmd,
|
||||
// listCmd,
|
||||
)
|
||||
}
|
||||
|
||||
func Execute() {
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
ui.Fatal(err)
|
||||
}
|
||||
}
|
9
cmd/dagger/main.go
Normal file
9
cmd/dagger/main.go
Normal file
@ -0,0 +1,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"dagger.cloud/go/cmd/dagger/cmd"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cmd.Execute()
|
||||
}
|
434
dagger/client.go
Normal file
434
dagger/client.go
Normal file
@ -0,0 +1,434 @@
|
||||
package dagger
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
// Cue
|
||||
"cuelang.org/go/cue"
|
||||
cueerrors "cuelang.org/go/cue/errors"
|
||||
cueformat "cuelang.org/go/cue/format"
|
||||
|
||||
// buildkit
|
||||
bk "github.com/moby/buildkit/client"
|
||||
_ "github.com/moby/buildkit/client/connhelper/dockercontainer"
|
||||
"github.com/moby/buildkit/client/llb"
|
||||
bkgw "github.com/moby/buildkit/frontend/gateway/client"
|
||||
|
||||
// docker output
|
||||
"github.com/containerd/console"
|
||||
"github.com/moby/buildkit/util/progress/progressui"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultBuildkitHost = "docker-container://buildkitd"
|
||||
|
||||
bkConfigKey = "context"
|
||||
bkInputKey = ":dagger:input:"
|
||||
bkActionKey = ":dagger:action:"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
c *bk.Client
|
||||
|
||||
inputs map[string]llb.State
|
||||
localdirs map[string]string
|
||||
|
||||
BKFrontend bkgw.BuildFunc
|
||||
}
|
||||
|
||||
func NewClient(ctx context.Context, host string) (*Client, error) {
|
||||
// buildkit client
|
||||
if host == "" {
|
||||
host = os.Getenv("BUILDKIT_HOST")
|
||||
}
|
||||
if host == "" {
|
||||
host = defaultBuildkitHost
|
||||
}
|
||||
c, err := bk.New(ctx, host)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "buildkit client")
|
||||
}
|
||||
return &Client{
|
||||
c: c,
|
||||
inputs: map[string]llb.State{},
|
||||
localdirs: map[string]string{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Client) ConnectInput(target string, input interface{}) error {
|
||||
var st llb.State
|
||||
switch in := input.(type) {
|
||||
case llb.State:
|
||||
st = in
|
||||
case string:
|
||||
// Generate a random local input label for security
|
||||
st = c.AddLocalDir(in, target)
|
||||
default:
|
||||
return fmt.Errorf("unsupported input type")
|
||||
}
|
||||
c.inputs[bkInputKey+target] = st
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) AddLocalDir(dir, label string, opts ...llb.LocalOption) llb.State {
|
||||
c.localdirs[label] = dir
|
||||
return llb.Local(label, opts...)
|
||||
}
|
||||
|
||||
// Set cue config for future calls.
|
||||
// input can be:
|
||||
// - llb.State: valid cue config directory
|
||||
// - io.Reader: valid cue source
|
||||
// - string: local path to valid cue file or directory
|
||||
// - func(llb.State)llb.Stte: modify existing state
|
||||
|
||||
func (c *Client) SetConfig(inputs ...interface{}) error {
|
||||
for _, input := range inputs {
|
||||
if err := c.setConfig(input); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) setConfig(input interface{}) error {
|
||||
var st llb.State
|
||||
switch in := input.(type) {
|
||||
case llb.State:
|
||||
st = in
|
||||
case func(llb.State) llb.State:
|
||||
// Modify previous state
|
||||
last, ok := c.inputs[bkConfigKey]
|
||||
if !ok {
|
||||
last = llb.Scratch()
|
||||
}
|
||||
st = in(last)
|
||||
case io.Reader:
|
||||
contents, err := ioutil.ReadAll(in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
st = llb.Scratch().File(llb.Mkfile(
|
||||
"config.cue",
|
||||
0660,
|
||||
contents,
|
||||
))
|
||||
// Interpret string as a path (dir or file)
|
||||
case string:
|
||||
info, err := os.Stat(in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.IsDir() {
|
||||
// FIXME: include pattern *.cue ooh yeah
|
||||
st = c.AddLocalDir(in, "config",
|
||||
//llb.IncludePatterns([]string{"*.cue", "cue.mod"})),
|
||||
llb.FollowPaths([]string{"*.cue", "cue.mod"}),
|
||||
)
|
||||
} else {
|
||||
f, err := os.Open(in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
return c.SetConfig(f)
|
||||
}
|
||||
}
|
||||
c.inputs[bkConfigKey] = st
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) Run(ctx context.Context, action string) (*Output, error) {
|
||||
// Spawn Build() goroutine
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
events := make(chan *bk.SolveStatus)
|
||||
outr, outw := io.Pipe()
|
||||
// Spawn build function
|
||||
eg.Go(c.buildfn(ctx, action, events, outw))
|
||||
// Spawn print function(s)
|
||||
dispCtx := context.TODO()
|
||||
var eventsdup chan *bk.SolveStatus
|
||||
if os.Getenv("DOCKER_OUTPUT") != "" {
|
||||
eventsdup = make(chan *bk.SolveStatus)
|
||||
eg.Go(c.dockerprintfn(dispCtx, eventsdup, os.Stderr))
|
||||
}
|
||||
eg.Go(c.printfn(dispCtx, events, eventsdup))
|
||||
// Retrieve output
|
||||
out := NewOutput()
|
||||
eg.Go(c.outputfn(ctx, outr, out))
|
||||
return out, eg.Wait()
|
||||
}
|
||||
|
||||
func (c *Client) buildfn(ctx context.Context, action string, ch chan *bk.SolveStatus, w io.WriteCloser) func() error {
|
||||
return func() error {
|
||||
defer debugf("buildfn complete")
|
||||
// Setup solve options
|
||||
opts := bk.SolveOpt{
|
||||
FrontendAttrs: map[string]string{
|
||||
bkActionKey: action,
|
||||
},
|
||||
LocalDirs: c.localdirs,
|
||||
FrontendInputs: c.inputs,
|
||||
// FIXME: catch output & return as cue value
|
||||
Exports: []bk.ExportEntry{
|
||||
{
|
||||
Type: bk.ExporterTar,
|
||||
Output: func(m map[string]string) (io.WriteCloser, error) {
|
||||
return w, nil
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
// Setup frontend
|
||||
bkFrontend := c.BKFrontend
|
||||
if bkFrontend == nil {
|
||||
r := &Runtime{}
|
||||
bkFrontend = r.BKFrontend
|
||||
}
|
||||
resp, err := c.c.Build(ctx, opts, "", bkFrontend, ch)
|
||||
if err != nil {
|
||||
// Close exporter pipe so that export processor can return
|
||||
w.Close()
|
||||
err = errors.New(bkCleanError(err.Error()))
|
||||
return errors.Wrap(err, "buildkit solve")
|
||||
}
|
||||
for k, v := range resp.ExporterResponse {
|
||||
// FIXME consume exporter response
|
||||
fmt.Printf("exporter response: %s=%s\n", k, v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Read tar export stream from buildkit Build(), and extract cue output
|
||||
func (c *Client) outputfn(ctx context.Context, r io.Reader, out *Output) func() error {
|
||||
return func() error {
|
||||
defer debugf("outputfn complete")
|
||||
tr := tar.NewReader(r)
|
||||
for {
|
||||
debugf("outputfn: reading next tar entry")
|
||||
h, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read tar stream")
|
||||
}
|
||||
if !strings.HasSuffix(h.Name, ".cue") {
|
||||
debugf("skipping non-cue file from exporter tar stream: %s", h.Name)
|
||||
continue
|
||||
}
|
||||
debugf("outputfn: compiling & merging %q", h.Name)
|
||||
// FIXME: only doing this for debug. you can pass tr directly as io.Reader.
|
||||
contents, err := ioutil.ReadAll(tr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
//if err := out.FillSource(h.Name, tr); err != nil {
|
||||
if err := out.FillSource(h.Name, contents); err != nil {
|
||||
debugf("error with %s: contents=\n------\n%s\n-----\n", h.Name, contents)
|
||||
return errors.Wrap(err, h.Name)
|
||||
}
|
||||
debugf("outputfn: DONE: compiling & merging %q", h.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Status of a node in the config tree being computed
|
||||
// Node may be a component, or a value within a component
|
||||
// (eg. a script or individual operation in a script)
|
||||
type Node struct {
|
||||
Path cue.Path
|
||||
*bk.Vertex
|
||||
}
|
||||
|
||||
func (n Node) ComponentPath() cue.Path {
|
||||
var parts []cue.Selector
|
||||
for _, sel := range n.Path.Selectors() {
|
||||
if strings.HasPrefix(sel.String(), "#") {
|
||||
break
|
||||
}
|
||||
parts = append(parts, sel)
|
||||
}
|
||||
return cue.MakePath(parts...)
|
||||
}
|
||||
|
||||
func (n Node) Logf(msg string, args ...interface{}) {
|
||||
componentPath := n.ComponentPath().String()
|
||||
args = append([]interface{}{componentPath}, args...)
|
||||
if msg != "" && !strings.HasSuffix(msg, "\n") {
|
||||
msg += "\n"
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "[%s] "+msg, args...)
|
||||
}
|
||||
|
||||
func (n Node) LogStream(nStream int, data []byte) {
|
||||
var stream string
|
||||
switch nStream {
|
||||
case 1:
|
||||
stream = "stdout"
|
||||
case 2:
|
||||
stream = "stderr"
|
||||
default:
|
||||
stream = fmt.Sprintf("%d", nStream)
|
||||
}
|
||||
// FIXME: use bufio reader?
|
||||
lines := strings.Split(string(data), "\n")
|
||||
for _, line := range lines {
|
||||
n.Logf("[%s] %s", stream, line)
|
||||
}
|
||||
}
|
||||
|
||||
func (n Node) LogError(errmsg string) {
|
||||
n.Logf("ERROR: %s", bkCleanError(errmsg))
|
||||
}
|
||||
|
||||
func (c *Client) printfn(ctx context.Context, ch, ch2 chan *bk.SolveStatus) func() error {
|
||||
return func() error {
|
||||
// Node status mapped to buildkit vertex digest
|
||||
nodesByDigest := map[string]*Node{}
|
||||
// Node status mapped to cue path
|
||||
nodesByPath := map[string]*Node{}
|
||||
|
||||
defer debugf("printfn complete")
|
||||
if ch2 != nil {
|
||||
defer close(ch2)
|
||||
}
|
||||
ticker := time.NewTicker(150 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-ticker.C:
|
||||
case status, ok := <-ch:
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
if ch2 != nil {
|
||||
ch2 <- status
|
||||
}
|
||||
debugf("status event: vertexes:%d statuses:%d logs:%d\n",
|
||||
len(status.Vertexes),
|
||||
len(status.Statuses),
|
||||
len(status.Logs),
|
||||
)
|
||||
for _, v := range status.Vertexes {
|
||||
p := cue.ParsePath(v.Name)
|
||||
if err := p.Err(); err != nil {
|
||||
debugf("ignoring buildkit vertex %q: not a valid cue path", p.String())
|
||||
continue
|
||||
}
|
||||
n := &Node{
|
||||
Path: p,
|
||||
Vertex: v,
|
||||
}
|
||||
nodesByPath[n.Path.String()] = n
|
||||
nodesByDigest[n.Digest.String()] = n
|
||||
if n.Error != "" {
|
||||
n.LogError(n.Error)
|
||||
}
|
||||
}
|
||||
for _, log := range status.Logs {
|
||||
if n, ok := nodesByDigest[log.Vertex.String()]; ok {
|
||||
n.LogStream(log.Stream, log.Data)
|
||||
}
|
||||
}
|
||||
// debugJSON(status)
|
||||
// FIXME: callbacks for extracting stream/result
|
||||
// see proto 67
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// A helper to remove noise from buildkit error messages.
|
||||
// FIXME: Obviously a cleaner solution would be nice.
|
||||
func bkCleanError(msg string) string {
|
||||
noise := []string{
|
||||
"executor failed running ",
|
||||
"buildkit-runc did not terminate successfully",
|
||||
"rpc error: code = Unknown desc =",
|
||||
"failed to solve: ",
|
||||
}
|
||||
for _, s := range noise {
|
||||
msg = strings.Replace(msg, s, "", -1)
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
func (c *Client) dockerprintfn(ctx context.Context, ch chan *bk.SolveStatus, out io.Writer) func() error {
|
||||
return func() error {
|
||||
defer debugf("dockerprintfn complete")
|
||||
var cons console.Console
|
||||
// FIXME: use smarter writer from blr
|
||||
return progressui.DisplaySolveStatus(ctx, "", cons, out, ch)
|
||||
}
|
||||
}
|
||||
|
||||
type Output struct {
|
||||
r *cue.Runtime
|
||||
inst *cue.Instance
|
||||
}
|
||||
|
||||
func NewOutput() *Output {
|
||||
r := &cue.Runtime{}
|
||||
inst, _ := r.Compile("", "")
|
||||
return &Output{
|
||||
r: r,
|
||||
inst: inst,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Output) Print(w io.Writer) error {
|
||||
v := o.Cue().Value().Eval()
|
||||
b, err := cueformat.Node(v.Syntax())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Write(b)
|
||||
return err
|
||||
}
|
||||
|
||||
func (o *Output) JSON() JSON {
|
||||
return cueToJSON(o.Cue().Value())
|
||||
}
|
||||
|
||||
func (o *Output) Cue() *cue.Instance {
|
||||
return o.inst
|
||||
}
|
||||
|
||||
func (o *Output) FillSource(filename string, x interface{}) error {
|
||||
inst, err := o.r.Compile(filename, x)
|
||||
if err != nil {
|
||||
return fmt.Errorf("compile %s: %s", filename, cueerrors.Details(err, nil))
|
||||
}
|
||||
if err := o.FillValue(inst.Value()); err != nil {
|
||||
return fmt.Errorf("merge %s: %s", filename, cueerrors.Details(err, nil))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Output) FillValue(x interface{}) error {
|
||||
inst, err := o.inst.Fill(x)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := inst.Value().Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
o.inst = inst
|
||||
return nil
|
||||
}
|
158
dagger/gen.go
Normal file
158
dagger/gen.go
Normal file
@ -0,0 +1,158 @@
|
||||
package dagger
|
||||
|
||||
// Generated by gen.sh. DO NOT EDIT.
|
||||
|
||||
var DaggerSpec = `
|
||||
package dagger
|
||||
|
||||
// A DAG is the basic unit of programming in dagger.
|
||||
// It is a special kind of program which runs as a pipeline of computing nodes running in parallel,
|
||||
// instead of a sequence of operations to be run by a single node.
|
||||
//
|
||||
// It is a powerful way to automate various parts of an application delivery workflow:
|
||||
// build, test, deploy, generate configuration, enforce policies, publish artifacts, etc.
|
||||
//
|
||||
// The DAG architecture has many benefits:
|
||||
// - Because DAGs are made of nodes executing in parallel, they are easy to scale.
|
||||
// - Because all inputs and outputs are snapshotted and content-addressed, DAGs
|
||||
// can easily be made repeatable, can be cached aggressively, and can be replayed
|
||||
// at will.
|
||||
// - Because nodes are executed by the same container engine as docker-build, DAGs
|
||||
// can be developed using any language or technology capable of running in a docker.
|
||||
// Dockerfiles and docker images are natively supported for maximum compatibility.
|
||||
//
|
||||
// - Because DAGs are programmed declaratively with a powerful configuration language,
|
||||
// they are much easier to test, debug and refactor than traditional programming languages.
|
||||
//
|
||||
// To execute a DAG, the dagger runtime JIT-compiles it to a low-level format called
|
||||
// llb, and executes it with buildkit.
|
||||
// Think of buildkit as a specialized VM for running compute graphs; and dagger as
|
||||
// a complete programming environment for that VM.
|
||||
//
|
||||
// The tradeoff for all those wonderful features is that a DAG architecture cannot be used
|
||||
// for all software: only software than can be run as a pipeline.
|
||||
//
|
||||
|
||||
// A dagger component is a configuration value augmented
|
||||
// by scripts defining how to compute it, present it to a user,
|
||||
// encrypt it, etc.
|
||||
|
||||
// FIXME: #Component will not match embedded scalars.
|
||||
// use Runtime.isComponent() for a reliable check
|
||||
#Component: {
|
||||
#dagger: #ComponentConfig
|
||||
...
|
||||
}
|
||||
|
||||
// The contents of a #dagger annotation
|
||||
#ComponentConfig: {
|
||||
input?: bool
|
||||
|
||||
// script to compute the value
|
||||
compute?: #Script
|
||||
|
||||
terminal?: {
|
||||
// Display a message when opening a terminal session
|
||||
greeting?: string
|
||||
command: [string]: #Script
|
||||
}
|
||||
// Configure how the component is incorporated to user settings.
|
||||
// Configure how the end-user can configure this component
|
||||
settings?: {
|
||||
// If not specified, scrape from comments
|
||||
title?: string
|
||||
description?: string
|
||||
// Disable user input, even if incomplete?
|
||||
hidden: true | *false
|
||||
ui: _ // insert here something which can be compiled to react-jsonschema-form
|
||||
// Show the cue default value to the user, as a default input value?
|
||||
showDefault: true | *false
|
||||
|
||||
// Insert information needed by:
|
||||
// 1) clients to encrypt
|
||||
// ie. web wizard, cli
|
||||
// 2) middleware to implement deicphering in the cuellb pipeline
|
||||
// eg. integration with clcoud KMS, Vault...
|
||||
//
|
||||
// 3) connectors to make sure secrets are preserved
|
||||
encrypt?: {
|
||||
pubkey: string
|
||||
cipher: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Any component can be referenced as a directory, since
|
||||
// every dagger script outputs a filesystem state (aka a directory)
|
||||
#Dir: #Component
|
||||
|
||||
#Script: [...#Op]
|
||||
|
||||
// One operation in a script
|
||||
#Op: #FetchContainer | #FetchGit | #Export | #Exec | #Load | #Copy
|
||||
|
||||
// Export a value from fs state to cue
|
||||
#Export: {
|
||||
do: "export"
|
||||
// Source path in the container
|
||||
source: string
|
||||
format: "json"|"yaml"|*"string"|"number"|"boolean"
|
||||
}
|
||||
|
||||
#Load: #LoadComponent| #LoadScript
|
||||
#LoadComponent: {
|
||||
do: "load"
|
||||
from: #Component
|
||||
}
|
||||
#LoadScript: {
|
||||
do: "load"
|
||||
from: #Script
|
||||
}
|
||||
|
||||
|
||||
#Exec: {
|
||||
do: "exec"
|
||||
args: [...string]
|
||||
env: [string]: string
|
||||
always: true | *false
|
||||
dir: string | *"/"
|
||||
mount?: [string]: #MountTmp | #MountCache | #MountComponent | #MountScript
|
||||
}
|
||||
|
||||
#MountTmp: "tmpfs"
|
||||
#MountCache: "cache"
|
||||
#MountComponent: {
|
||||
input: #Component
|
||||
path: string | *"/"
|
||||
}
|
||||
#MountScript: {
|
||||
input: #Script
|
||||
path: string | *"/"
|
||||
}
|
||||
|
||||
#FetchContainer: {
|
||||
do: "fetch-container"
|
||||
ref: string
|
||||
}
|
||||
|
||||
#FetchGit: {
|
||||
do: "fetch-git"
|
||||
remote: string
|
||||
ref: string
|
||||
}
|
||||
|
||||
#Copy: {
|
||||
do: "copy"
|
||||
from: #Script | #Component
|
||||
src: string | *"/"
|
||||
dest: string | *"/"
|
||||
}
|
||||
|
||||
|
||||
#TestScript: #Script & [
|
||||
{ do: "fetch-container", ref: "alpine:latest" },
|
||||
{ do: "exec", args: ["echo", "hello", "world" ] }
|
||||
]
|
||||
`
|
17
dagger/gen.sh
Executable file
17
dagger/gen.sh
Executable file
@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
cue eval spec.cue >/dev/null
|
||||
(
|
||||
cat <<'EOF'
|
||||
package dagger
|
||||
|
||||
// Generated by gen.sh. DO NOT EDIT.
|
||||
|
||||
var DaggerSpec = `
|
||||
EOF
|
||||
cat spec.cue
|
||||
cat <<'EOF'
|
||||
`
|
||||
EOF
|
||||
) > gen.go
|
326
dagger/job.go
Normal file
326
dagger/job.go
Normal file
@ -0,0 +1,326 @@
|
||||
package dagger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"cuelang.org/go/cue"
|
||||
cueerrors "cuelang.org/go/cue/errors"
|
||||
cueload "cuelang.org/go/cue/load"
|
||||
cueflow "cuelang.org/go/tools/flow"
|
||||
"github.com/moby/buildkit/client/llb"
|
||||
bkgw "github.com/moby/buildkit/frontend/gateway/client"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Solver interface {
|
||||
Solve(context.Context, llb.State) (bkgw.Reference, error)
|
||||
}
|
||||
|
||||
// 1 buildkit build = 1 job
|
||||
type Job struct {
|
||||
c bkgw.Client
|
||||
// needed for cue operations
|
||||
r *Runtime
|
||||
}
|
||||
|
||||
// Execute and wrap the result in a buildkit result
|
||||
func (job Job) BKExecute(ctx context.Context) (_r *bkgw.Result, _e error) {
|
||||
debugf("Executing bk frontend")
|
||||
// wrap errors to avoid crashing buildkit with cue error types (why??)
|
||||
defer func() {
|
||||
if _e != nil {
|
||||
_e = fmt.Errorf("%s", cueerrors.Details(_e, nil))
|
||||
debugf("execute returned an error. Wrapping...")
|
||||
}
|
||||
}()
|
||||
out, err := job.Execute(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// encode job output to buildkit result
|
||||
debugf("[runtime] job executed. Encoding output")
|
||||
// FIXME: we can't serialize result to standalone cue (with no imports).
|
||||
// So the client cannot safely compile output without access to the same cue.mod
|
||||
// as the runtime (which we don't want).
|
||||
// So for now we return the output as json, still parsed as cue on the client
|
||||
// to keep our options open. Once there is a "tree shake" primitive, we can
|
||||
// use that to return cue.
|
||||
//
|
||||
// Uncomment to return actual cue:
|
||||
// ----
|
||||
// outbytes, err := cueformat.Node(out.Value().Eval().Syntax())
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// ----
|
||||
outbytes := cueToJSON(out.Value())
|
||||
debugf("[runtime] output encoded. Writing output to exporter")
|
||||
outref, err := job.Solve(ctx,
|
||||
llb.Scratch().File(llb.Mkfile("computed.cue", 0600, outbytes)),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
debugf("[runtime] output written to exporter. returning to buildkit solver")
|
||||
res := bkgw.NewResult()
|
||||
res.SetRef(outref)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (job Job) Execute(ctx context.Context) (_i *cue.Instance, _e error) {
|
||||
debugf("[runtime] Execute()")
|
||||
defer func() { debugf("[runtime] DONE Execute(): err=%v", _e) }()
|
||||
state, err := job.Config(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Merge input information into the cue config
|
||||
inputs, err := job.Inputs(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for target := range inputs {
|
||||
// FIXME: cleaner code generation, missing cue.Value.FillPath
|
||||
state, err = job.r.fill(state, `#dagger: input: true`, target)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "connect input %q", target)
|
||||
}
|
||||
}
|
||||
action := job.Action()
|
||||
switch action {
|
||||
case "compute":
|
||||
return job.doCompute(ctx, state)
|
||||
case "export":
|
||||
return job.doExport(ctx, state)
|
||||
default:
|
||||
return job.doExport(ctx, state)
|
||||
}
|
||||
}
|
||||
|
||||
func (job Job) doExport(ctx context.Context, state *cue.Instance) (*cue.Instance, error) {
|
||||
return state, nil
|
||||
}
|
||||
|
||||
func (job Job) doCompute(ctx context.Context, state *cue.Instance) (*cue.Instance, error) {
|
||||
out, err := job.r.Compile("computed.cue", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Setup cueflow
|
||||
debugf("Setting up cueflow")
|
||||
flow := cueflow.New(
|
||||
&cueflow.Config{
|
||||
UpdateFunc: func(c *cueflow.Controller, t *cueflow.Task) error {
|
||||
debugf("cueflow event")
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
debugf("cueflow task %q: %s", t.Path().String(), t.State().String())
|
||||
if t.State() == cueflow.Terminated {
|
||||
debugf("cueflow task %q: filling result", t.Path().String())
|
||||
out, err = out.Fill(t.Value(), cuePathToStrings(t.Path())...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// FIXME: catch merge errors early with state
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
state,
|
||||
// Task match func
|
||||
func(v cue.Value) (cueflow.Runner, error) {
|
||||
// Is v a component (has #dagger) with a field 'compute' ?
|
||||
isComponent, err := job.r.isComponent(v, "compute")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !isComponent {
|
||||
return nil, nil
|
||||
}
|
||||
debugf("[%s] component detected\n", v.Path().String())
|
||||
// task runner func
|
||||
runner := cueflow.RunnerFunc(func(t *cueflow.Task) error {
|
||||
computeScript := t.Value().LookupPath(cue.ParsePath("#dagger.compute"))
|
||||
script, err := job.newScript(computeScript)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Run the script & fill the result into the task
|
||||
return script.Run(ctx, t)
|
||||
})
|
||||
return runner, nil
|
||||
},
|
||||
)
|
||||
debugf("Running cueflow")
|
||||
if err := flow.Run(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
debugf("Completed cueflow run. Merging result.")
|
||||
state, err = state.Fill(out)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
debugf("Result merged")
|
||||
// Return only the computed values
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (job Job) bk() bkgw.Client {
|
||||
return job.c
|
||||
}
|
||||
|
||||
func (job Job) Action() string {
|
||||
opts := job.bk().BuildOpts().Opts
|
||||
if action, ok := opts[bkActionKey]; ok {
|
||||
return action
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Load the cue config for this job
|
||||
// (received as llb input)
|
||||
func (job Job) Config(ctx context.Context) (*cue.Instance, error) {
|
||||
src := llb.Local(bkConfigKey,
|
||||
llb.SessionID(job.bk().BuildOpts().SessionID),
|
||||
llb.SharedKeyHint(bkConfigKey),
|
||||
llb.WithCustomName("load config"),
|
||||
)
|
||||
|
||||
bkInputs, err := job.bk().Inputs(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if st, ok := bkInputs[bkConfigKey]; ok {
|
||||
src = st
|
||||
}
|
||||
// job.runDebug(ctx, src, "ls", "-la", "/mnt")
|
||||
return job.LoadCue(ctx, src)
|
||||
}
|
||||
|
||||
func (job Job) runDebug(ctx context.Context, mnt llb.State, args ...string) error {
|
||||
opts := []llb.RunOption{
|
||||
llb.Args(args),
|
||||
llb.AddMount("/mnt", mnt),
|
||||
}
|
||||
cmd := llb.Image("alpine").Run(opts...).Root()
|
||||
ref, err := job.Solve(ctx, cmd)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "debug")
|
||||
}
|
||||
// force non-lazy solve
|
||||
if _, err := ref.ReadDir(ctx, bkgw.ReadDirRequest{Path: "/"}); err != nil {
|
||||
return errors.Wrap(err, "debug")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (job Job) Inputs(ctx context.Context) (map[string]llb.State, error) {
|
||||
bkInputs, err := job.bk().Inputs(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
inputs := map[string]llb.State{}
|
||||
for key, input := range bkInputs {
|
||||
if !strings.HasPrefix(key, bkInputKey) {
|
||||
continue
|
||||
}
|
||||
target := strings.Replace(key, bkInputKey, "", 1)
|
||||
targetPath := cue.ParsePath(target)
|
||||
if err := targetPath.Err(); err != nil {
|
||||
return nil, errors.Wrapf(err, "input target %q", target)
|
||||
}
|
||||
// FIXME: check that the path can be passed to Fill
|
||||
// (eg. only regular fields, no array indexes, no defs)
|
||||
// see cuePathToStrings
|
||||
inputs[target] = input
|
||||
}
|
||||
return inputs, nil
|
||||
}
|
||||
|
||||
// loadFiles recursively loads all .cue files from a buildkit gateway
|
||||
// FIXME: this is highly inefficient.
|
||||
func loadFiles(ctx context.Context, ref bkgw.Reference, p, overlayPrefix string, overlay map[string]cueload.Source) error {
|
||||
// FIXME: we cannot use `IncludePattern` here, otherwise sub directories
|
||||
// (e.g. "cue.mod") will be skipped.
|
||||
files, err := ref.ReadDir(ctx, bkgw.ReadDirRequest{
|
||||
Path: p,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, f := range files {
|
||||
fPath := path.Join(p, f.GetPath())
|
||||
if f.IsDir() {
|
||||
if err := loadFiles(ctx, ref, fPath, overlayPrefix, overlay); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if filepath.Ext(fPath) != ".cue" {
|
||||
continue
|
||||
}
|
||||
|
||||
contents, err := ref.ReadFile(ctx, bkgw.ReadRequest{
|
||||
Filename: fPath,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, f.GetPath())
|
||||
}
|
||||
overlay[path.Join(overlayPrefix, fPath)] = cueload.FromBytes(contents)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (job Job) LoadCue(ctx context.Context, st llb.State, args ...string) (*cue.Instance, error) {
|
||||
// The CUE overlay needs to be prefixed by a non-conflicting path with the
|
||||
// local filesystem, otherwise Cue will merge the Overlay with whatever Cue
|
||||
// files it finds locally.
|
||||
const overlayPrefix = "/config"
|
||||
|
||||
buildConfig := &cueload.Config{
|
||||
Dir: overlayPrefix,
|
||||
Overlay: map[string]cueload.Source{},
|
||||
}
|
||||
buildArgs := args
|
||||
|
||||
// Inject cue files from llb state into overlay
|
||||
ref, err := job.Solve(ctx, st)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := loadFiles(ctx, ref, ".", overlayPrefix, buildConfig.Overlay); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
instances := cueload.Instances(buildArgs, buildConfig)
|
||||
if len(instances) != 1 {
|
||||
return nil, errors.New("only one package is supported at a time")
|
||||
}
|
||||
inst, err := job.r.Build(instances[0])
|
||||
if err != nil {
|
||||
return nil, cueErr(err)
|
||||
}
|
||||
return inst, nil
|
||||
}
|
||||
|
||||
func (job Job) Solve(ctx context.Context, st llb.State) (bkgw.Reference, error) {
|
||||
// marshal llb
|
||||
def, err := st.Marshal(ctx, llb.LinuxAmd64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// call solve
|
||||
res, err := job.bk().Solve(ctx, bkgw.SolveRequest{Definition: def.ToPB()})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// always use single reference (ignore multiple outputs & metadata)
|
||||
return res.SingleRef()
|
||||
}
|
138
dagger/json.go
Normal file
138
dagger/json.go
Normal file
@ -0,0 +1,138 @@
|
||||
package dagger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"cuelang.org/go/cue"
|
||||
cuejson "cuelang.org/go/encoding/json"
|
||||
"github.com/KromDaniel/jonson"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type JSON []byte
|
||||
|
||||
func (s JSON) Get(path ...string) ([]byte, error) {
|
||||
if s == nil {
|
||||
s = []byte("{}")
|
||||
}
|
||||
var (
|
||||
root *jonson.JSON
|
||||
)
|
||||
root, err := jonson.Parse(s)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "parse root json")
|
||||
}
|
||||
pointer := root
|
||||
for _, key := range path {
|
||||
// FIXME: we can traverse maps but not arrays (need to handle int keys)
|
||||
pointer = pointer.At(key)
|
||||
}
|
||||
// FIXME: use indent function from stdlib
|
||||
return pointer.ToJSON()
|
||||
}
|
||||
|
||||
func (s JSON) Unset(path ...string) (JSON, error) {
|
||||
if s == nil {
|
||||
s = []byte("{}")
|
||||
}
|
||||
var (
|
||||
root *jonson.JSON
|
||||
)
|
||||
root, err := jonson.Parse(s)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unset: parse root json")
|
||||
}
|
||||
var (
|
||||
pointer = root
|
||||
pathDir []string
|
||||
)
|
||||
if len(path) > 0 {
|
||||
pathDir = path[:len(path)-1]
|
||||
}
|
||||
for _, key := range pathDir {
|
||||
pointer = pointer.At(key)
|
||||
}
|
||||
if len(path) == 0 {
|
||||
pointer.Set(nil)
|
||||
} else {
|
||||
key := path[len(path)-1]
|
||||
pointer.DeleteMapKey(key)
|
||||
}
|
||||
return root.ToJSON()
|
||||
}
|
||||
|
||||
func (s JSON) Set(valueJSON []byte, path ...string) (JSON, error) {
|
||||
if s == nil {
|
||||
s = []byte("{}")
|
||||
}
|
||||
var (
|
||||
root *jonson.JSON
|
||||
value *jonson.JSON
|
||||
)
|
||||
root, err := jonson.Parse(s)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "parse root json")
|
||||
}
|
||||
value, err = jonson.Parse(valueJSON)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "SetJSON: parse value json: |%s|", valueJSON)
|
||||
}
|
||||
var (
|
||||
pointer = root
|
||||
pathDir []string
|
||||
)
|
||||
if len(path) > 0 {
|
||||
pathDir = path[:len(path)-1]
|
||||
}
|
||||
for _, key := range pathDir {
|
||||
if !pointer.ObjectKeyExists(key) {
|
||||
pointer.MapSet(key, jonson.NewEmptyJSONMap())
|
||||
}
|
||||
pointer = pointer.At(key)
|
||||
}
|
||||
if len(path) == 0 {
|
||||
pointer.Set(value)
|
||||
} else {
|
||||
key := path[len(path)-1]
|
||||
pointer.MapSet(key, value)
|
||||
}
|
||||
return root.ToJSON()
|
||||
}
|
||||
|
||||
func (s JSON) Merge(layers ...JSON) (JSON, error) {
|
||||
r := new(cue.Runtime)
|
||||
var resultInst *cue.Instance
|
||||
for i, l := range append([]JSON{s}, layers...) {
|
||||
if l == nil {
|
||||
continue
|
||||
}
|
||||
filename := fmt.Sprintf("%d", i)
|
||||
inst, err := cuejson.Decode(r, filename, []byte(l))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resultInst == nil {
|
||||
resultInst = inst
|
||||
} else {
|
||||
resultInst, err = resultInst.Fill(inst.Value())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resultInst.Err != nil {
|
||||
return nil, resultInst.Err
|
||||
}
|
||||
}
|
||||
}
|
||||
b, err := resultInst.Value().MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return JSON(b), nil
|
||||
}
|
||||
|
||||
func (s JSON) String() string {
|
||||
if s == nil {
|
||||
return "{}"
|
||||
}
|
||||
return string(s)
|
||||
}
|
109
dagger/runtime.go
Normal file
109
dagger/runtime.go
Normal file
@ -0,0 +1,109 @@
|
||||
//go:generate sh gen.sh
|
||||
package dagger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"cuelang.org/go/cue"
|
||||
cueerrors "cuelang.org/go/cue/errors"
|
||||
bkgw "github.com/moby/buildkit/frontend/gateway/client"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Runtime struct {
|
||||
l sync.Mutex
|
||||
|
||||
cue.Runtime
|
||||
}
|
||||
|
||||
func (r *Runtime) Cue() *cue.Runtime {
|
||||
return &(r.Runtime)
|
||||
}
|
||||
|
||||
func (r *Runtime) fill(inst *cue.Instance, v interface{}, target string) (*cue.Instance, error) {
|
||||
targetPath := cue.ParsePath(target)
|
||||
if err := targetPath.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p := cuePathToStrings(targetPath)
|
||||
if src, ok := v.(string); ok {
|
||||
vinst, err := r.Compile(target, src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return inst.Fill(vinst.Value(), p...)
|
||||
}
|
||||
return inst.Fill(v, p...)
|
||||
}
|
||||
|
||||
// func (r Runtime) Run(...)
|
||||
// Buildkit run entrypoint
|
||||
func (r *Runtime) BKFrontend(ctx context.Context, c bkgw.Client) (*bkgw.Result, error) {
|
||||
return r.newJob(c).BKExecute(ctx)
|
||||
}
|
||||
|
||||
func (r *Runtime) newJob(c bkgw.Client) Job {
|
||||
return Job{
|
||||
r: r,
|
||||
c: c,
|
||||
}
|
||||
}
|
||||
|
||||
// Check whether a value is a valid component
|
||||
// FIXME: calling matchSpec("#Component") is not enough because
|
||||
// it does not match embedded scalars.
|
||||
func (r *Runtime) isComponent(v cue.Value, fields ...string) (bool, error) {
|
||||
cfg := v.LookupPath(cue.ParsePath("#dagger"))
|
||||
if cfg.Err() != nil {
|
||||
// No "#dagger" -> not a component
|
||||
return false, nil
|
||||
}
|
||||
for _, field := range fields {
|
||||
if cfg.Lookup(field).Err() != nil {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
if err := r.validateSpec(cfg, "#ComponentConfig"); err != nil {
|
||||
return true, errors.Wrap(err, "invalid #dagger")
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// eg. validateSpec(op, "#Op")
|
||||
// eg. validateSpec(dag, "#DAG")
|
||||
func (r *Runtime) validateSpec(v cue.Value, defpath string) (err error) {
|
||||
// Expand cue errors to get full details
|
||||
// FIXME: there is probably a cleaner way to do this.
|
||||
defer func() {
|
||||
if err != nil {
|
||||
err = fmt.Errorf("%s", cueerrors.Details(err, nil))
|
||||
}
|
||||
}()
|
||||
r.l.Lock()
|
||||
defer r.l.Unlock()
|
||||
|
||||
// FIXME cache spec instance
|
||||
spec, err := r.Compile("dagger.cue", DaggerSpec)
|
||||
if err != nil {
|
||||
panic("invalid spec")
|
||||
}
|
||||
def := spec.Value().LookupPath(cue.ParsePath(defpath))
|
||||
if err := def.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
v = v.Eval()
|
||||
if err := v.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
res := def.Unify(v)
|
||||
if err := res.Validate(cue.Final()); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Runtime) matchSpec(v cue.Value, def string) bool {
|
||||
return r.validateSpec(v, def) == nil
|
||||
}
|
326
dagger/script.go
Normal file
326
dagger/script.go
Normal file
@ -0,0 +1,326 @@
|
||||
package dagger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"cuelang.org/go/cue"
|
||||
"github.com/moby/buildkit/client/llb"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Script struct {
|
||||
v cue.Value
|
||||
job Job
|
||||
|
||||
// current state
|
||||
state *State
|
||||
}
|
||||
|
||||
func (job Job) newScript(v cue.Value) (*Script, error) {
|
||||
s := &Script{
|
||||
v: v,
|
||||
job: job,
|
||||
state: NewState(job),
|
||||
}
|
||||
if err := s.Validate(); err != nil {
|
||||
return nil, s.err(err, "invalid script")
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
type Action func(context.Context, cue.Value, Fillable) error
|
||||
|
||||
func (s *Script) Run(ctx context.Context, out Fillable) error {
|
||||
op, err := s.Cue().List()
|
||||
if err != nil {
|
||||
return s.err(err, "run")
|
||||
}
|
||||
i := 0
|
||||
for op.Next() {
|
||||
// If op is not concrete, interrupt execution without error.
|
||||
// This allows gradual resolution: compute what you can compute.. leave the rest incomplete.
|
||||
if !cueIsConcrete(op.Value()) {
|
||||
debugf("%s: non-concrete op. Leaving script unfinished", op.Value().Path().String())
|
||||
return nil
|
||||
}
|
||||
if err := s.Do(ctx, op.Value(), out); err != nil {
|
||||
return s.err(err, "run op %d", i+1)
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Script) Do(ctx context.Context, op cue.Value, out Fillable) error {
|
||||
// Skip no-ops without error (allows more flexible use of if())
|
||||
// FIXME: maybe not needed once a clear pattern is established for
|
||||
// how to use if() in a script?
|
||||
if cueIsEmptyStruct(op) {
|
||||
return nil
|
||||
}
|
||||
actions := map[string]Action{
|
||||
// "#Copy": s.copy,
|
||||
"#Exec": s.exec,
|
||||
"#Export": s.export,
|
||||
"#FetchContainer": s.fetchContainer,
|
||||
"#FetchGit": s.fetchGit,
|
||||
"#Load": s.load,
|
||||
"#Copy": s.copy,
|
||||
}
|
||||
for def, action := range actions {
|
||||
if s.matchSpec(op, def) {
|
||||
debugf("OP MATCH: %s: %s: %v", def, op.Path().String(), op)
|
||||
return action(ctx, op, out)
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("[%s] invalid operation: %s", s.v.Path().String(), cueToJSON(op))
|
||||
}
|
||||
|
||||
func (s *Script) copy(ctx context.Context, v cue.Value, out Fillable) error {
|
||||
// Decode copy options
|
||||
var op struct {
|
||||
Src string
|
||||
Dest string
|
||||
}
|
||||
if err := v.Decode(&op); err != nil {
|
||||
return err
|
||||
}
|
||||
from := v.Lookup("from")
|
||||
if isComponent, err := s.job.r.isComponent(from); err != nil {
|
||||
return err
|
||||
} else if isComponent {
|
||||
return s.copyComponent(ctx, from, op.Src, op.Dest)
|
||||
}
|
||||
if s.matchSpec(from, "#Script") {
|
||||
return s.copyScript(ctx, from, op.Src, op.Dest)
|
||||
}
|
||||
return fmt.Errorf("copy: invalid source")
|
||||
}
|
||||
|
||||
func (s *Script) copyScript(ctx context.Context, from cue.Value, src, dest string) error {
|
||||
// Load source script
|
||||
fromScript, err := s.job.newScript(from)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Execute source script
|
||||
if err := fromScript.Run(ctx, Discard()); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.State().Change(ctx, func(st llb.State) llb.State {
|
||||
return st.File(llb.Copy(
|
||||
fromScript.State().LLB(),
|
||||
src,
|
||||
dest,
|
||||
// FIXME: allow more configurable llb options
|
||||
// For now we define the following convenience presets:
|
||||
&llb.CopyInfo{
|
||||
CopyDirContentsOnly: true,
|
||||
CreateDestPath: true,
|
||||
AllowWildcard: true,
|
||||
},
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Script) copyComponent(ctx context.Context, from cue.Value, src, dest string) error {
|
||||
return s.copyScript(ctx, from.LookupPath(cue.ParsePath("#dagger.compute")), src, dest)
|
||||
}
|
||||
|
||||
func (s *Script) load(ctx context.Context, op cue.Value, out Fillable) error {
|
||||
from := op.Lookup("from")
|
||||
isComponent, err := s.job.r.isComponent(from)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if isComponent {
|
||||
debugf("LOAD: from is a component")
|
||||
return s.loadScript(ctx, from.LookupPath(cue.ParsePath("#dagger.compute")))
|
||||
}
|
||||
if s.matchSpec(from, "#Script") {
|
||||
return s.loadScript(ctx, from)
|
||||
}
|
||||
return fmt.Errorf("load: invalid source")
|
||||
}
|
||||
|
||||
func (s *Script) loadScript(ctx context.Context, v cue.Value) error {
|
||||
from, err := s.job.newScript(v)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "load")
|
||||
}
|
||||
// NOTE we discard cue outputs from running the loaded script.
|
||||
// This means we load the LLB state but NOT the cue exports.
|
||||
// In other words: cue exports are always private to their original location.
|
||||
if err := from.Run(ctx, Discard()); err != nil {
|
||||
return errors.Wrap(err, "load/execute")
|
||||
}
|
||||
// overwrite buildkit state from loaded from
|
||||
s.state = from.state
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Script) exec(ctx context.Context, v cue.Value, out Fillable) error {
|
||||
var opts []llb.RunOption
|
||||
var cmd struct {
|
||||
Args []string
|
||||
Env map[string]string
|
||||
Dir string
|
||||
Always bool
|
||||
}
|
||||
v.Decode(&cmd)
|
||||
// marker for status events
|
||||
opts = append(opts, llb.WithCustomName(v.Path().String()))
|
||||
// args
|
||||
opts = append(opts, llb.Args(cmd.Args))
|
||||
// dir
|
||||
dir := cmd.Dir
|
||||
if dir == "" {
|
||||
dir = "/"
|
||||
}
|
||||
// env
|
||||
for k, v := range cmd.Env {
|
||||
opts = append(opts, llb.AddEnv(k, v))
|
||||
}
|
||||
// always?
|
||||
if cmd.Always {
|
||||
cacheBuster, err := randomID(8)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
opts = append(opts, llb.AddEnv("DAGGER_CACHEBUSTER", cacheBuster))
|
||||
}
|
||||
// mounts
|
||||
mnt, _ := v.Lookup("mount").Fields()
|
||||
for mnt.Next() {
|
||||
opt, err := s.mount(ctx, mnt.Label(), mnt.Value())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
opts = append(opts, opt)
|
||||
}
|
||||
// --> Execute
|
||||
return s.State().Change(ctx, func(st llb.State) llb.State {
|
||||
return st.Run(opts...).Root()
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Script) mount(ctx context.Context, dest string, source cue.Value) (llb.RunOption, error) {
|
||||
if s.matchSpec(source, "#MountTmp") {
|
||||
return llb.AddMount(dest, llb.Scratch(), llb.Tmpfs()), nil
|
||||
}
|
||||
if s.matchSpec(source, "#MountCache") {
|
||||
// FIXME: cache mount
|
||||
return nil, fmt.Errorf("FIXME: cache mount not yet implemented")
|
||||
}
|
||||
if s.matchSpec(source, "#MountScript") {
|
||||
return s.mountScript(ctx, dest, source)
|
||||
}
|
||||
if s.matchSpec(source, "#MountComponent") {
|
||||
return s.mountComponent(ctx, dest, source)
|
||||
}
|
||||
return nil, fmt.Errorf("mount %s to %s: invalid source", source.Path().String(), dest)
|
||||
}
|
||||
|
||||
// mount when the input is a script (see mountComponent, mountTmpfs, mountCache)
|
||||
func (s *Script) mountScript(ctx context.Context, dest string, source cue.Value) (llb.RunOption, error) {
|
||||
script, err := s.job.newScript(source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// FIXME: this is where we re-run everything,
|
||||
// and rely on solver cache / dedup
|
||||
if err := script.Run(ctx, Discard()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return llb.AddMount(dest, script.State().LLB()), nil
|
||||
}
|
||||
|
||||
func (s *Script) mountComponent(ctx context.Context, dest string, source cue.Value) (llb.RunOption, error) {
|
||||
return s.mountScript(ctx, dest, source.LookupPath(cue.ParsePath("from.#dagger.compute")))
|
||||
}
|
||||
|
||||
func (s *Script) fetchContainer(ctx context.Context, v cue.Value, out Fillable) error {
|
||||
var op struct {
|
||||
Ref string
|
||||
}
|
||||
if err := v.Decode(&op); err != nil {
|
||||
return errors.Wrap(err, "decode fetch-container")
|
||||
}
|
||||
return s.State().Change(ctx, llb.Image(op.Ref))
|
||||
}
|
||||
|
||||
func (s *Script) fetchGit(ctx context.Context, v cue.Value, out Fillable) error {
|
||||
// See #FetchGit in spec.cue
|
||||
var op struct {
|
||||
Remote string
|
||||
Ref string
|
||||
}
|
||||
if err := v.Decode(&op); err != nil {
|
||||
return errors.Wrap(err, "decode fetch-git")
|
||||
}
|
||||
return s.State().Change(ctx, llb.Git(op.Remote, op.Ref))
|
||||
}
|
||||
|
||||
func (s *Script) export(ctx context.Context, v cue.Value, out Fillable) error {
|
||||
// See #Export in spec.cue
|
||||
var op struct {
|
||||
Source string
|
||||
// FIXME: target
|
||||
// Target string
|
||||
Format string
|
||||
}
|
||||
v.Decode(&op)
|
||||
b, err := s.State().ReadFile(ctx, op.Source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch op.Format {
|
||||
case "string":
|
||||
return out.Fill(string(b))
|
||||
case "json":
|
||||
var o interface{}
|
||||
if err := json.Unmarshal(b, &o); err != nil {
|
||||
return err
|
||||
}
|
||||
return out.Fill(o)
|
||||
default:
|
||||
return fmt.Errorf("unsupported export format: %q", op.Format)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Script) Cue() cue.Value {
|
||||
return s.v
|
||||
}
|
||||
|
||||
func (s *Script) Location() string {
|
||||
return s.Cue().Path().String()
|
||||
}
|
||||
|
||||
func (s *Script) err(e error, msg string, args ...interface{}) error {
|
||||
return errors.Wrapf(e, s.Location()+": "+msg, args...)
|
||||
}
|
||||
|
||||
func (s *Script) Validate() error {
|
||||
return s.job.r.validateSpec(s.Cue(), "#Script")
|
||||
}
|
||||
|
||||
func (s *Script) State() *State {
|
||||
return s.state
|
||||
}
|
||||
|
||||
func (s *Script) matchSpec(v cue.Value, def string) bool {
|
||||
// FIXME: we manually filter out empty structs to avoid false positives
|
||||
// This is necessary because Runtime.ValidateSpec has a bug
|
||||
// where an empty struct matches everything.
|
||||
// see https://github.com/cuelang/cue/issues/566#issuecomment-749735878
|
||||
// Once the bug is fixed, the manual check can be removed.
|
||||
if st, err := v.Struct(); err == nil {
|
||||
if st.Len() == 0 {
|
||||
debugf("FIXME: manually filtering out empty struct from spec match")
|
||||
return false
|
||||
}
|
||||
}
|
||||
return s.job.r.matchSpec(v, def)
|
||||
}
|
152
dagger/spec.cue
Normal file
152
dagger/spec.cue
Normal file
@ -0,0 +1,152 @@
|
||||
package dagger
|
||||
|
||||
// A DAG is the basic unit of programming in dagger.
|
||||
// It is a special kind of program which runs as a pipeline of computing nodes running in parallel,
|
||||
// instead of a sequence of operations to be run by a single node.
|
||||
//
|
||||
// It is a powerful way to automate various parts of an application delivery workflow:
|
||||
// build, test, deploy, generate configuration, enforce policies, publish artifacts, etc.
|
||||
//
|
||||
// The DAG architecture has many benefits:
|
||||
// - Because DAGs are made of nodes executing in parallel, they are easy to scale.
|
||||
// - Because all inputs and outputs are snapshotted and content-addressed, DAGs
|
||||
// can easily be made repeatable, can be cached aggressively, and can be replayed
|
||||
// at will.
|
||||
// - Because nodes are executed by the same container engine as docker-build, DAGs
|
||||
// can be developed using any language or technology capable of running in a docker.
|
||||
// Dockerfiles and docker images are natively supported for maximum compatibility.
|
||||
//
|
||||
// - Because DAGs are programmed declaratively with a powerful configuration language,
|
||||
// they are much easier to test, debug and refactor than traditional programming languages.
|
||||
//
|
||||
// To execute a DAG, the dagger runtime JIT-compiles it to a low-level format called
|
||||
// llb, and executes it with buildkit.
|
||||
// Think of buildkit as a specialized VM for running compute graphs; and dagger as
|
||||
// a complete programming environment for that VM.
|
||||
//
|
||||
// The tradeoff for all those wonderful features is that a DAG architecture cannot be used
|
||||
// for all software: only software than can be run as a pipeline.
|
||||
//
|
||||
|
||||
// A dagger component is a configuration value augmented
|
||||
// by scripts defining how to compute it, present it to a user,
|
||||
// encrypt it, etc.
|
||||
|
||||
// FIXME: #Component will not match embedded scalars.
|
||||
// use Runtime.isComponent() for a reliable check
|
||||
#Component: {
|
||||
#dagger: #ComponentConfig
|
||||
...
|
||||
}
|
||||
|
||||
// The contents of a #dagger annotation
|
||||
#ComponentConfig: {
|
||||
input?: bool
|
||||
|
||||
// script to compute the value
|
||||
compute?: #Script
|
||||
|
||||
terminal?: {
|
||||
// Display a message when opening a terminal session
|
||||
greeting?: string
|
||||
command: [string]: #Script
|
||||
}
|
||||
// Configure how the component is incorporated to user settings.
|
||||
// Configure how the end-user can configure this component
|
||||
settings?: {
|
||||
// If not specified, scrape from comments
|
||||
title?: string
|
||||
description?: string
|
||||
// Disable user input, even if incomplete?
|
||||
hidden: true | *false
|
||||
ui: _ // insert here something which can be compiled to react-jsonschema-form
|
||||
// Show the cue default value to the user, as a default input value?
|
||||
showDefault: true | *false
|
||||
|
||||
// Insert information needed by:
|
||||
// 1) clients to encrypt
|
||||
// ie. web wizard, cli
|
||||
// 2) middleware to implement deicphering in the cuellb pipeline
|
||||
// eg. integration with clcoud KMS, Vault...
|
||||
//
|
||||
// 3) connectors to make sure secrets are preserved
|
||||
encrypt?: {
|
||||
pubkey: string
|
||||
cipher: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Any component can be referenced as a directory, since
|
||||
// every dagger script outputs a filesystem state (aka a directory)
|
||||
#Dir: #Component
|
||||
|
||||
#Script: [...#Op]
|
||||
|
||||
// One operation in a script
|
||||
#Op: #FetchContainer | #FetchGit | #Export | #Exec | #Load | #Copy
|
||||
|
||||
// Export a value from fs state to cue
|
||||
#Export: {
|
||||
do: "export"
|
||||
// Source path in the container
|
||||
source: string
|
||||
format: "json"|"yaml"|*"string"|"number"|"boolean"
|
||||
}
|
||||
|
||||
#Load: #LoadComponent| #LoadScript
|
||||
#LoadComponent: {
|
||||
do: "load"
|
||||
from: #Component
|
||||
}
|
||||
#LoadScript: {
|
||||
do: "load"
|
||||
from: #Script
|
||||
}
|
||||
|
||||
|
||||
#Exec: {
|
||||
do: "exec"
|
||||
args: [...string]
|
||||
env: [string]: string
|
||||
always: true | *false
|
||||
dir: string | *"/"
|
||||
mount?: [string]: #MountTmp | #MountCache | #MountComponent | #MountScript
|
||||
}
|
||||
|
||||
#MountTmp: "tmpfs"
|
||||
#MountCache: "cache"
|
||||
#MountComponent: {
|
||||
input: #Component
|
||||
path: string | *"/"
|
||||
}
|
||||
#MountScript: {
|
||||
input: #Script
|
||||
path: string | *"/"
|
||||
}
|
||||
|
||||
#FetchContainer: {
|
||||
do: "fetch-container"
|
||||
ref: string
|
||||
}
|
||||
|
||||
#FetchGit: {
|
||||
do: "fetch-git"
|
||||
remote: string
|
||||
ref: string
|
||||
}
|
||||
|
||||
#Copy: {
|
||||
do: "copy"
|
||||
from: #Script | #Component
|
||||
src: string | *"/"
|
||||
dest: string | *"/"
|
||||
}
|
||||
|
||||
|
||||
#TestScript: #Script & [
|
||||
{ do: "fetch-container", ref: "alpine:latest" },
|
||||
{ do: "exec", args: ["echo", "hello", "world" ] }
|
||||
]
|
94
dagger/spec_test.go
Normal file
94
dagger/spec_test.go
Normal file
@ -0,0 +1,94 @@
|
||||
package dagger
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"cuelang.org/go/cue"
|
||||
)
|
||||
|
||||
func TestMatch(t *testing.T) {
|
||||
var data = []struct {
|
||||
Src string
|
||||
Def string
|
||||
}{
|
||||
{
|
||||
Src: `do: "exec", args: ["echo", "hello"]`,
|
||||
Def: "#Exec",
|
||||
},
|
||||
{
|
||||
Src: `do: "fetch-git", remote: "github.com/shykes/tests"`,
|
||||
Def: "#FetchGit",
|
||||
},
|
||||
{
|
||||
Src: `do: "load", from: [{do: "exec", args: ["echo", "hello"]}]`,
|
||||
Def: "#Load",
|
||||
},
|
||||
{
|
||||
Src: `do: "load", from: #dagger: compute: [{do: "exec", args: ["echo", "hello"]}]`,
|
||||
Def: "#Load",
|
||||
},
|
||||
// Make sure an empty op does NOT match
|
||||
{
|
||||
Src: ``,
|
||||
Def: "",
|
||||
},
|
||||
{
|
||||
Src: `do: "load"
|
||||
let package={bash:">3.0"}
|
||||
from: foo
|
||||
let foo={#dagger: compute: [
|
||||
{do: "fetch-container", ref: "alpine"},
|
||||
for pkg, info in package {
|
||||
if (info & true) != _|_ {
|
||||
do: "exec"
|
||||
args: ["echo", "hello", pkg]
|
||||
}
|
||||
if (info & string) != _|_ {
|
||||
do: "exec"
|
||||
args: ["echo", "hello", pkg, info]
|
||||
}
|
||||
}
|
||||
]}
|
||||
`,
|
||||
Def: "#Load",
|
||||
},
|
||||
}
|
||||
for _, d := range data {
|
||||
testMatch(t, d.Src, d.Def)
|
||||
}
|
||||
}
|
||||
|
||||
// Test an example op for false positives and negatives
|
||||
func testMatch(t *testing.T, src interface{}, def string) {
|
||||
r := &Runtime{}
|
||||
op := compile(t, r, src)
|
||||
if def != "" {
|
||||
if !r.matchSpec(op, def) {
|
||||
t.Errorf("false negative: %s: %q", def, src)
|
||||
}
|
||||
}
|
||||
for _, cmpDef := range []string{
|
||||
"#Exec",
|
||||
"#FetchGit",
|
||||
"#FetchContainer",
|
||||
"#Export",
|
||||
"#Load",
|
||||
"#Copy",
|
||||
} {
|
||||
if cmpDef == def {
|
||||
continue
|
||||
}
|
||||
if r.matchSpec(op, cmpDef) {
|
||||
t.Errorf("false positive: %s: %q", cmpDef, src)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func compile(t *testing.T, r *Runtime, src interface{}) cue.Value {
|
||||
inst, err := r.Compile("", src)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return inst.Value()
|
||||
}
|
53
dagger/state.go
Normal file
53
dagger/state.go
Normal file
@ -0,0 +1,53 @@
|
||||
package dagger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
"github.com/moby/buildkit/client/llb"
|
||||
bkgw "github.com/moby/buildkit/frontend/gateway/client"
|
||||
)
|
||||
|
||||
type State struct {
|
||||
// Before last solve
|
||||
input llb.State
|
||||
// After last solve
|
||||
output bkgw.Reference
|
||||
// How to produce the output
|
||||
s Solver
|
||||
}
|
||||
|
||||
func NewState(s Solver) *State {
|
||||
return &State{
|
||||
input: llb.Scratch(),
|
||||
s: s,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *State) ReadFile(ctx context.Context, filename string) ([]byte, error) {
|
||||
if s.output == nil {
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
return s.output.ReadFile(ctx, bkgw.ReadRequest{Filename: filename})
|
||||
}
|
||||
|
||||
func (s *State) Change(ctx context.Context, op interface{}) error {
|
||||
input := s.input
|
||||
switch OP := op.(type) {
|
||||
case llb.State:
|
||||
input = OP
|
||||
case func(llb.State) llb.State:
|
||||
input = OP(input)
|
||||
}
|
||||
output, err := s.s.Solve(ctx, input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.input = input
|
||||
s.output = output
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *State) LLB() llb.State {
|
||||
return s.input
|
||||
}
|
26
dagger/ui/ui.go
Normal file
26
dagger/ui/ui.go
Normal file
@ -0,0 +1,26 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func Fatalf(msg string, args ...interface{}) {
|
||||
if !strings.HasSuffix(msg, "\n") {
|
||||
msg = msg + "\n"
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, msg, args...)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func Fatal(msg interface{}) {
|
||||
Fatalf("%s\n", msg)
|
||||
}
|
||||
|
||||
func Info(msg string, args ...interface{}) {
|
||||
if !strings.HasSuffix(msg, "\n") {
|
||||
msg = msg + "\n"
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "[info] "+msg, args...)
|
||||
}
|
288
dagger/utils.go
Normal file
288
dagger/utils.go
Normal file
@ -0,0 +1,288 @@
|
||||
package dagger
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"cuelang.org/go/cue"
|
||||
cueAst "cuelang.org/go/cue/ast"
|
||||
cueerrors "cuelang.org/go/cue/errors"
|
||||
cueformat "cuelang.org/go/cue/format"
|
||||
cueload "cuelang.org/go/cue/load"
|
||||
cueParser "cuelang.org/go/cue/parser"
|
||||
"github.com/moby/buildkit/client/llb"
|
||||
"github.com/moby/buildkit/client/llb/imagemetaresolver"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// A nil equivalent for cue.Value (when returning errors)
|
||||
var qnil cue.Value
|
||||
|
||||
type Fillable interface {
|
||||
Fill(interface{}) error
|
||||
}
|
||||
|
||||
func Discard() Fillable {
|
||||
return discard{}
|
||||
}
|
||||
|
||||
type discard struct{}
|
||||
|
||||
func (d discard) Fill(x interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type fillableValue struct {
|
||||
root cue.Value
|
||||
}
|
||||
|
||||
func cuePrint(v cue.Value) (string, error) {
|
||||
b, err := cueformat.Node(v.Syntax())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
func (f *fillableValue) Fill(v interface{}) error {
|
||||
root2 := f.root.Fill(v)
|
||||
if err := root2.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
f.root = root2
|
||||
return nil
|
||||
}
|
||||
|
||||
func cueScratch(r *cue.Runtime) Fillable {
|
||||
f := &fillableValue{}
|
||||
if inst, err := r.Compile("", ""); err == nil {
|
||||
f.root = inst.Value()
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func cueErr(err error) error {
|
||||
return fmt.Errorf("%s", cueerrors.Details(err, &cueerrors.Config{}))
|
||||
}
|
||||
|
||||
func cueDecodeArray(a cue.Value, idx int, out interface{}) {
|
||||
a.LookupPath(cue.MakePath(cue.Index(idx))).Decode(out)
|
||||
}
|
||||
|
||||
func cueToJSON(v cue.Value) JSON {
|
||||
var out JSON
|
||||
v.Walk(
|
||||
func(v cue.Value) bool {
|
||||
b, err := v.MarshalJSON()
|
||||
if err == nil {
|
||||
newOut, err := out.Set(b, cuePathToStrings(v.Path())...)
|
||||
if err == nil {
|
||||
out = newOut
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
nil,
|
||||
)
|
||||
return out
|
||||
}
|
||||
|
||||
// Build a cue instance from a directory and args
|
||||
func cueBuild(r *cue.Runtime, cueRoot string, buildArgs ...string) (*cue.Instance, error) {
|
||||
var err error
|
||||
cueRoot, err = filepath.Abs(cueRoot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buildConfig := &cueload.Config{
|
||||
ModuleRoot: cueRoot,
|
||||
Dir: cueRoot,
|
||||
}
|
||||
instances := cueload.Instances(buildArgs, buildConfig)
|
||||
if len(instances) != 1 {
|
||||
return nil, errors.New("only one package is supported at a time")
|
||||
}
|
||||
return r.Build(instances[0])
|
||||
}
|
||||
|
||||
func debugJSON(v interface{}) {
|
||||
if os.Getenv("DEBUG") != "" {
|
||||
e := json.NewEncoder(os.Stderr)
|
||||
e.SetIndent("", " ")
|
||||
e.Encode(v)
|
||||
}
|
||||
}
|
||||
|
||||
func debugf(msg string, args ...interface{}) {
|
||||
if !strings.HasSuffix(msg, "\n") {
|
||||
msg = msg + "\n"
|
||||
}
|
||||
if os.Getenv("DEBUG") != "" {
|
||||
fmt.Fprintf(os.Stderr, msg, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func debug(msg string) {
|
||||
if os.Getenv("DEBUG") != "" {
|
||||
fmt.Fprintln(os.Stderr, msg)
|
||||
}
|
||||
}
|
||||
|
||||
func randomID(size int) (string, error) {
|
||||
b := make([]byte, size)
|
||||
_, err := rand.Read(b)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("%x", b), nil
|
||||
}
|
||||
|
||||
func cueWrapExpr(p string, v cueAst.Expr) (cueAst.Expr, error) {
|
||||
pExpr, err := cueParser.ParseExpr("path", p)
|
||||
if err != nil {
|
||||
return v, err
|
||||
}
|
||||
out := v
|
||||
cursor := pExpr
|
||||
walk:
|
||||
for {
|
||||
switch c := cursor.(type) {
|
||||
case *cueAst.SelectorExpr:
|
||||
out = cueAst.NewStruct(
|
||||
&cueAst.Field{
|
||||
Value: out,
|
||||
Label: c.Sel,
|
||||
},
|
||||
)
|
||||
cursor = c.X
|
||||
case *cueAst.Ident:
|
||||
out = cueAst.NewStruct(
|
||||
&cueAst.Field{
|
||||
Value: out,
|
||||
Label: c,
|
||||
},
|
||||
)
|
||||
break walk
|
||||
default:
|
||||
return out, fmt.Errorf("invalid path expression: %q", p)
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func cueWrapFile(p string, v interface{}) (*cueAst.File, error) {
|
||||
f, err := cueParser.ParseFile("value", v)
|
||||
if err != nil {
|
||||
return f, err
|
||||
}
|
||||
decls := make([]cueAst.Decl, 0, len(f.Decls))
|
||||
for _, decl := range f.Decls {
|
||||
switch d := decl.(type) {
|
||||
case *cueAst.Field:
|
||||
wrappedExpr, err := cueWrapExpr(p, cueAst.NewStruct(d))
|
||||
if err != nil {
|
||||
return f, err
|
||||
}
|
||||
decls = append(decls, &cueAst.EmbedDecl{Expr: wrappedExpr})
|
||||
case *cueAst.EmbedDecl:
|
||||
wrappedExpr, err := cueWrapExpr(p, d.Expr)
|
||||
if err != nil {
|
||||
return f, err
|
||||
}
|
||||
d.Expr = wrappedExpr
|
||||
decls = append(decls, d)
|
||||
case *cueAst.ImportDecl:
|
||||
decls = append(decls, decl)
|
||||
default:
|
||||
fmt.Printf("skipping unsupported decl type %#v\n\n", decl)
|
||||
continue
|
||||
}
|
||||
}
|
||||
f.Decls = decls
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func cueIsEmptyStruct(v cue.Value) bool {
|
||||
if st, err := v.Struct(); err == nil {
|
||||
if st.Len() == 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Return false if v is not concrete, or contains any
|
||||
// non-concrete fields or items.
|
||||
func cueIsConcrete(v cue.Value) bool {
|
||||
// FIXME: use Value.Walk?
|
||||
if it, err := v.Fields(); err == nil {
|
||||
for it.Next() {
|
||||
if !cueIsConcrete(it.Value()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
if it, err := v.List(); err == nil {
|
||||
for it.Next() {
|
||||
if !cueIsConcrete(it.Value()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
dv, _ := v.Default()
|
||||
return v.IsConcrete() || dv.IsConcrete()
|
||||
}
|
||||
|
||||
// LLB Helper to pull a Docker image + all its metadata
|
||||
func llbDockerImage(ref string) llb.State {
|
||||
return llb.Image(
|
||||
ref,
|
||||
llb.WithMetaResolver(imagemetaresolver.Default()),
|
||||
)
|
||||
}
|
||||
|
||||
func cueStringsToCuePath(parts ...string) cue.Path {
|
||||
selectors := make([]cue.Selector, 0, len(parts))
|
||||
for _, part := range parts {
|
||||
selectors = append(selectors, cue.Str(part))
|
||||
}
|
||||
return cue.MakePath(selectors...)
|
||||
}
|
||||
|
||||
func cuePathToStrings(p cue.Path) []string {
|
||||
selectors := p.Selectors()
|
||||
out := make([]string, len(selectors))
|
||||
for i, sel := range selectors {
|
||||
out[i] = sel.String()
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Validate a cue path, and return a canonical version
|
||||
func cueCleanPath(p string) (string, error) {
|
||||
cp := cue.ParsePath(p)
|
||||
return cp.String(), cp.Err()
|
||||
}
|
||||
|
||||
func autoMarshal(value interface{}) ([]byte, error) {
|
||||
switch v := value.(type) {
|
||||
case []byte:
|
||||
return v, nil
|
||||
case string:
|
||||
return []byte(v), nil
|
||||
case io.Reader:
|
||||
return ioutil.ReadAll(v)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported marshal inoput type")
|
||||
}
|
||||
return []byte(fmt.Sprintf("%v", value)), nil
|
||||
}
|
23
examples/acme-platform/README.md
Normal file
23
examples/acme-platform/README.md
Normal file
@ -0,0 +1,23 @@
|
||||
# ACME platform
|
||||
|
||||
Welcome to the acme-platform repository. It contains everything you need to start developing and shipping improvements
|
||||
to the ACME Clothing Store.
|
||||
|
||||
For information or support, contact the ACME Platform team: platform@acme.infralabs.io
|
||||
|
||||
# Things you can do with ACME platform
|
||||
|
||||
|
||||
## Pre-merge or post-merge QA
|
||||
|
||||
## Create a personal dev environment
|
||||
|
||||
## Cross-team integration testing
|
||||
|
||||
## Sales demos
|
||||
|
||||
## End-to-end product reviews
|
||||
|
||||
## Testing infrastructure changes
|
||||
|
||||
## Deploying to production (REQUIRES SPECIAL PRIVILEGES, talk to your SRE)
|
38
examples/acme-platform/acme.cue
Normal file
38
examples/acme-platform/acme.cue
Normal file
@ -0,0 +1,38 @@
|
||||
// ACME platform: everything you need to develop and ship improvements to
|
||||
// the ACME clothing store.
|
||||
package acme
|
||||
|
||||
import (
|
||||
"dagger.cloud/dagger"
|
||||
"dagger.cloud/netlify"
|
||||
"dagger.cloud/aws/ecs"
|
||||
"dagger.cloud/microstaging"
|
||||
)
|
||||
|
||||
// Website on netlify
|
||||
www: netlify & {
|
||||
domain: string | *defaultDomain
|
||||
|
||||
// By default, use a generated microstaging.io domain
|
||||
// for easy environments on demand.
|
||||
let defaultDomain=microstaging.#Domain & {
|
||||
token: _
|
||||
prefix: "www.acme"
|
||||
}
|
||||
}
|
||||
|
||||
// API deployed on ECS
|
||||
api: ecs & {
|
||||
domain: _ | *defaultDomain
|
||||
|
||||
let defaultDomain = microstaging.#Domain & {
|
||||
token: _
|
||||
prefix: "api.acme"
|
||||
}
|
||||
}
|
||||
|
||||
// Database on RDS
|
||||
db: rds & {
|
||||
engine: "postgresql"
|
||||
|
||||
}
|
24
examples/acme-platform/alpine.cue
Normal file
24
examples/acme-platform/alpine.cue
Normal file
@ -0,0 +1,24 @@
|
||||
package alpine
|
||||
|
||||
|
||||
#Image: {
|
||||
|
||||
version: string | *"latest"
|
||||
packages: [...string]
|
||||
|
||||
#dag: {
|
||||
do: [
|
||||
{
|
||||
//
|
||||
//
|
||||
// fetch alpine
|
||||
},
|
||||
{
|
||||
for _, pkg in packages {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
]
|
||||
}
|
||||
}
|
1
examples/acme-platform/cue.mod/module.cue
Normal file
1
examples/acme-platform/cue.mod/module.cue
Normal file
@ -0,0 +1 @@
|
||||
module: "acme.infralabs.io/acme"
|
@ -0,0 +1,104 @@
|
||||
package dagger
|
||||
|
||||
// A DAG is the basic unit of programming in dagger.
|
||||
// It is a special kind of program which runs as a pipeline of computing nodes running in parallel,
|
||||
// instead of a sequence of operations to be run by a single node.
|
||||
//
|
||||
// It is a powerful way to automate various parts of an application delivery workflow:
|
||||
// build, test, deploy, generate configuration, enforce policies, publish artifacts, etc.
|
||||
//
|
||||
// The DAG architecture has many benefits:
|
||||
// - Because DAGs are made of nodes executing in parallel, they are easy to scale.
|
||||
// - Because all inputs and outputs are snapshotted and content-addressed, DAGs
|
||||
// can easily be made repeatable, can be cached aggressively, and can be replayed
|
||||
// at will.
|
||||
// - Because nodes are executed by the same container engine as docker-build, DAGs
|
||||
// can be developed using any language or technology capable of running in a docker.
|
||||
// Dockerfiles and docker images are natively supported for maximum compatibility.
|
||||
//
|
||||
// - Because DAGs are programmed declaratively with a powerful configuration language,
|
||||
// they are much easier to test, debug and refactor than traditional programming languages.
|
||||
//
|
||||
// To execute a DAG, the dagger runtime JIT-compiles it to a low-level format called
|
||||
// llb, and executes it with buildkit.
|
||||
// Think of buildkit as a specialized VM for running compute graphs; and dagger as
|
||||
// a complete programming environment for that VM.
|
||||
//
|
||||
// The tradeoff for all those wonderful features is that a DAG architecture cannot be used
|
||||
// for all software: only software than can be run as a pipeline.
|
||||
//
|
||||
|
||||
// A dagger component is a configuration value augmented
|
||||
// by scripts defining how to compute it, present it to a user,
|
||||
// encrypt it, etc.
|
||||
#Component: #dagger: {
|
||||
// script to compute the value
|
||||
compute?: #Script
|
||||
|
||||
terminal?: {
|
||||
// Display a message when opening a terminal session
|
||||
greeting?: string
|
||||
command: [string]: #Script
|
||||
}
|
||||
// Configure how the component is incorporated to user settings.
|
||||
// Configure how the end-user can configure this component
|
||||
settings?: {
|
||||
// If not specified, scrape from comments
|
||||
title?: string
|
||||
description?: string
|
||||
// Disable user input, even if incomplete?
|
||||
hidden: true | *false
|
||||
ui: _ // insert here something which can be compiled to react-jsonschema-form
|
||||
// Show the cue default value to the user, as a default input value?
|
||||
showDefault: true | *false
|
||||
|
||||
// Insert information needed by:
|
||||
// 1) clients to encrypt
|
||||
// ie. web wizard, cli
|
||||
// 2) middleware to implement deicphering in the cuellb pipeline
|
||||
// eg. integration with clcoud KMS, Vault...
|
||||
//
|
||||
// 3) connectors to make sure secrets are preserved
|
||||
encrypt?: {
|
||||
pubkey: string
|
||||
cipher: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Any component can be referenced as a directory, since
|
||||
// every dagger script outputs a filesystem state (aka a directory)
|
||||
#Dir: #Component
|
||||
|
||||
#Script: [...#Op]
|
||||
|
||||
// One operation in a script
|
||||
#Op: #Fetch | #Export | #Run | #Local
|
||||
|
||||
// Export a value from fs state to cue
|
||||
#Export: ["export", string, "json"|"yaml"|"string"|"number"|"boolean"]
|
||||
|
||||
#Run: #runNoOpts | #runWithOpts
|
||||
#runNoOpts: ["run", ...string]
|
||||
#runWithOpts: ["run", #RunOpts, ...string]
|
||||
|
||||
#RunOpts: {
|
||||
mount?: string: "tmpfs" | "cache" | { from: #Component|#Script }
|
||||
env: [string]: string
|
||||
dir: string | *"/"
|
||||
always: true | *false
|
||||
}
|
||||
|
||||
#Local: ["local", string]
|
||||
|
||||
#Fetch: #FetchGit | #FetchContainer
|
||||
#FetchContainer: ["fetch", "container", string]
|
||||
#FetchGit: ["fetch", "git", string, string]
|
||||
|
||||
|
||||
#TestScript: #Script & [
|
||||
["fetch", "container", "alpine:latest"],
|
||||
["run", "echo", "hello", "world"]
|
||||
]
|
63
examples/acme-platform/mynetlify.cue
Normal file
63
examples/acme-platform/mynetlify.cue
Normal file
@ -0,0 +1,63 @@
|
||||
package netlify
|
||||
|
||||
import (
|
||||
".../alpine"
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
auth: {
|
||||
#dag: {
|
||||
encrypted: true
|
||||
do: [
|
||||
{
|
||||
action: "fetch"
|
||||
type: "docker"
|
||||
source: "alpine"
|
||||
},
|
||||
{
|
||||
action: "push"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
{
|
||||
username: string
|
||||
password: string
|
||||
} | {
|
||||
// FIXME: enrypted!
|
||||
token: string
|
||||
}
|
||||
}
|
||||
|
||||
name: string
|
||||
domain?: string
|
||||
// FIXME: directory!
|
||||
source: bl.#Dir
|
||||
|
||||
let base = alpine.#Image & {
|
||||
version: "foo"
|
||||
packages: ["rsync", "npm", "openssh"]
|
||||
}
|
||||
|
||||
// Netlify site ID
|
||||
id: {
|
||||
info1: string
|
||||
info2: string
|
||||
|
||||
#dag: {
|
||||
// run code to fetch id from netlify API
|
||||
from: base
|
||||
do: [
|
||||
{
|
||||
action: "run"
|
||||
command: ["netlify-get-id", name, "-o", "/netlify-id.json"]
|
||||
}
|
||||
]
|
||||
export: json: "/netlify-id.json"
|
||||
}
|
||||
}
|
||||
|
||||
url: string
|
216
examples/acme-platform/netlify/netlify.cue
Normal file
216
examples/acme-platform/netlify/netlify.cue
Normal file
@ -0,0 +1,216 @@
|
||||
// Custom netlify package
|
||||
// ACME platform team <platform@acme.infralabs.io>
|
||||
//
|
||||
// TODO: upstream to dagger standard library.
|
||||
package netlify
|
||||
|
||||
import (
|
||||
"dagger.cloud/dagger"
|
||||
)
|
||||
|
||||
// Netlify API token
|
||||
token: {
|
||||
#dag: {
|
||||
encrypt: cipher: "..."
|
||||
}
|
||||
|
||||
string
|
||||
}
|
||||
|
||||
|
||||
// Netlify site name
|
||||
name?: string
|
||||
|
||||
// Source directory to deploy
|
||||
source: dagger.#Dir
|
||||
|
||||
|
||||
let apply={
|
||||
#dag: {
|
||||
from: alpine.#Base
|
||||
do: [
|
||||
["run", "npm", "install", "netlify-cli", "-g"],
|
||||
[
|
||||
"copy",
|
||||
[
|
||||
"fetch", "git", "https://github.com/shykes/tests", "netlify-scripts",
|
||||
], "/", "/src",
|
||||
]
|
||||
// 2. fetch custom netlify scripts & iunstall
|
||||
// 3. get ID from name; create if doesn't exist
|
||||
// 4. deploy (via builder)
|
||||
]
|
||||
command: {
|
||||
debug: {
|
||||
from: base
|
||||
do: ["run", "sh", "-c", """
|
||||
env && find /netlify
|
||||
"""]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
apply
|
||||
|
||||
deployedDir: {
|
||||
#dag: {
|
||||
from: apply
|
||||
export: dir: "/netlify/content"
|
||||
}
|
||||
}
|
||||
|
||||
// Netlify site ID
|
||||
ID: {
|
||||
string
|
||||
|
||||
#dag: {
|
||||
from: apply
|
||||
export: string: "/netlify/site-id"
|
||||
}
|
||||
}
|
||||
|
||||
url: {
|
||||
string
|
||||
|
||||
#dag: {
|
||||
from: apply
|
||||
export: string: "/netlify/url"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Example of short-form cuellb pipeline
|
||||
// 1. single-op pipeline can omit the array
|
||||
// 2. action encoded in first key, instead of `action: ` field
|
||||
// 3. op may implement short-form,
|
||||
// in this case: `run: [...string]` instead of `run: { command: [...string] }`
|
||||
do: run: ["ntlfy-get-site-id", name, "-o", "/netlify/site-id"]
|
||||
// Declarative export from container, instead of awkward `readFile` pseudo-op
|
||||
export: string: "/netlify/site-id"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Configuration presets
|
||||
preset: {
|
||||
*"html" | "react" | "custom"
|
||||
|
||||
#dag: {
|
||||
settings: {
|
||||
markup: select: {
|
||||
"Static HTML site (no build)": "html"
|
||||
"ReactJS app built with npm": "react"
|
||||
"Custom builder": "custom"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Custom builder
|
||||
// Default: no build, deploy as-is.
|
||||
builder: {
|
||||
in: dagger.#Dir & source
|
||||
out: dagger.#Dir
|
||||
|
||||
if preset == "html" {
|
||||
// Pass-through builder that does nothing
|
||||
out: in
|
||||
}
|
||||
if preset == "react" {
|
||||
let app = reactjs.#App & {
|
||||
source: in
|
||||
}
|
||||
out: app.build
|
||||
}
|
||||
|
||||
...
|
||||
}
|
||||
|
||||
|
||||
scripts: {
|
||||
dagger.#Directory | *latestScripts
|
||||
|
||||
let latestScripts = {
|
||||
#dag: {
|
||||
do: {
|
||||
action: "fetch"
|
||||
type: "git"
|
||||
source: "https://github.com/shykes/tests"
|
||||
ref: "netlify-scripts"
|
||||
}
|
||||
}
|
||||
export: dir: "/"
|
||||
}
|
||||
}
|
||||
|
||||
// This is configurable for dev mode, but hide it from end users.
|
||||
#dag: settings: hidden: true
|
||||
}
|
||||
|
||||
// Version of the netlify CLI to use
|
||||
cliVersion: string | *latestCLIVersion
|
||||
|
||||
let latestCLIVersion = {
|
||||
string
|
||||
|
||||
#dag: {
|
||||
from: base
|
||||
do: run: ["sh", "-c", "npm show netlify-cli dist-tags.latest > /latest-cli-version"]
|
||||
export: string: "/latest-cli-version"
|
||||
}
|
||||
}
|
||||
|
||||
// Custom container to run netlify commands + wrappers
|
||||
let base=alpine.#Base & {
|
||||
package: {
|
||||
npm: true
|
||||
curl: true
|
||||
}
|
||||
}
|
||||
|
||||
let runner = {
|
||||
#dag: {
|
||||
from: base
|
||||
do: [
|
||||
{
|
||||
run: "npm", "install
|
||||
action: "run"
|
||||
command: ["npm", "install", "-g", "netlify-cli@" + cliVersion]
|
||||
},
|
||||
{
|
||||
// YOU ARE HERE
|
||||
// incorporate "netify scripts from personal github" pattern from other POC
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
url: {
|
||||
string
|
||||
|
||||
#dag: {
|
||||
from: runner
|
||||
do: run: {
|
||||
command: #"""
|
||||
netlify deploy
|
||||
--dir="$(pwd)" \
|
||||
--auth="$(cat /netlify/token)" \
|
||||
--site="${NETLIFY_SITE_ID}" \
|
||||
--message="Blocklayer 'netlify deploy'" \
|
||||
--prod \
|
||||
| tee /tmp/stdout
|
||||
curl \
|
||||
-i -X POST \
|
||||
-H "Authorization: Bearer $(cat /netlify/token)" \
|
||||
"https://api.netlify.com/api/v1/sites/${NETLIFY_SITE_ID}/ssl"
|
||||
"""#
|
||||
mount: {
|
||||
"/netlify/token": token
|
||||
"/netlify/source": builder.out
|
||||
}
|
||||
dir: "/netlify/source"
|
||||
env: {
|
||||
NETLIFY_SITE_ID: ID
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
89
examples/acme-platform/scratch.cue
Normal file
89
examples/acme-platform/scratch.cue
Normal file
@ -0,0 +1,89 @@
|
||||
package netlify
|
||||
|
||||
|
||||
#dag: {
|
||||
do: [
|
||||
{
|
||||
action: "fetch"
|
||||
type: "container"
|
||||
repository: "alpine"
|
||||
tag: "latest"
|
||||
},
|
||||
{
|
||||
action: "run"
|
||||
command: "apk add ..."
|
||||
},
|
||||
{
|
||||
action: "copy"
|
||||
from: [
|
||||
{
|
||||
action: "fetch"
|
||||
type: "git"
|
||||
repo: "https://github.com/shykes/stuff"
|
||||
}
|
||||
]
|
||||
source: "/"
|
||||
dest: "/src"
|
||||
},
|
||||
|
||||
]
|
||||
}
|
||||
|
||||
// Name of the netlify site
|
||||
name: {
|
||||
string
|
||||
|
||||
#dag: {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// ID of the netlify site
|
||||
// FIXME: compute
|
||||
id: {
|
||||
string
|
||||
|
||||
#dag: {
|
||||
from: ...
|
||||
do: [
|
||||
action: "run"
|
||||
command: ["netlify-get-id", name, "-o", "/netlify-id.txt"]
|
||||
]
|
||||
export: string: "/netlify-id.txt"
|
||||
}
|
||||
}
|
||||
|
||||
// API token
|
||||
// FIXME: encrypt secret!
|
||||
token: {
|
||||
#encrypt: {
|
||||
pubkey: _
|
||||
cipher: _
|
||||
}
|
||||
string
|
||||
}
|
||||
|
||||
// FIXME: how to receive a directory?
|
||||
source: bl.#Dir
|
||||
|
||||
|
||||
// Domain of the Netlify site
|
||||
domain?: string
|
||||
|
||||
// FIXME: compute
|
||||
url: {
|
||||
|
||||
#dag: {
|
||||
do: [
|
||||
// ...
|
||||
{
|
||||
action: "run"
|
||||
command: "netlify deploy"
|
||||
dir: "/src"
|
||||
mount: "/src": source
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
string
|
||||
}
|
3
examples/acme-platform/throaway
Normal file
3
examples/acme-platform/throaway
Normal file
@ -0,0 +1,3 @@
|
||||
|
||||
|
||||
|
46
examples/olivier/cue.mod/pkg/dagger.cloud/alpine/alpine.cue
Normal file
46
examples/olivier/cue.mod/pkg/dagger.cloud/alpine/alpine.cue
Normal file
@ -0,0 +1,46 @@
|
||||
package alpine
|
||||
|
||||
// Default version pinned to digest. Manually updated.
|
||||
let defaultDigest="sha256:3c7497bf0c7af93428242d6176e8f7905f2201d8fc5861f45be7a346b5f23436"
|
||||
|
||||
ref: string
|
||||
|
||||
// Match a combination of inputs 'version' and 'digest':
|
||||
*{
|
||||
// no version, no digest:
|
||||
ref: "index.docker.io/alpine@\(defaultDigest)"
|
||||
} | {
|
||||
// version, no digest
|
||||
version: string
|
||||
ref: "alpine:\(version)"
|
||||
} | {
|
||||
// digest, no version
|
||||
digest: string
|
||||
ref: "alpine@\(digest)"
|
||||
} | {
|
||||
// version and digest
|
||||
version: string
|
||||
digest: string
|
||||
ref: "alpine:\(version)@\(digest)"
|
||||
}
|
||||
|
||||
// Packages to install
|
||||
package: [string]: true | false | string
|
||||
|
||||
#dagger: compute: [
|
||||
{
|
||||
do: "fetch-container"
|
||||
"ref": ref
|
||||
},
|
||||
for pkg, info in package {
|
||||
if (info & true) != _|_ {
|
||||
do: "exec"
|
||||
args: ["apk", "add", "-U", "--no-cache", pkg]
|
||||
}
|
||||
if (info & string) != _|_ {
|
||||
do: "exec"
|
||||
args: ["apk", "add", "-U", "--no-cache", "\(pkg)\(info)"]
|
||||
}
|
||||
},
|
||||
]
|
||||
|
145
examples/olivier/cue.mod/pkg/dagger.cloud/dagger/dagger.cue
Normal file
145
examples/olivier/cue.mod/pkg/dagger.cloud/dagger/dagger.cue
Normal file
@ -0,0 +1,145 @@
|
||||
package dagger
|
||||
|
||||
// A DAG is the basic unit of programming in dagger.
|
||||
// It is a special kind of program which runs as a pipeline of computing nodes running in parallel,
|
||||
// instead of a sequence of operations to be run by a single node.
|
||||
//
|
||||
// It is a powerful way to automate various parts of an application delivery workflow:
|
||||
// build, test, deploy, generate configuration, enforce policies, publish artifacts, etc.
|
||||
//
|
||||
// The DAG architecture has many benefits:
|
||||
// - Because DAGs are made of nodes executing in parallel, they are easy to scale.
|
||||
// - Because all inputs and outputs are snapshotted and content-addressed, DAGs
|
||||
// can easily be made repeatable, can be cached aggressively, and can be replayed
|
||||
// at will.
|
||||
// - Because nodes are executed by the same container engine as docker-build, DAGs
|
||||
// can be developed using any language or technology capable of running in a docker.
|
||||
// Dockerfiles and docker images are natively supported for maximum compatibility.
|
||||
//
|
||||
// - Because DAGs are programmed declaratively with a powerful configuration language,
|
||||
// they are much easier to test, debug and refactor than traditional programming languages.
|
||||
//
|
||||
// To execute a DAG, the dagger runtime JIT-compiles it to a low-level format called
|
||||
// llb, and executes it with buildkit.
|
||||
// Think of buildkit as a specialized VM for running compute graphs; and dagger as
|
||||
// a complete programming environment for that VM.
|
||||
//
|
||||
// The tradeoff for all those wonderful features is that a DAG architecture cannot be used
|
||||
// for all software: only software than can be run as a pipeline.
|
||||
//
|
||||
|
||||
// A dagger component is a configuration value augmented
|
||||
// by scripts defining how to compute it, present it to a user,
|
||||
// encrypt it, etc.
|
||||
#Component: {
|
||||
#dagger: {
|
||||
// script to compute the value
|
||||
compute?: #Script
|
||||
|
||||
terminal?: {
|
||||
// Display a message when opening a terminal session
|
||||
greeting?: string
|
||||
command: [string]: #Script
|
||||
}
|
||||
// Configure how the component is incorporated to user settings.
|
||||
// Configure how the end-user can configure this component
|
||||
settings?: {
|
||||
// If not specified, scrape from comments
|
||||
title?: string
|
||||
description?: string
|
||||
// Disable user input, even if incomplete?
|
||||
hidden: true | *false
|
||||
ui: _ // insert here something which can be compiled to react-jsonschema-form
|
||||
// Show the cue default value to the user, as a default input value?
|
||||
showDefault: true | *false
|
||||
|
||||
// Insert information needed by:
|
||||
// 1) clients to encrypt
|
||||
// ie. web wizard, cli
|
||||
// 2) middleware to implement deicphering in the cuellb pipeline
|
||||
// eg. integration with clcoud KMS, Vault...
|
||||
//
|
||||
// 3) connectors to make sure secrets are preserved
|
||||
encrypt?: {
|
||||
pubkey: string
|
||||
cipher: string
|
||||
}
|
||||
}
|
||||
}
|
||||
...
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Any component can be referenced as a directory, since
|
||||
// every dagger script outputs a filesystem state (aka a directory)
|
||||
#Dir: #Component
|
||||
|
||||
#Script: [...#Op]
|
||||
|
||||
// One operation in a script
|
||||
// #Op: #FetchContainer | #FetchGit | #Export | #Exec | #Copy | #Load
|
||||
#Op: #FetchContainer | #Export | #Exec
|
||||
|
||||
// Export a value from fs state to cue
|
||||
#Export: {
|
||||
do: "export"
|
||||
// Source path in the container
|
||||
source: string
|
||||
format: "json"|"yaml"|*"string"|"number"|"boolean"
|
||||
}
|
||||
|
||||
#Load: #LoadComponent| #LoadScript
|
||||
#LoadComponent: {
|
||||
do: "load"
|
||||
from: #Component
|
||||
}
|
||||
#LoadScript: {
|
||||
do: "load"
|
||||
from: #Script
|
||||
}
|
||||
|
||||
|
||||
#Exec: {
|
||||
do: "exec"
|
||||
args: [...string]
|
||||
mount?: [string]: #MountTmp | #MountCache | #MountComponent | #MountScript
|
||||
env: [string]: string
|
||||
dir: string | *"/"
|
||||
always: true | *false
|
||||
}
|
||||
|
||||
#MountTmp: "tmpfs"
|
||||
#MountCache: "cache"
|
||||
#MountComponent: {
|
||||
input: #Component
|
||||
path: string | *"/"
|
||||
}
|
||||
#MountScript: {
|
||||
input: #Script
|
||||
path: string | *"/"
|
||||
}
|
||||
|
||||
#FetchContainer: {
|
||||
do: "fetch-container"
|
||||
ref: string
|
||||
}
|
||||
|
||||
#FetchGit: {
|
||||
do: "fetch-git"
|
||||
remote: string
|
||||
ref: string
|
||||
}
|
||||
|
||||
#Copy: {
|
||||
do: "copy"
|
||||
input: #Script | #Component
|
||||
src: string | *"/"
|
||||
dest: string | *"/"
|
||||
}
|
||||
|
||||
|
||||
#TestScript: #Script & [
|
||||
{ do: "fetch-container", ref: "alpine:latest" },
|
||||
{ do: "exec", args: ["echo", "hello", "world" ], env: DEBUG: "1" }
|
||||
]
|
63
examples/olivier/example.cue
Normal file
63
examples/olivier/example.cue
Normal file
@ -0,0 +1,63 @@
|
||||
package example
|
||||
|
||||
import (
|
||||
"dagger.cloud/alpine"
|
||||
)
|
||||
|
||||
test: {
|
||||
string
|
||||
#dagger: compute: [
|
||||
{ do: "load", from: alpine },
|
||||
{
|
||||
do: "copy"
|
||||
from: [
|
||||
{ do: "fetch-container", ref: alpine.ref },
|
||||
]
|
||||
dest: "/src"
|
||||
},
|
||||
{
|
||||
do: "exec"
|
||||
dir: "/src"
|
||||
args: ["sh", "-c", """
|
||||
ls -l > /tmp/out
|
||||
"""
|
||||
]
|
||||
},
|
||||
{
|
||||
do: "export"
|
||||
source: "/tmp/out"
|
||||
format: "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
www: {
|
||||
|
||||
// Domain where the site will be deployed (user input)
|
||||
domain: string
|
||||
|
||||
// URL after deployment (computed)
|
||||
url: {
|
||||
string & =~ "https://.*"
|
||||
|
||||
#dagger: {
|
||||
compute: [
|
||||
{ do: "load", from: alpine },
|
||||
{
|
||||
do: "exec"
|
||||
args: ["sh", "-c",
|
||||
"""
|
||||
echo 'deploying to netlify (not really)'
|
||||
echo 'https://\(domain)/foo' > /tmp/out
|
||||
"""
|
||||
]
|
||||
},
|
||||
{
|
||||
do: "export"
|
||||
source: "/tmp/out"
|
||||
format: "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
3
examples/olivier/values.cue
Normal file
3
examples/olivier/values.cue
Normal file
@ -0,0 +1,3 @@
|
||||
package example
|
||||
|
||||
www: domain: "www.foobar.com"
|
1
examples/simple/cue.mod/module.cue
Normal file
1
examples/simple/cue.mod/module.cue
Normal file
@ -0,0 +1 @@
|
||||
module: "acme.infralabs.io/acme"
|
46
examples/simple/cue.mod/pkg/dagger.cloud/alpine/alpine.cue
Normal file
46
examples/simple/cue.mod/pkg/dagger.cloud/alpine/alpine.cue
Normal file
@ -0,0 +1,46 @@
|
||||
package alpine
|
||||
|
||||
// Default version pinned to digest. Manually updated.
|
||||
let defaultDigest="sha256:3c7497bf0c7af93428242d6176e8f7905f2201d8fc5861f45be7a346b5f23436"
|
||||
|
||||
ref: string
|
||||
|
||||
// Match a combination of inputs 'version' and 'digest':
|
||||
*{
|
||||
// no version, no digest:
|
||||
ref: "index.docker.io/alpine@\(defaultDigest)"
|
||||
} | {
|
||||
// version, no digest
|
||||
version: string
|
||||
ref: "alpine:\(version)"
|
||||
} | {
|
||||
// digest, no version
|
||||
digest: string
|
||||
ref: "alpine@\(digest)"
|
||||
} | {
|
||||
// version and digest
|
||||
version: string
|
||||
digest: string
|
||||
ref: "alpine:\(version)@\(digest)"
|
||||
}
|
||||
|
||||
// Packages to install
|
||||
package: [string]: true | false | string
|
||||
|
||||
#dagger: compute: [
|
||||
{
|
||||
do: "fetch-container"
|
||||
"ref": ref
|
||||
},
|
||||
for pkg, info in package {
|
||||
if (info & true) != _|_ {
|
||||
do: "exec"
|
||||
args: ["apk", "add", "-U", "--no-cache", pkg]
|
||||
}
|
||||
if (info & string) != _|_ {
|
||||
do: "exec"
|
||||
args: ["apk", "add", "-U", "--no-cache", "\(pkg)\(info)"]
|
||||
}
|
||||
},
|
||||
]
|
||||
|
145
examples/simple/cue.mod/pkg/dagger.cloud/dagger/dagger.cue
Normal file
145
examples/simple/cue.mod/pkg/dagger.cloud/dagger/dagger.cue
Normal file
@ -0,0 +1,145 @@
|
||||
package dagger
|
||||
|
||||
// A DAG is the basic unit of programming in dagger.
|
||||
// It is a special kind of program which runs as a pipeline of computing nodes running in parallel,
|
||||
// instead of a sequence of operations to be run by a single node.
|
||||
//
|
||||
// It is a powerful way to automate various parts of an application delivery workflow:
|
||||
// build, test, deploy, generate configuration, enforce policies, publish artifacts, etc.
|
||||
//
|
||||
// The DAG architecture has many benefits:
|
||||
// - Because DAGs are made of nodes executing in parallel, they are easy to scale.
|
||||
// - Because all inputs and outputs are snapshotted and content-addressed, DAGs
|
||||
// can easily be made repeatable, can be cached aggressively, and can be replayed
|
||||
// at will.
|
||||
// - Because nodes are executed by the same container engine as docker-build, DAGs
|
||||
// can be developed using any language or technology capable of running in a docker.
|
||||
// Dockerfiles and docker images are natively supported for maximum compatibility.
|
||||
//
|
||||
// - Because DAGs are programmed declaratively with a powerful configuration language,
|
||||
// they are much easier to test, debug and refactor than traditional programming languages.
|
||||
//
|
||||
// To execute a DAG, the dagger runtime JIT-compiles it to a low-level format called
|
||||
// llb, and executes it with buildkit.
|
||||
// Think of buildkit as a specialized VM for running compute graphs; and dagger as
|
||||
// a complete programming environment for that VM.
|
||||
//
|
||||
// The tradeoff for all those wonderful features is that a DAG architecture cannot be used
|
||||
// for all software: only software than can be run as a pipeline.
|
||||
//
|
||||
|
||||
// A dagger component is a configuration value augmented
|
||||
// by scripts defining how to compute it, present it to a user,
|
||||
// encrypt it, etc.
|
||||
#Component: {
|
||||
#dagger: {
|
||||
// script to compute the value
|
||||
compute?: #Script
|
||||
|
||||
terminal?: {
|
||||
// Display a message when opening a terminal session
|
||||
greeting?: string
|
||||
command: [string]: #Script
|
||||
}
|
||||
// Configure how the component is incorporated to user settings.
|
||||
// Configure how the end-user can configure this component
|
||||
settings?: {
|
||||
// If not specified, scrape from comments
|
||||
title?: string
|
||||
description?: string
|
||||
// Disable user input, even if incomplete?
|
||||
hidden: true | *false
|
||||
ui: _ // insert here something which can be compiled to react-jsonschema-form
|
||||
// Show the cue default value to the user, as a default input value?
|
||||
showDefault: true | *false
|
||||
|
||||
// Insert information needed by:
|
||||
// 1) clients to encrypt
|
||||
// ie. web wizard, cli
|
||||
// 2) middleware to implement deicphering in the cuellb pipeline
|
||||
// eg. integration with clcoud KMS, Vault...
|
||||
//
|
||||
// 3) connectors to make sure secrets are preserved
|
||||
encrypt?: {
|
||||
pubkey: string
|
||||
cipher: string
|
||||
}
|
||||
}
|
||||
}
|
||||
...
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Any component can be referenced as a directory, since
|
||||
// every dagger script outputs a filesystem state (aka a directory)
|
||||
#Dir: #Component
|
||||
|
||||
#Script: [...#Op]
|
||||
|
||||
// One operation in a script
|
||||
// #Op: #FetchContainer | #FetchGit | #Export | #Exec | #Copy | #Load
|
||||
#Op: #FetchContainer | #Export | #Exec
|
||||
|
||||
// Export a value from fs state to cue
|
||||
#Export: {
|
||||
do: "export"
|
||||
// Source path in the container
|
||||
source: string
|
||||
format: "json"|"yaml"|*"string"|"number"|"boolean"
|
||||
}
|
||||
|
||||
#Load: #LoadComponent| #LoadScript
|
||||
#LoadComponent: {
|
||||
do: "load"
|
||||
from: #Component
|
||||
}
|
||||
#LoadScript: {
|
||||
do: "load"
|
||||
from: #Script
|
||||
}
|
||||
|
||||
|
||||
#Exec: {
|
||||
do: "exec"
|
||||
args: [...string]
|
||||
mount?: [string]: #MountTmp | #MountCache | #MountComponent | #MountScript
|
||||
env: [string]: string
|
||||
dir: string | *"/"
|
||||
always: true | *false
|
||||
}
|
||||
|
||||
#MountTmp: "tmpfs"
|
||||
#MountCache: "cache"
|
||||
#MountComponent: {
|
||||
input: #Component
|
||||
path: string | *"/"
|
||||
}
|
||||
#MountScript: {
|
||||
input: #Script
|
||||
path: string | *"/"
|
||||
}
|
||||
|
||||
#FetchContainer: {
|
||||
do: "fetch-container"
|
||||
ref: string
|
||||
}
|
||||
|
||||
#FetchGit: {
|
||||
do: "fetch-git"
|
||||
remote: string
|
||||
ref: string
|
||||
}
|
||||
|
||||
#Copy: {
|
||||
do: "copy"
|
||||
input: #Script | #Component
|
||||
src: string | *"/"
|
||||
dest: string | *"/"
|
||||
}
|
||||
|
||||
|
||||
#TestScript: #Script & [
|
||||
{ do: "fetch-container", ref: "alpine:latest" },
|
||||
{ do: "exec", args: ["echo", "hello", "world" ], env: DEBUG: "1" }
|
||||
]
|
33
examples/simple/simple.cue
Normal file
33
examples/simple/simple.cue
Normal file
@ -0,0 +1,33 @@
|
||||
// ACME platform: everything you need to develop and ship improvements to
|
||||
// the ACME clothing store.
|
||||
package acme
|
||||
|
||||
import (
|
||||
"dagger.cloud/alpine"
|
||||
)
|
||||
|
||||
let base=alpine & {
|
||||
package: {
|
||||
bash: ">3.0"
|
||||
rsync: true
|
||||
}
|
||||
}
|
||||
|
||||
www: {
|
||||
|
||||
source: {
|
||||
#dagger: input: true
|
||||
}
|
||||
|
||||
host: string
|
||||
|
||||
url: {
|
||||
string
|
||||
|
||||
#dagger: compute: [
|
||||
{ do: "load", from: base },
|
||||
{ do: "exec", args: ["sh", "-c", "echo -n 'https://\(host)/foo' > /tmp/out"] },
|
||||
{ do: "export", format: "string", source: "/tmp/out" },
|
||||
]
|
||||
}
|
||||
}
|
3
examples/simple/values.cue
Normal file
3
examples/simple/values.cue
Normal file
@ -0,0 +1,3 @@
|
||||
package acme
|
||||
|
||||
www: host: "acme.infralabs.io"
|
76
go.mod
Normal file
76
go.mod
Normal file
@ -0,0 +1,76 @@
|
||||
module dagger.cloud/go
|
||||
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
cuelang.org/go v0.3.0-alpha6
|
||||
github.com/Azure/azure-sdk-for-go v16.2.1+incompatible // indirect
|
||||
github.com/Azure/go-autorest v10.8.1+incompatible // indirect
|
||||
github.com/KromDaniel/jonson v0.0.0-20180630143114-d2f9c3c389db
|
||||
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d // indirect
|
||||
github.com/aws/aws-sdk-go v1.15.11 // indirect
|
||||
github.com/bitly/go-simplejson v0.5.0 // indirect
|
||||
github.com/blang/semver v3.1.0+incompatible // indirect
|
||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
|
||||
github.com/bshuster-repo/logrus-logstash-hook v0.4.1 // indirect
|
||||
github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd // indirect
|
||||
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b // indirect
|
||||
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 // indirect
|
||||
github.com/containerd/console v1.0.0
|
||||
github.com/containerd/containerd v1.4.0-0.20191014053712-acdcf13d5eaf
|
||||
github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba // indirect
|
||||
github.com/dnaeon/go-vcr v1.0.1 // indirect
|
||||
github.com/docker/cli v0.0.0-20200227165822-2298e6a3fe24
|
||||
github.com/docker/distribution v2.7.1+incompatible
|
||||
github.com/docker/docker v1.14.0-0.20190319215453-e7b5f7dbe98c // indirect
|
||||
github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916 // indirect
|
||||
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 // indirect
|
||||
github.com/emicklei/proto v1.9.0 // indirect
|
||||
github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7 // indirect
|
||||
github.com/gofrs/uuid v3.3.0+incompatible
|
||||
github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33 // indirect
|
||||
github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce // indirect
|
||||
github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874 // indirect
|
||||
github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7 // indirect
|
||||
github.com/machinebox/graphql v0.2.2
|
||||
github.com/marstr/guid v1.1.0 // indirect
|
||||
github.com/matryer/is v1.3.0 // indirect
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db
|
||||
github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f // indirect
|
||||
github.com/moby/buildkit v0.7.1-0.20200529013301-643f0ffb5643
|
||||
github.com/ncw/swift v1.0.47 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0
|
||||
github.com/opencontainers/runc v1.0.0-rc9.0.20200221051241-688cf6d43cc4 // indirect
|
||||
github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39 // indirect
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/prometheus/procfs v0.0.5 // indirect
|
||||
github.com/rs/zerolog v1.17.2
|
||||
github.com/satori/go.uuid v1.2.0 // indirect
|
||||
github.com/spf13/cobra v1.0.0
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/viper v1.6.2
|
||||
github.com/tonistiigi/fsutil v0.0.0-20200512175118-ae3a8d753069
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f // indirect
|
||||
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 // indirect
|
||||
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 // indirect
|
||||
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f // indirect
|
||||
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 // indirect
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
|
||||
google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff // indirect
|
||||
google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8 // indirect
|
||||
google.golang.org/grpc v1.27.1
|
||||
gopkg.in/yaml.v3 v3.0.0-20200506231410-2ff61e1afc86 // indirect
|
||||
k8s.io/kubernetes v1.13.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/hashicorp/go-immutable-radix => github.com/tonistiigi/go-immutable-radix v0.0.0-20170803185627-826af9ccf0fe
|
||||
|
||||
replace github.com/jaguilar/vt100 => github.com/tonistiigi/vt100 v0.0.0-20190402012908-ad4c4a574305
|
||||
|
||||
replace github.com/containerd/containerd => github.com/containerd/containerd v1.3.1-0.20200227195959-4d242818bf55
|
||||
|
||||
replace github.com/docker/docker => github.com/docker/docker v1.4.2-0.20200227233006-38f52c9fec82
|
444
go.sum
Normal file
444
go.sum
Normal file
@ -0,0 +1,444 @@
|
||||
bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cuelang.org/go v0.2.2 h1:i/wFo48WDibGHKQTRZ08nB8PqmGpVpQ2sRflZPj73nQ=
|
||||
cuelang.org/go v0.3.0-alpha4.0.20201118122203-cd621ffa2727 h1:9oH36KOs7IQwrSXlLhGRy6PPkiwBCC7pcbkTE1TmPwY=
|
||||
cuelang.org/go v0.3.0-alpha4.0.20201118122203-cd621ffa2727/go.mod h1:un6c4wnW3jEIrp97ARM3eJOlIJojP61ASQTRHyR0uiE=
|
||||
cuelang.org/go v0.3.0-alpha6 h1:UA+GMa6JdTMG2ywjBCbEzgPbZ4y4pudaFGvFSeZ9h8s=
|
||||
cuelang.org/go v0.3.0-alpha6/go.mod h1:NwRWsRzQvqkhCHdkIjFBKo9ujPd1OQLZzugDjElHh8Q=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/AkihiroSuda/containerd-fuse-overlayfs v0.0.0-20200512015515-32086ef23a5a/go.mod h1:RkqizX9+ro7Pp7RxEZAJWIr1/FrkZKCuUDi944JHt0U=
|
||||
github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||
github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/KromDaniel/jonson v0.0.0-20180630143114-d2f9c3c389db h1:Zkf5kwhxdW0xV7WM/crqIcOP5LCFGnAmumWSFAewJ74=
|
||||
github.com/KromDaniel/jonson v0.0.0-20180630143114-d2f9c3c389db/go.mod h1:RU+6d0CNIRSp6yo1mXLIIrnFa/3LHhvcDVLVJyovptM=
|
||||
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
|
||||
github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/apache/thrift v0.0.0-20161221203622-b2a4d4ae21c7/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
|
||||
github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
|
||||
github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
|
||||
github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
|
||||
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50=
|
||||
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
|
||||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
||||
github.com/cockroachdb/apd/v2 v2.0.1 h1:y1Rh3tEU89D+7Tgbw+lp52T6p/GJLpDmNvr10UWqLTE=
|
||||
github.com/cockroachdb/apd/v2 v2.0.1/go.mod h1:DDxRlzC2lo3/vSlmSoS7JkqbbrARPuFOGr0B9pvN3Gw=
|
||||
github.com/codahale/hdrhistogram v0.0.0-20160425231609-f8ad88b59a58/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
|
||||
github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko=
|
||||
github.com/containerd/cgroups v0.0.0-20200327175542-b44481373989/go.mod h1:CStdkl05lBnJej94BPFoJ7vB8cELKXwViS+dgfW0/M8=
|
||||
github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=
|
||||
github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE=
|
||||
github.com/containerd/console v1.0.0 h1:fU3UuQapBs+zLJu82NhR11Rif1ny2zfMMAyPJzSN5tQ=
|
||||
github.com/containerd/console v1.0.0/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE=
|
||||
github.com/containerd/containerd v1.3.1-0.20200227195959-4d242818bf55 h1:FGO0nwSBESgoGCakj+w3OQXyrMLsz2omdo9b2UfG/BQ=
|
||||
github.com/containerd/containerd v1.3.1-0.20200227195959-4d242818bf55/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
||||
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
|
||||
github.com/containerd/continuity v0.0.0-20200228182428-0f16d7a0959c/go.mod h1:Dq467ZllaHgAtVp4p1xUQWBrFXR9s/wyoTpG8zOJGkY=
|
||||
github.com/containerd/continuity v0.0.0-20200413184840-d3ef23f19fbb h1:nXPkFq8X1a9ycY3GYQpFNxHh3j2JgY7zDZfq2EXMIzk=
|
||||
github.com/containerd/continuity v0.0.0-20200413184840-d3ef23f19fbb/go.mod h1:Dq467ZllaHgAtVp4p1xUQWBrFXR9s/wyoTpG8zOJGkY=
|
||||
github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI=
|
||||
github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0=
|
||||
github.com/containerd/go-cni v0.0.0-20200107172653-c154a49e2c75/go.mod h1:0mg8r6FCdbxvLDqCXwAx2rO+KA37QICjKL8+wHOG5OE=
|
||||
github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0=
|
||||
github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g=
|
||||
github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o=
|
||||
github.com/containerd/ttrpc v1.0.1 h1:IfVOxKbjyBn9maoye2JN95pgGYOmPkQVqxtOu7rtNIc=
|
||||
github.com/containerd/ttrpc v1.0.1/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y=
|
||||
github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc=
|
||||
github.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg=
|
||||
github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
|
||||
github.com/docker/cli v0.0.0-20200227165822-2298e6a3fe24 h1:bjsfAvm8BVtvQFxV7TYznmKa35J8+fmgrRJWvcS3yJo=
|
||||
github.com/docker/cli v0.0.0-20200227165822-2298e6a3fe24/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
|
||||
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v1.4.2-0.20200227233006-38f52c9fec82 h1:kZwwJwYnVWtU/byBNjD9rEGWVMvwnfiKu9lFJXjrk04=
|
||||
github.com/docker/docker v1.4.2-0.20200227233006-38f52c9fec82/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker-credential-helpers v0.6.0/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y=
|
||||
github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
|
||||
github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI=
|
||||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/docker/libnetwork v0.8.0-dev.2.0.20200226230617-d8334ccdb9be/go.mod h1:93m0aTqz6z+g32wla4l4WxTrdtvBRmVzYRkYvasA5Z8=
|
||||
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
|
||||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/emicklei/proto v1.6.15/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A=
|
||||
github.com/emicklei/proto v1.9.0 h1:l0QiNT6Qs7Yj0Mb4X6dnWBQer4ebei2BFcgQLbGqUDc=
|
||||
github.com/emicklei/proto v1.9.0/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
|
||||
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gofrs/flock v0.7.0 h1:pGFUjl501gafK9HBt1VGL1KCOd/YhIooID+xgyJCf3g=
|
||||
github.com/gofrs/flock v0.7.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
||||
github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/googleapis v1.3.2 h1:kX1es4djPJrsDhY7aZKJy7aZasdcB5oSOEphMjSB53c=
|
||||
github.com/gogo/googleapis v1.3.2/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
|
||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/shlex v0.0.0-20150127133951-6f45313302b9 h1:JM174NTeGNJ2m/oLH3UOWOvWQQKd+BoL3hcSCUWFLt0=
|
||||
github.com/google/shlex v0.0.0-20150127133951-6f45313302b9/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
|
||||
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.2.0 h1:0IKlLyQ3Hs9nDaiK5cSHAGmcQEIC8l2Ts1u6x5Dfrqg=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.2.0/go.mod h1:mJzapYve32yjrKlk9GbyCZHuPgZsrbyIbyKhSzOpg6s=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU=
|
||||
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw=
|
||||
github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hashicorp/uuid v0.0.0-20160311170451-ebb0a03e909c/go.mod h1:fHzc09UnyJyqyW+bFuq864eh+wC7dj65aXmXLRe5to0=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/ishidawataru/sctp v0.0.0-20191218070446-00ab2ac2db07/go.mod h1:co9pwDoBCm1kGxawmb4sPq0cSIOOWNPT4KnHotMP1Zg=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/machinebox/graphql v0.2.2/go.mod h1:F+kbVMHuwrQ5tYgU9JXlnskM8nOaFxCAEolaQybkjWA=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho=
|
||||
github.com/matryer/is v1.3.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/hashstructure v1.0.0/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A=
|
||||
github.com/moby/buildkit v0.7.1-0.20200529013301-643f0ffb5643 h1:UFxj67FF2yIwNjCUV0PL+GbzSovoXMk+mWz9Cn+wa04=
|
||||
github.com/moby/buildkit v0.7.1-0.20200529013301-643f0ffb5643/go.mod h1:4otpTPV294PXwTbjBlYnS1Er42Pe6baNDIS84W/Q2PM=
|
||||
github.com/moby/sys/mount v0.1.0/go.mod h1:FVQFLDRWwyBjDTBNQXDlWnSFREqOo3OKX9aqhmeoo74=
|
||||
github.com/moby/sys/mountinfo v0.1.0/go.mod h1:w2t2Avltqx8vE7gX5l+QiBKxODu2TX0+Syr3h52Tw4o=
|
||||
github.com/moby/sys/mountinfo v0.1.3/go.mod h1:w2t2Avltqx8vE7gX5l+QiBKxODu2TX0+Syr3h52Tw4o=
|
||||
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c h1:nXxl5PrvVm2L/wCy8dQu6DMTwH4oIuGN8GJDAlqDdVE=
|
||||
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de h1:D5x39vF5KCwKQaw+OC9ZPiLVHXz3UFw2+psEX+gYcto=
|
||||
github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de/go.mod h1:kJun4WP5gFuHZgRjZUWWuH1DTxCtxbHDOIJsudS8jzY=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
|
||||
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||
github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
|
||||
github.com/opencontainers/runc v1.0.0-rc10/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
|
||||
github.com/opencontainers/runc v1.0.0-rc9.0.20200221051241-688cf6d43cc4 h1:JhRvjyrjq24YPSDS0MQo9KJHQh95naK5fYl9IT+dzPM=
|
||||
github.com/opencontainers/runc v1.0.0-rc9.0.20200221051241-688cf6d43cc4/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
|
||||
github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs=
|
||||
github.com/opencontainers/selinux v1.5.1/go.mod h1:yTcKuYAh6R95iDpefGLQaPaRwJFwyzAJufJyiTt7s0g=
|
||||
github.com/opentracing-contrib/go-stdlib v0.0.0-20171029140428-b1a47cfbdd75/go.mod h1:PLldrQSroqzH70Xl+1DQcGnefIbqsKR7UDaiux3zV+w=
|
||||
github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU=
|
||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/go-internal v1.6.2-0.20200830194709-1115b6af0369/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||
github.com/rs/zerolog v1.17.2/go.mod h1:9nvC1axdVrAHcu/s9taAVfBuIdTZLVQmKQyvrUjF5+I=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/serialx/hashring v0.0.0-20190422032157-8b2912629002/go.mod h1:/yeG0My1xr/u+HZrFQ1tOQQQQrOawfyMUH13ai5brBc=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8=
|
||||
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
|
||||
github.com/spf13/viper v1.6.2/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k=
|
||||
github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tonistiigi/fsutil v0.0.0-20200512175118-ae3a8d753069 h1:F8MYILe5YNjbPLpmF/OgYrfsHBnAFZecylt1AXl8zow=
|
||||
github.com/tonistiigi/fsutil v0.0.0-20200512175118-ae3a8d753069/go.mod h1:uA7OEv9Ab41e89xMMPlecftcyNsXGVHuI71vi1acNeg=
|
||||
github.com/tonistiigi/go-immutable-radix v0.0.0-20170803185627-826af9ccf0fe/go.mod h1:/+MCh11CJf2oz0BXmlmqyopK/ad1rKkcOXPoYuPCJYU=
|
||||
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea h1:SXhTLE6pb6eld/v/cCndK0AMpt1wiVFb/YYmqB3/QG0=
|
||||
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea/go.mod h1:WPnis/6cRcDZSUvVmezrxJPkiO87ThFYsoUiMwWNDJk=
|
||||
github.com/tonistiigi/vt100 v0.0.0-20190402012908-ad4c4a574305 h1:y/1cL5AL2oRcfzz8CAHHhR6kDDfIOT0WEyH5k40sccM=
|
||||
github.com/tonistiigi/vt100 v0.0.0-20190402012908-ad4c4a574305/go.mod h1:gXOLibKqQTRAVuVZ9gX7G9Ykky8ll8yb4slxsEMoY0c=
|
||||
github.com/uber/jaeger-client-go v2.11.2+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
|
||||
github.com/uber/jaeger-lib v1.2.1/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs=
|
||||
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA=
|
||||
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg=
|
||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20200513190911-00229845015e/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2 h1:eDrdRpKgkcCqKZQwyZRyeFZgfqt37SL7Kv3tok06cKE=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200612220849-54c614fe050c/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200117163144-32f20d992d24/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200227132054-3f1135a288c9 h1:Koy0f8zyrEVfIHetH7wjP5mQLUXiqDpubSg8V1fAxqc=
|
||||
google.golang.org/genproto v0.0.0-20200227132054-3f1135a288c9/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
|
||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200506231410-2ff61e1afc86 h1:OfFoIUYv/me30yv7XlMy4F9RJw8DEm8WQ6QG1Ph4bH0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200506231410-2ff61e1afc86/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=
|
43
main.go
Normal file
43
main.go
Normal file
@ -0,0 +1,43 @@
|
||||
// A simple main.go for testing the dagger Go API
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"dagger.cloud/go/dagger"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ctx := context.TODO()
|
||||
c, err := dagger.NewClient(ctx, "")
|
||||
if err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
configPath := "."
|
||||
if len(os.Args) > 1 {
|
||||
configPath = os.Args[1]
|
||||
}
|
||||
|
||||
if err := c.SetConfig(configPath); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
// if err := c.ConnectInput("source", os.Getenv("HOME")+"/Documents/github/samalba/hello-go"); err != nil {
|
||||
// fatal(err)
|
||||
// }
|
||||
if err := c.Run(ctx, "compute"); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func fatalf(msg string, args ...interface{}) {
|
||||
fmt.Fprintf(os.Stderr, msg, args...)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func fatal(msg interface{}) {
|
||||
fatalf("%s\n", msg)
|
||||
}
|
Reference in New Issue
Block a user