buildkit/client/llb/source.go

265 lines
5.0 KiB
Go

package llb
import (
"context"
_ "crypto/sha256"
"encoding/json"
"os"
"strconv"
"strings"
"github.com/docker/distribution/reference"
"github.com/moby/buildkit/solver/pb"
digest "github.com/opencontainers/go-digest"
"github.com/pkg/errors"
)
type SourceOp struct {
id string
attrs map[string]string
output Output
cachedPB []byte
cachedOpMetadata *pb.OpMetadata
err error
}
func NewSource(id string, attrs map[string]string) *SourceOp {
s := &SourceOp{
id: id,
attrs: attrs,
}
s.output = &output{vertex: s}
return s
}
func (s *SourceOp) Validate() error {
if s.err != nil {
return s.err
}
if s.id == "" {
return errors.Errorf("source identifier can't be empty")
}
return nil
}
func (s *SourceOp) Marshal() ([]byte, *pb.OpMetadata, error) {
if s.cachedPB != nil {
return s.cachedPB, s.cachedOpMetadata, nil
}
if err := s.Validate(); err != nil {
return nil, nil, err
}
proto := &pb.Op{
Op: &pb.Op_Source{
Source: &pb.SourceOp{Identifier: s.id, Attrs: s.attrs},
},
}
dt, err := proto.Marshal()
if err != nil {
return nil, nil, err
}
s.cachedPB = dt
s.cachedOpMetadata = &pb.OpMetadata{}
return dt, s.cachedOpMetadata, nil
}
func (s *SourceOp) Output() Output {
return s.output
}
func (s *SourceOp) Inputs() []Output {
return nil
}
func Source(id string) State {
return NewState(NewSource(id, nil).Output())
}
func Image(ref string, opts ...ImageOption) State {
r, err := reference.ParseNormalizedNamed(ref)
if err == nil {
ref = reference.TagNameOnly(r).String()
}
src := NewSource("docker-image://"+ref, nil) // controversial
if err != nil {
src.err = err
}
var info ImageInfo
for _, opt := range opts {
opt(&info)
}
if info.metaResolver != nil {
_, dt, err := info.metaResolver.ResolveImageConfig(context.TODO(), ref)
if err != nil {
src.err = err
} else {
var img struct {
Config struct {
Env []string `json:"Env,omitempty"`
WorkingDir string `json:"WorkingDir,omitempty"`
User string `json:"User,omitempty"`
} `json:"config,omitempty"`
}
if err := json.Unmarshal(dt, &img); err != nil {
src.err = err
} else {
st := NewState(src.Output())
for _, env := range img.Config.Env {
parts := strings.SplitN(env, "=", 2)
if len(parts[0]) > 0 {
var v string
if len(parts) > 1 {
v = parts[1]
}
st = st.AddEnv(parts[0], v)
}
}
st = st.Dir(img.Config.WorkingDir)
return st
}
}
}
return NewState(src.Output())
}
type ImageOption func(*ImageInfo)
type ImageInfo struct {
metaResolver ImageMetaResolver
}
func Git(remote, ref string, opts ...GitOption) State {
id := remote
if ref != "" {
id += "#" + ref
}
gi := &GitInfo{}
for _, o := range opts {
o(gi)
}
attrs := map[string]string{}
if gi.KeepGitDir {
attrs[pb.AttrKeepGitDir] = "true"
}
source := NewSource("git://"+id, attrs)
return NewState(source.Output())
}
type GitOption func(*GitInfo)
type GitInfo struct {
KeepGitDir bool
}
func KeepGitDir() GitOption {
return func(gi *GitInfo) {
gi.KeepGitDir = true
}
}
func Scratch() State {
return NewState(nil)
}
func Local(name string, opts ...LocalOption) State {
gi := &LocalInfo{}
for _, o := range opts {
o(gi)
}
attrs := map[string]string{}
if gi.SessionID != "" {
attrs[pb.AttrLocalSessionID] = gi.SessionID
}
if gi.IncludePatterns != "" {
attrs[pb.AttrIncludePatterns] = gi.IncludePatterns
}
source := NewSource("local://"+name, attrs)
return NewState(source.Output())
}
type LocalOption func(*LocalInfo)
func SessionID(id string) LocalOption {
return func(li *LocalInfo) {
li.SessionID = id
}
}
func IncludePatterns(p []string) LocalOption {
return func(li *LocalInfo) {
dt, _ := json.Marshal(p) // empty on error
li.IncludePatterns = string(dt)
}
}
type LocalInfo struct {
SessionID string
IncludePatterns string
}
func HTTP(url string, opts ...HTTPOption) State {
hi := &HTTPInfo{}
for _, o := range opts {
o(hi)
}
attrs := map[string]string{}
if hi.Checksum != "" {
attrs[pb.AttrHTTPChecksum] = hi.Checksum.String()
}
if hi.Filename != "" {
attrs[pb.AttrHTTPFilename] = hi.Filename
}
if hi.Perm != 0 {
attrs[pb.AttrHTTPPerm] = "0" + strconv.FormatInt(int64(hi.Perm), 8)
}
if hi.UID != 0 {
attrs[pb.AttrHTTPUID] = strconv.Itoa(hi.UID)
}
if hi.UID != 0 {
attrs[pb.AttrHTTPGID] = strconv.Itoa(hi.GID)
}
source := NewSource(url, attrs)
return NewState(source.Output())
}
type HTTPInfo struct {
Checksum digest.Digest
Filename string
Perm int
UID int
GID int
}
type HTTPOption func(*HTTPInfo)
func Checksum(dgst digest.Digest) HTTPOption {
return func(hi *HTTPInfo) {
hi.Checksum = dgst
}
}
func Chmod(perm os.FileMode) HTTPOption {
return func(hi *HTTPInfo) {
hi.Perm = int(perm) & 0777
}
}
func Filename(name string) HTTPOption {
return func(hi *HTTPInfo) {
hi.Filename = name
}
}
func Chown(uid, gid int) HTTPOption {
return func(hi *HTTPInfo) {
hi.UID = uid
hi.GID = gid
}
}