This repository has been archived on 2024-04-08. You can view files and clone it, but cannot push or open issues or pull requests.
dagger/state/input.go
Andrea Luzzardi a61e8dcb62 prepare the transition to #Plan.context
This change helps the transition between `dagger input` and `#Plan.context`.

In summary, the codebase now relies on a *context* for execution with mapping to *IDs*.
In the future, *context* will come from a `#Plan.context`.
In the meantime, a bridge converts `dagger input` to a plan context. This allows both *old* and *new* style configurations to co-exist with the same underlying engine.

- Implement `plancontext`. Context holds the execution context for a plan. Currently this includes the platform, local directories, secrets and services (e.g. unix/npipe).
- Contextual data can be registered at any point. In the future, this will be done by `#Plan.context`
- Migrated the `dagger input` codebase to register inputs in a `plancontext`
- Migrated low-level types/operations to the *Context ID* pattern.
  - `dagger.#Stream` now only includes an `id` (instead of `unix` path)
  - `dagger.#Secret` still includes only an ID, but now it's based off `plancontext`
  - `op.#Local` now only includes an `id` (instead of `path`, `include`, `exclude`.

Signed-off-by: Andrea Luzzardi <aluzzardi@gmail.com>
2021-11-19 11:29:38 -08:00

284 lines
6.0 KiB
Go

package state
import (
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
"strings"
"cuelang.org/go/cue"
"go.dagger.io/dagger/compiler"
"go.dagger.io/dagger/plancontext"
)
// An input is a value or artifact supplied by the user.
//
// - A value is any structured data which can be encoded as cue.
//
// - An artifact is a piece of data, like a source code checkout,
// binary bundle, docker image, database backup etc.
//
// Artifacts can be passed as inputs, generated dynamically from
// other inputs, and received as outputs.
// Under the hood, an artifact is encoded as a LLB pipeline, and
// attached to the cue configuration as a
//
type Input struct {
Dir *dirInput `yaml:"dir,omitempty"`
Git *gitInput `yaml:"git,omitempty"`
Secret *secretInput `yaml:"secret,omitempty"`
Text *textInput `yaml:"text,omitempty"`
JSON *jsonInput `yaml:"json,omitempty"`
YAML *yamlInput `yaml:"yaml,omitempty"`
File *fileInput `yaml:"file,omitempty"`
Bool *boolInput `yaml:"bool,omitempty"`
Socket *socketInput `yaml:"socket,omitempty"`
}
func (i Input) Compile(state *State) (*compiler.Value, error) {
switch {
case i.Dir != nil:
return i.Dir.Compile(state)
case i.Git != nil:
return i.Git.Compile(state)
case i.Text != nil:
return i.Text.Compile(state)
case i.Secret != nil:
return i.Secret.Compile(state)
case i.JSON != nil:
return i.JSON.Compile(state)
case i.YAML != nil:
return i.YAML.Compile(state)
case i.File != nil:
return i.File.Compile(state)
case i.Bool != nil:
return i.Bool.Compile(state)
case i.Socket != nil:
return i.Socket.Compile(state)
default:
return nil, fmt.Errorf("input has not been set")
}
}
// An input artifact loaded from a local directory
func DirInput(path string, include []string, exclude []string) Input {
return Input{
Dir: &dirInput{
Path: path,
Include: include,
Exclude: exclude,
},
}
}
type dirInput struct {
Path string `yaml:"path,omitempty"`
Include []string `yaml:"include,omitempty"`
Exclude []string `yaml:"exclude,omitempty"`
}
func (dir dirInput) Compile(state *State) (*compiler.Value, error) {
p := dir.Path
if !filepath.IsAbs(p) {
p = filepath.Clean(path.Join(state.Project, dir.Path))
}
if !strings.HasPrefix(p, state.Project) {
return nil, fmt.Errorf("%q is outside the project", dir.Path)
}
// Check that directory exists
if _, err := os.Stat(p); os.IsNotExist(err) {
return nil, fmt.Errorf("%q dir doesn't exist", dir.Path)
}
id := state.Context.Directories.Register(&plancontext.Directory{
Path: p,
Include: dir.Include,
Exclude: dir.Exclude,
})
llb := fmt.Sprintf(
`#up: [{do:"local", id: "%s"}]`,
id,
)
return compiler.Compile("", llb)
}
// An input artifact loaded from a git repository
type gitInput struct {
Remote string `yaml:"remote,omitempty"`
Ref string `yaml:"ref,omitempty"`
Dir string `yaml:"dir,omitempty"`
}
func GitInput(remote, ref, dir string) Input {
return Input{
Git: &gitInput{
Remote: remote,
Ref: ref,
Dir: dir,
},
}
}
func (git gitInput) Compile(_ *State) (*compiler.Value, error) {
ref := "HEAD"
if git.Ref != "" {
ref = git.Ref
}
dir := ""
if git.Dir != "" {
dir = fmt.Sprintf(`,{do:"subdir", dir:"%s"}`, git.Dir)
}
return compiler.Compile("", fmt.Sprintf(
`#up: [{do:"fetch-git", remote:"%s", ref:"%s"}%s]`,
git.Remote,
ref,
dir,
))
}
// An input value encoded as text
func TextInput(data string) Input {
i := textInput(data)
return Input{
Text: &i,
}
}
type textInput string
func (i textInput) Compile(_ *State) (*compiler.Value, error) {
return compiler.Compile("", fmt.Sprintf("%q", i))
}
// A secret input value
func SecretInput(data string) Input {
i := secretInput(data)
return Input{
Secret: &i,
}
}
type secretInput string
func (i secretInput) Compile(st *State) (*compiler.Value, error) {
id := st.Context.Secrets.Register(&plancontext.Secret{
PlainText: i.PlainText(),
})
secretValue := fmt.Sprintf(`{id: %q}`, id)
return compiler.Compile("", secretValue)
}
func (i secretInput) PlainText() string {
return string(i)
}
// An input value encoded as Bool
func BoolInput(data string) Input {
i := boolInput(data)
return Input{
Bool: &i,
}
}
type boolInput string
func (i boolInput) Compile(_ *State) (*compiler.Value, error) {
s := map[boolInput]struct{}{
"true": {},
"false": {},
}
if _, ok := s[i]; ok {
return compiler.DecodeJSON("", []byte(i))
}
return nil, fmt.Errorf("%q is not a valid boolean: <true|false>", i)
}
// An input value encoded as JSON
func JSONInput(data string) Input {
i := jsonInput(data)
return Input{
JSON: &i,
}
}
type jsonInput string
func (i jsonInput) Compile(_ *State) (*compiler.Value, error) {
return compiler.DecodeJSON("", []byte(i))
}
// An input value encoded as YAML
func YAMLInput(data string) Input {
i := yamlInput(data)
return Input{
YAML: &i,
}
}
type yamlInput string
func (i yamlInput) Compile(_ *State) (*compiler.Value, error) {
return compiler.DecodeYAML("", []byte(i))
}
func FileInput(data string) Input {
return Input{
File: &fileInput{
Path: data,
},
}
}
type fileInput struct {
Path string `yaml:"path,omitempty"`
}
func (i fileInput) Compile(_ *State) (*compiler.Value, error) {
data, err := ioutil.ReadFile(i.Path)
if err != nil {
return nil, err
}
value := compiler.NewValue()
if err := value.FillPath(cue.MakePath(), data); err != nil {
return nil, err
}
return value, nil
}
// A socket input value
func SocketInput(data, socketType string) Input {
i := socketInput{}
switch socketType {
case "npipe":
i.Npipe = data
case "unix":
i.Unix = data
}
return Input{
Socket: &i,
}
}
type socketInput struct {
Unix string `json:"unix,omitempty" yaml:"unix,omitempty"`
Npipe string `json:"npipe,omitempty" yaml:"npipe,omitempty"`
}
func (i socketInput) Compile(st *State) (*compiler.Value, error) {
id := st.Context.Services.Register(&plancontext.Service{
Unix: i.Unix,
Npipe: i.Npipe,
})
socketValue := fmt.Sprintf(`{id: %q}`, id)
return compiler.Compile("", socketValue)
}