syntax = "proto3";

// Package pb provides the protobuf definition of LLB: low-level builder instruction.
// LLB is DAG-structured; Op represents a vertex, and Definition represents a graph.
package pb;

import "github.com/gogo/protobuf/gogoproto/gogo.proto";

option (gogoproto.stable_marshaler_all) = true;

// Op represents a vertex of the LLB DAG.
message Op {
	// inputs is a set of input edges.
	repeated Input inputs = 1;
	oneof op {
		ExecOp exec = 2;
		SourceOp source = 3;
		FileOp file = 4;
		BuildOp build = 5;
	}
	Platform platform = 10;
	WorkerConstraints constraints = 11;
}

// Platform is github.com/opencontainers/image-spec/specs-go/v1.Platform
message Platform {
	string Architecture = 1;
	string OS = 2;
	string Variant = 3;
	string OSVersion = 4; // unused
	repeated string OSFeatures = 5; // unused
}

// Input represents an input edge for an Op.
message Input {
	// digest of the marshaled input Op
	string digest = 1 [(gogoproto.customtype) = "github.com/opencontainers/go-digest.Digest", (gogoproto.nullable) = false];
	// output index of the input Op
	int64 index = 2 [(gogoproto.customtype) = "OutputIndex", (gogoproto.nullable) = false];
}

// ExecOp executes a command in a container.
message ExecOp {
	Meta meta = 1;
	repeated Mount mounts = 2;
	NetMode network = 3;
	SecurityMode security = 4;
}

// Meta is a set of arguments for ExecOp.
// Meta is unrelated to LLB metadata.
// FIXME: rename (ExecContext? ExecArgs?)
message Meta {
	repeated string args = 1;
	repeated string env = 2;
	string cwd = 3;
	string user = 4;
	ProxyEnv proxy_env = 5;
	repeated HostIP extraHosts = 6;
}

enum NetMode {
	UNSET = 0; // sandbox
	HOST = 1;
	NONE = 2;
}

enum SecurityMode {
	SANDBOX = 0;
	INSECURE = 1; // privileged mode
}

// Mount specifies how to mount an input Op as a filesystem.
message Mount {
	int64 input = 1 [(gogoproto.customtype) = "InputIndex", (gogoproto.nullable) = false];
	string selector = 2;
	string dest = 3;
	int64 output = 4 [(gogoproto.customtype) = "OutputIndex", (gogoproto.nullable) = false];
	bool readonly = 5;
	MountType mountType = 6;
	CacheOpt cacheOpt = 20;
	SecretOpt secretOpt = 21;
	SSHOpt SSHOpt = 22;
}

// MountType defines a type of a mount from a supported set
enum MountType {
	BIND = 0;
	SECRET = 1;
	SSH = 2;
	CACHE = 3;
	TMPFS = 4;
}

// CacheOpt defines options specific to cache mounts
message CacheOpt {
	// ID is an optional namespace for the mount
	string ID = 1;
	// Sharing is the sharing mode for the mount 
	CacheSharingOpt sharing = 2;
}

// CacheSharingOpt defines different sharing modes for cache mount
enum CacheSharingOpt {
	// SHARED cache mount can be used concurrently by multiple writers
	SHARED = 0;
	// PRIVATE creates a new mount if there are multiple writers
	PRIVATE = 1;
	// LOCKED pauses second writer until first one releases the mount
	LOCKED = 2;
}

// SecretOpt defines options describing secret mounts
message SecretOpt {
	// ID of secret. Used for quering the value.
	string ID = 1;
	// UID of secret file
	uint32 uid = 2;
	// GID of secret file
	uint32 gid = 3;
	// Mode is the filesystem mode of secret file
	uint32 mode = 4;
	// Optional defines if secret value is required. Error is produced
	// if value is not found and optional is false.
	bool optional = 5;
}

// SSHOpt defines options describing secret mounts
message SSHOpt {
	// ID of exposed ssh rule. Used for quering the value.
	string ID = 1;
	// UID of agent socket
	uint32 uid = 2;
	// GID of agent socket
	uint32 gid = 3;
	// Mode is the filesystem mode of agent socket
	uint32 mode = 4;
	// Optional defines if ssh socket is required. Error is produced
	// if client does not expose ssh.
	bool optional = 5;
}

// SourceOp specifies a source such as build contexts and images.
message SourceOp {
	// TODO: use source type or any type instead of URL protocol.
	// identifier e.g. local://, docker-image://, git://, https://...
	string identifier = 1;
	// attrs are defined in attr.go
	map<string, string> attrs = 2;
}

// BuildOp is used for nested build invocation.
// BuildOp is experimental and can break without backwards compatibility
message BuildOp {
	int64 builder = 1 [(gogoproto.customtype) = "InputIndex", (gogoproto.nullable) = false];
	map<string, BuildInput> inputs = 2;
	Definition def = 3;
	map<string, string> attrs = 4;
	// outputs
}

// BuildInput is used for BuildOp.
message BuildInput {
	int64 input = 1 [(gogoproto.customtype) = "InputIndex", (gogoproto.nullable) = false];
}

// OpMetadata is a per-vertex metadata entry, which can be defined for arbitrary Op vertex and overridable on the run time.
message OpMetadata {
	// ignore_cache specifies to ignore the cache for this Op.
	bool ignore_cache = 1;
	// Description can be used for keeping any text fields that builder doesn't parse
	map<string, string> description = 2; 
	// index 3 reserved for WorkerConstraint in previous versions
	// WorkerConstraint worker_constraint = 3;
	ExportCache export_cache = 4;
	
	map<string, bool> caps = 5 [(gogoproto.castkey) = "github.com/moby/buildkit/util/apicaps.CapID", (gogoproto.nullable) = false];
}

message ExportCache {
	bool Value = 1;
}

message ProxyEnv {
	string http_proxy = 1;
	string https_proxy = 2;
	string ftp_proxy = 3;
	string no_proxy = 4;
}

// WorkerConstraints defines conditions for the worker
message WorkerConstraints {
	repeated string filter = 1; // containerd-style filter
}

// Definition is the LLB definition structure with per-vertex metadata entries
message Definition {
	// def is a list of marshaled Op messages
	repeated bytes def = 1;
	// metadata contains metadata for the each of the Op messages.
	// A key must be an LLB op digest string. Currently, empty string is not expected as a key, but it may change in the future.
	map<string, OpMetadata> metadata = 2 [(gogoproto.castkey) = "github.com/opencontainers/go-digest.Digest", (gogoproto.nullable) = false];
}

message HostIP {
	string Host = 1;
	string IP = 2;
}

message FileOp {
	repeated FileAction actions = 2;
}

message FileAction {
	int64 input = 1 [(gogoproto.customtype) = "InputIndex", (gogoproto.nullable) = false]; // could be real input or target (target index + max input index)
	int64 secondaryInput = 2 [(gogoproto.customtype) = "InputIndex", (gogoproto.nullable) = false]; // --//--
	int64 output = 3 [(gogoproto.customtype) = "OutputIndex", (gogoproto.nullable) = false];
	oneof action {
		// FileActionCopy copies files from secondaryInput on top of input
		FileActionCopy copy = 4;
		// FileActionMkFile creates a new file
		FileActionMkFile mkfile = 5;
		// FileActionMkDir creates a new directory
		FileActionMkDir mkdir = 6;
		// FileActionRm removes a file
		FileActionRm rm = 7;
	}
}

message FileActionCopy {
	// src is the source path
	string src = 1;
	// dest path
	string dest = 2;
	// optional owner override
	ChownOpt owner = 3;
	// optional permission bits override
	int32 mode = 4;
	// followSymlink resolves symlinks in src
	bool followSymlink = 5;
	// dirCopyContents only copies contents if src is a directory
	bool dirCopyContents = 6;
	// attemptUnpackDockerCompatibility detects if src is an archive to unpack it instead
	bool attemptUnpackDockerCompatibility = 7;
	// createDestPath creates dest path directories if needed
	bool createDestPath = 8;
	// allowWildcard allows filepath.Match wildcards in src path
	bool allowWildcard = 9;
	// allowEmptyWildcard doesn't fail the whole copy if wildcard doesn't resolve to files
	bool allowEmptyWildcard = 10;
	// optional created time override
	int64 timestamp = 11;
}

message FileActionMkFile {
	// path for the new file
	string path = 1;
	// permission bits
	int32 mode = 2;
	// data is the new file contents
	bytes data = 3;
	// optional owner for the new file
	ChownOpt owner = 4;
	// optional created time override
	int64 timestamp = 5;
}

message FileActionMkDir {
	// path for the new directory
	string path = 1;
	// permission bits
	int32 mode = 2;
	// makeParents creates parent directories as well if needed
	bool makeParents = 3;
	// optional owner for the new directory
	ChownOpt owner = 4;
	// optional created time override
	int64 timestamp = 5;
}

message FileActionRm {
	// path to remove
	string path = 1;
	// allowNotFound doesn't fail the rm if file is not found
	bool allowNotFound = 2;
	// allowWildcard allows filepath.Match wildcards in path
	bool allowWildcard = 3;
}

message ChownOpt {
	UserOpt user = 1;
	UserOpt group = 2;
}

message UserOpt {
	oneof user {
		NamedUserOpt byName = 1;
		uint32 byID = 2;
	}
}

message NamedUserOpt {
	string name = 1;
	int64 input = 2 [(gogoproto.customtype) = "InputIndex", (gogoproto.nullable) = false];
}