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 } }