summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--pkg/api/handlers/compat/containers.go142
-rw-r--r--pkg/api/handlers/compat/containers_logs.go154
-rw-r--r--pkg/api/handlers/compat/images_build.go73
-rw-r--r--pkg/api/server/register_images.go6
-rw-r--r--pkg/bindings/images/images.go3
-rw-r--r--pkg/domain/infra/tunnel/images.go89
6 files changed, 280 insertions, 187 deletions
diff --git a/pkg/api/handlers/compat/containers.go b/pkg/api/handlers/compat/containers.go
index 8ce2180ab..b103e399d 100644
--- a/pkg/api/handlers/compat/containers.go
+++ b/pkg/api/handlers/compat/containers.go
@@ -1,29 +1,21 @@
package compat
import (
- "encoding/binary"
"encoding/json"
"fmt"
- "io"
"net/http"
- "strconv"
"strings"
- "sync"
- "time"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/define"
- "github.com/containers/libpod/libpod/logs"
"github.com/containers/libpod/pkg/api/handlers"
"github.com/containers/libpod/pkg/api/handlers/utils"
"github.com/containers/libpod/pkg/signal"
- "github.com/containers/libpod/pkg/util"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/go-connections/nat"
"github.com/gorilla/schema"
"github.com/pkg/errors"
- log "github.com/sirupsen/logrus"
)
func RemoveContainer(w http.ResponseWriter, r *http.Request) {
@@ -213,140 +205,6 @@ func WaitContainer(w http.ResponseWriter, r *http.Request) {
})
}
-func LogsFromContainer(w http.ResponseWriter, r *http.Request) {
- decoder := r.Context().Value("decoder").(*schema.Decoder)
- runtime := r.Context().Value("runtime").(*libpod.Runtime)
-
- query := struct {
- Follow bool `schema:"follow"`
- Stdout bool `schema:"stdout"`
- Stderr bool `schema:"stderr"`
- Since string `schema:"since"`
- Until string `schema:"until"`
- Timestamps bool `schema:"timestamps"`
- Tail string `schema:"tail"`
- }{
- Tail: "all",
- }
- if err := decoder.Decode(&query, r.URL.Query()); err != nil {
- utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
- return
- }
-
- if !(query.Stdout || query.Stderr) {
- msg := fmt.Sprintf("%s: you must choose at least one stream", http.StatusText(http.StatusBadRequest))
- utils.Error(w, msg, http.StatusBadRequest, errors.Errorf("%s for %s", msg, r.URL.String()))
- return
- }
-
- name := utils.GetName(r)
- ctnr, err := runtime.LookupContainer(name)
- if err != nil {
- utils.ContainerNotFound(w, name, err)
- return
- }
-
- var tail int64 = -1
- if query.Tail != "all" {
- tail, err = strconv.ParseInt(query.Tail, 0, 64)
- if err != nil {
- utils.BadRequest(w, "tail", query.Tail, err)
- return
- }
- }
-
- var since time.Time
- if _, found := r.URL.Query()["since"]; found {
- since, err = util.ParseInputTime(query.Since)
- if err != nil {
- utils.BadRequest(w, "since", query.Since, err)
- return
- }
- }
-
- var until time.Time
- if _, found := r.URL.Query()["until"]; found {
- // FIXME: until != since but the logs backend does not yet support until.
- since, err = util.ParseInputTime(query.Until)
- if err != nil {
- utils.BadRequest(w, "until", query.Until, err)
- return
- }
- }
-
- options := &logs.LogOptions{
- Details: true,
- Follow: query.Follow,
- Since: since,
- Tail: tail,
- Timestamps: query.Timestamps,
- }
-
- var wg sync.WaitGroup
- options.WaitGroup = &wg
-
- logChannel := make(chan *logs.LogLine, tail+1)
- if err := runtime.Log([]*libpod.Container{ctnr}, options, logChannel); err != nil {
- utils.InternalServerError(w, errors.Wrapf(err, "Failed to obtain logs for Container '%s'", name))
- return
- }
- go func() {
- wg.Wait()
- close(logChannel)
- }()
-
- w.WriteHeader(http.StatusOK)
-
- var frame strings.Builder
- header := make([]byte, 8)
- for ok := true; ok; ok = query.Follow {
- for line := range logChannel {
- if _, found := r.URL.Query()["until"]; found {
- if line.Time.After(until) {
- break
- }
- }
-
- // Reset buffer we're ready to loop again
- frame.Reset()
- switch line.Device {
- case "stdout":
- if !query.Stdout {
- continue
- }
- header[0] = 1
- case "stderr":
- if !query.Stderr {
- continue
- }
- header[0] = 2
- default:
- // Logging and moving on is the best we can do here. We may have already sent
- // a Status and Content-Type to client therefore we can no longer report an error.
- log.Infof("unknown Device type '%s' in log file from Container %s", line.Device, ctnr.ID())
- continue
- }
-
- if query.Timestamps {
- frame.WriteString(line.Time.Format(time.RFC3339))
- frame.WriteString(" ")
- }
- frame.WriteString(line.Msg)
-
- binary.BigEndian.PutUint32(header[4:], uint32(frame.Len()))
- if _, err := w.Write(header[0:8]); err != nil {
- log.Errorf("unable to write log output header: %q", err)
- }
- if _, err := io.WriteString(w, frame.String()); err != nil {
- log.Errorf("unable to write frame string: %q", err)
- }
- if flusher, ok := w.(http.Flusher); ok {
- flusher.Flush()
- }
- }
- }
-}
-
func LibpodToContainer(l *libpod.Container, sz bool) (*handlers.Container, error) {
imageID, imageName := l.Image()
diff --git a/pkg/api/handlers/compat/containers_logs.go b/pkg/api/handlers/compat/containers_logs.go
new file mode 100644
index 000000000..3b25a3ecc
--- /dev/null
+++ b/pkg/api/handlers/compat/containers_logs.go
@@ -0,0 +1,154 @@
+package compat
+
+import (
+ "encoding/binary"
+ "fmt"
+ "io"
+ "net/http"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/logs"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/containers/libpod/pkg/util"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+ log "github.com/sirupsen/logrus"
+)
+
+func LogsFromContainer(w http.ResponseWriter, r *http.Request) {
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+
+ query := struct {
+ Follow bool `schema:"follow"`
+ Stdout bool `schema:"stdout"`
+ Stderr bool `schema:"stderr"`
+ Since string `schema:"since"`
+ Until string `schema:"until"`
+ Timestamps bool `schema:"timestamps"`
+ Tail string `schema:"tail"`
+ }{
+ Tail: "all",
+ }
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+
+ if !(query.Stdout || query.Stderr) {
+ msg := fmt.Sprintf("%s: you must choose at least one stream", http.StatusText(http.StatusBadRequest))
+ utils.Error(w, msg, http.StatusBadRequest, errors.Errorf("%s for %s", msg, r.URL.String()))
+ return
+ }
+
+ name := utils.GetName(r)
+ ctnr, err := runtime.LookupContainer(name)
+ if err != nil {
+ utils.ContainerNotFound(w, name, err)
+ return
+ }
+
+ var tail int64 = -1
+ if query.Tail != "all" {
+ tail, err = strconv.ParseInt(query.Tail, 0, 64)
+ if err != nil {
+ utils.BadRequest(w, "tail", query.Tail, err)
+ return
+ }
+ }
+
+ var since time.Time
+ if _, found := r.URL.Query()["since"]; found {
+ since, err = util.ParseInputTime(query.Since)
+ if err != nil {
+ utils.BadRequest(w, "since", query.Since, err)
+ return
+ }
+ }
+
+ var until time.Time
+ if _, found := r.URL.Query()["until"]; found {
+ // FIXME: until != since but the logs backend does not yet support until.
+ since, err = util.ParseInputTime(query.Until)
+ if err != nil {
+ utils.BadRequest(w, "until", query.Until, err)
+ return
+ }
+ }
+
+ options := &logs.LogOptions{
+ Details: true,
+ Follow: query.Follow,
+ Since: since,
+ Tail: tail,
+ Timestamps: query.Timestamps,
+ }
+
+ var wg sync.WaitGroup
+ options.WaitGroup = &wg
+
+ logChannel := make(chan *logs.LogLine, tail+1)
+ if err := runtime.Log([]*libpod.Container{ctnr}, options, logChannel); err != nil {
+ utils.InternalServerError(w, errors.Wrapf(err, "Failed to obtain logs for Container '%s'", name))
+ return
+ }
+ go func() {
+ wg.Wait()
+ close(logChannel)
+ }()
+
+ w.WriteHeader(http.StatusOK)
+
+ var frame strings.Builder
+ header := make([]byte, 8)
+ for ok := true; ok; ok = query.Follow {
+ for line := range logChannel {
+ if _, found := r.URL.Query()["until"]; found {
+ if line.Time.After(until) {
+ break
+ }
+ }
+
+ // Reset buffer we're ready to loop again
+ frame.Reset()
+ switch line.Device {
+ case "stdout":
+ if !query.Stdout {
+ continue
+ }
+ header[0] = 1
+ case "stderr":
+ if !query.Stderr {
+ continue
+ }
+ header[0] = 2
+ default:
+ // Logging and moving on is the best we can do here. We may have already sent
+ // a Status and Content-Type to client therefore we can no longer report an error.
+ log.Infof("unknown Device type '%s' in log file from Container %s", line.Device, ctnr.ID())
+ continue
+ }
+
+ if query.Timestamps {
+ frame.WriteString(line.Time.Format(time.RFC3339))
+ frame.WriteString(" ")
+ }
+ frame.WriteString(line.Msg)
+
+ binary.BigEndian.PutUint32(header[4:], uint32(frame.Len()))
+ if _, err := w.Write(header[0:8]); err != nil {
+ log.Errorf("unable to write log output header: %q", err)
+ }
+ if _, err := io.WriteString(w, frame.String()); err != nil {
+ log.Errorf("unable to write frame string: %q", err)
+ }
+ if flusher, ok := w.(http.Flusher); ok {
+ flusher.Flush()
+ }
+ }
+ }
+}
diff --git a/pkg/api/handlers/compat/images_build.go b/pkg/api/handlers/compat/images_build.go
index 913994f46..f967acf32 100644
--- a/pkg/api/handlers/compat/images_build.go
+++ b/pkg/api/handlers/compat/images_build.go
@@ -48,34 +48,34 @@ func BuildImage(w http.ResponseWriter, r *http.Request) {
defer os.RemoveAll(anchorDir)
query := struct {
- Dockerfile string `schema:"dockerfile"`
- Tag string `schema:"t"`
- ExtraHosts string `schema:"extrahosts"`
- Remote string `schema:"remote"`
- Quiet bool `schema:"q"`
- NoCache bool `schema:"nocache"`
- CacheFrom string `schema:"cachefrom"`
- Pull bool `schema:"pull"`
- Rm bool `schema:"rm"`
- ForceRm bool `schema:"forcerm"`
- Memory int64 `schema:"memory"`
- MemSwap int64 `schema:"memswap"`
- CpuShares uint64 `schema:"cpushares"` //nolint
- CpuSetCpus string `schema:"cpusetcpus"` //nolint
- CpuPeriod uint64 `schema:"cpuperiod"` //nolint
- CpuQuota int64 `schema:"cpuquota"` //nolint
- BuildArgs string `schema:"buildargs"`
- ShmSize int `schema:"shmsize"`
- Squash bool `schema:"squash"`
- Labels string `schema:"labels"`
- NetworkMode string `schema:"networkmode"`
- Platform string `schema:"platform"`
- Target string `schema:"target"`
- Outputs string `schema:"outputs"`
- Registry string `schema:"registry"`
+ Dockerfile string `schema:"dockerfile"`
+ Tag []string `schema:"t"`
+ ExtraHosts string `schema:"extrahosts"`
+ Remote string `schema:"remote"`
+ Quiet bool `schema:"q"`
+ NoCache bool `schema:"nocache"`
+ CacheFrom string `schema:"cachefrom"`
+ Pull bool `schema:"pull"`
+ Rm bool `schema:"rm"`
+ ForceRm bool `schema:"forcerm"`
+ Memory int64 `schema:"memory"`
+ MemSwap int64 `schema:"memswap"`
+ CpuShares uint64 `schema:"cpushares"` //nolint
+ CpuSetCpus string `schema:"cpusetcpus"` //nolint
+ CpuPeriod uint64 `schema:"cpuperiod"` //nolint
+ CpuQuota int64 `schema:"cpuquota"` //nolint
+ BuildArgs string `schema:"buildargs"`
+ ShmSize int `schema:"shmsize"`
+ Squash bool `schema:"squash"`
+ Labels string `schema:"labels"`
+ NetworkMode string `schema:"networkmode"`
+ Platform string `schema:"platform"`
+ Target string `schema:"target"`
+ Outputs string `schema:"outputs"`
+ Registry string `schema:"registry"`
}{
Dockerfile: "Dockerfile",
- Tag: "",
+ Tag: []string{},
ExtraHosts: "",
Remote: "",
Quiet: false,
@@ -107,20 +107,19 @@ func BuildImage(w http.ResponseWriter, r *http.Request) {
}
var (
- // Tag is the name with optional tag...
- name = query.Tag
- tag = "latest"
+ output string
+ additionalNames []string
)
- if strings.Contains(query.Tag, ":") {
- tokens := strings.SplitN(query.Tag, ":", 2)
- name = tokens[0]
- tag = tokens[1]
+ if len(query.Tag) > 0 {
+ output = query.Tag[0]
+ }
+ if len(query.Tag) > 1 {
+ additionalNames = query.Tag[1:]
}
if _, found := r.URL.Query()["target"]; found {
- name = query.Target
+ output = query.Target
}
-
var buildArgs = map[string]string{}
if _, found := r.URL.Query()["buildargs"]; found {
if err := json.Unmarshal([]byte(query.BuildArgs), &buildArgs); err != nil {
@@ -168,8 +167,8 @@ func BuildImage(w http.ResponseWriter, r *http.Request) {
TransientMounts: nil,
Compression: archive.Gzip,
Args: buildArgs,
- Output: name,
- AdditionalTags: []string{tag},
+ Output: output,
+ AdditionalTags: additionalNames,
Log: func(format string, args ...interface{}) {
buildEvents = append(buildEvents, fmt.Sprintf(format, args...))
},
diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go
index 83584d0f3..754cb1b75 100644
--- a/pkg/api/server/register_images.go
+++ b/pkg/api/server/register_images.go
@@ -410,7 +410,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// swagger:operation POST /build compat buildImage
// ---
// tags:
- // - images
+ // - images (compat)
// summary: Create image
// description: Build an image from the given Dockerfile(s)
// parameters:
@@ -425,7 +425,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// name: t
// type: string
// default: latest
- // description: A name and optional tag to apply to the image in the `name:tag` format.
+ // description: A name and optional tag to apply to the image in the `name:tag` format. If you omit the tag the default latest value is assumed. You can provide several t parameters.
// - in: query
// name: extrahosts
// type: string
@@ -1211,7 +1211,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// name: t
// type: string
// default: latest
- // description: A name and optional tag to apply to the image in the `name:tag` format.
+ // description: A name and optional tag to apply to the image in the `name:tag` format. If you omit the tag the default latest value is assumed. You can provide several t parameters.
// - in: query
// name: extrahosts
// type: string
diff --git a/pkg/bindings/images/images.go b/pkg/bindings/images/images.go
index 9cb6a0ac5..bc2d116f3 100644
--- a/pkg/bindings/images/images.go
+++ b/pkg/bindings/images/images.go
@@ -229,6 +229,9 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO
if t := options.Output; len(t) > 0 {
params.Set("t", t)
}
+ for _, tag := range options.AdditionalTags {
+ params.Add("t", tag)
+ }
// TODO Remote, Quiet
if options.NoCache {
params.Set("nocache", "1")
diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go
index 9ddc5f1a9..35189cb0a 100644
--- a/pkg/domain/infra/tunnel/images.go
+++ b/pkg/domain/infra/tunnel/images.go
@@ -1,10 +1,13 @@
package tunnel
import (
+ "archive/tar"
+ "bytes"
"context"
+ "io"
"io/ioutil"
"os"
- "path"
+ "path/filepath"
"strings"
"github.com/containers/common/pkg/config"
@@ -16,6 +19,7 @@ import (
utils2 "github.com/containers/libpod/utils"
"github.com/containers/storage/pkg/archive"
"github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
)
func (ir *ImageEngine) Exists(_ context.Context, nameOrID string) (*entities.BoolReport, error) {
@@ -276,14 +280,27 @@ func (ir *ImageEngine) Config(_ context.Context) (*config.Config, error) {
}
func (ir *ImageEngine) Build(ctx context.Context, containerFiles []string, opts entities.BuildOptions) (*entities.BuildReport, error) {
- if len(containerFiles) > 1 {
- return nil, errors.New("something")
+ var tarReader io.Reader
+ tarfile, err := archive.Tar(opts.ContextDirectory, 0)
+ if err != nil {
+ return nil, err
}
- tarfile, err := archive.Tar(path.Base(containerFiles[0]), 0)
+ tarReader = tarfile
+ cwd, err := os.Getwd()
if err != nil {
return nil, err
}
- return images.Build(ir.ClientCxt, containerFiles, opts, tarfile)
+ if cwd != opts.ContextDirectory {
+ fn := func(h *tar.Header, r io.Reader) (data []byte, update bool, skip bool, err error) {
+ h.Name = filepath.Join(filepath.Base(opts.ContextDirectory), h.Name)
+ return nil, false, false, nil
+ }
+ tarReader, err = transformArchive(tarfile, false, fn)
+ if err != nil {
+ return nil, err
+ }
+ }
+ return images.Build(ir.ClientCxt, containerFiles, opts, tarReader)
}
func (ir *ImageEngine) Tree(ctx context.Context, nameOrID string, opts entities.ImageTreeOptions) (*entities.ImageTreeReport, error) {
@@ -297,3 +314,65 @@ func (ir *ImageEngine) Shutdown(_ context.Context) {
func (ir *ImageEngine) Sign(ctx context.Context, names []string, options entities.SignOptions) (*entities.SignReport, error) {
return nil, errors.New("not implemented yet")
}
+
+// Sourced from openshift image builder
+
+// TransformFileFunc is given a chance to transform an arbitrary input file.
+type TransformFileFunc func(h *tar.Header, r io.Reader) (data []byte, update bool, skip bool, err error)
+
+// filterArchive transforms the provided input archive to a new archive,
+// giving the fn a chance to transform arbitrary files.
+func filterArchive(r io.Reader, w io.Writer, fn TransformFileFunc) error {
+ tr := tar.NewReader(r)
+ tw := tar.NewWriter(w)
+
+ var body io.Reader = tr
+
+ for {
+ h, err := tr.Next()
+ if err == io.EOF {
+ return tw.Close()
+ }
+ if err != nil {
+ return err
+ }
+
+ name := h.Name
+ data, ok, skip, err := fn(h, tr)
+ logrus.Debugf("Transform %q -> %q: data=%t ok=%t skip=%t err=%v", name, h.Name, data != nil, ok, skip, err)
+ if err != nil {
+ return err
+ }
+ if skip {
+ continue
+ }
+ if ok {
+ h.Size = int64(len(data))
+ body = bytes.NewBuffer(data)
+ }
+ if err := tw.WriteHeader(h); err != nil {
+ return err
+ }
+ if _, err := io.Copy(tw, body); err != nil {
+ return err
+ }
+ }
+}
+
+func transformArchive(r io.Reader, compressed bool, fn TransformFileFunc) (io.Reader, error) {
+ var cwe error
+ pr, pw := io.Pipe()
+ go func() {
+ if compressed {
+ in, err := archive.DecompressStream(r)
+ if err != nil {
+ cwe = pw.CloseWithError(err)
+ return
+ }
+ r = in
+ }
+ err := filterArchive(r, pw, fn)
+ cwe = pw.CloseWithError(err)
+ }()
+ return pr, cwe
+}