github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/pkg/api/handlers/compat/images.go (about)

     1  package compat
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io"
     7  	"io/ioutil"
     8  	"net/http"
     9  	"os"
    10  	"strings"
    11  
    12  	"github.com/containers/buildah"
    13  	"github.com/containers/image/v5/manifest"
    14  	"github.com/containers/libpod/libpod"
    15  	image2 "github.com/containers/libpod/libpod/image"
    16  	"github.com/containers/libpod/pkg/api/handlers"
    17  	"github.com/containers/libpod/pkg/api/handlers/utils"
    18  	"github.com/containers/libpod/pkg/domain/entities"
    19  	"github.com/containers/libpod/pkg/util"
    20  	"github.com/docker/docker/api/types"
    21  	"github.com/gorilla/schema"
    22  	"github.com/pkg/errors"
    23  )
    24  
    25  func ExportImage(w http.ResponseWriter, r *http.Request) {
    26  	// 200 ok
    27  	// 500 server
    28  	runtime := r.Context().Value("runtime").(*libpod.Runtime)
    29  
    30  	name := utils.GetName(r)
    31  	newImage, err := runtime.ImageRuntime().NewFromLocal(name)
    32  	if err != nil {
    33  		utils.ImageNotFound(w, name, errors.Wrapf(err, "Failed to find image %s", name))
    34  		return
    35  	}
    36  	tmpfile, err := ioutil.TempFile("", "api.tar")
    37  	if err != nil {
    38  		utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile"))
    39  		return
    40  	}
    41  	if err := tmpfile.Close(); err != nil {
    42  		utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile"))
    43  		return
    44  	}
    45  	if err := newImage.Save(r.Context(), name, "docker-archive", tmpfile.Name(), []string{}, false, false); err != nil {
    46  		utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to save image"))
    47  		return
    48  	}
    49  	rdr, err := os.Open(tmpfile.Name())
    50  	if err != nil {
    51  		utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to read the exported tarfile"))
    52  		return
    53  	}
    54  	defer rdr.Close()
    55  	defer os.Remove(tmpfile.Name())
    56  	utils.WriteResponse(w, http.StatusOK, rdr)
    57  }
    58  
    59  func PruneImages(w http.ResponseWriter, r *http.Request) {
    60  	var (
    61  		filters []string
    62  	)
    63  	decoder := r.Context().Value("decoder").(*schema.Decoder)
    64  	runtime := r.Context().Value("runtime").(*libpod.Runtime)
    65  
    66  	query := struct {
    67  		All     bool
    68  		Filters map[string][]string `schema:"filters"`
    69  	}{
    70  		// This is where you can override the golang default value for one of fields
    71  	}
    72  
    73  	if err := decoder.Decode(&query, r.URL.Query()); err != nil {
    74  		utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
    75  		return
    76  	}
    77  
    78  	idr := []types.ImageDeleteResponseItem{}
    79  	for k, v := range query.Filters {
    80  		for _, val := range v {
    81  			filters = append(filters, fmt.Sprintf("%s=%s", k, val))
    82  		}
    83  	}
    84  	pruneCids, err := runtime.ImageRuntime().PruneImages(r.Context(), query.All, filters)
    85  	if err != nil {
    86  		utils.InternalServerError(w, err)
    87  		return
    88  	}
    89  	for _, p := range pruneCids {
    90  		idr = append(idr, types.ImageDeleteResponseItem{
    91  			Deleted: p,
    92  		})
    93  	}
    94  
    95  	//FIXME/TODO to do this exactly correct, pruneimages needs to return idrs and space-reclaimed, then we are golden
    96  	ipr := types.ImagesPruneReport{
    97  		ImagesDeleted:  idr,
    98  		SpaceReclaimed: 1, // TODO we cannot supply this right now
    99  	}
   100  	utils.WriteResponse(w, http.StatusOK, handlers.ImagesPruneReport{ImagesPruneReport: ipr})
   101  }
   102  
   103  func CommitContainer(w http.ResponseWriter, r *http.Request) {
   104  	var (
   105  		destImage string
   106  	)
   107  	decoder := r.Context().Value("decoder").(*schema.Decoder)
   108  	runtime := r.Context().Value("runtime").(*libpod.Runtime)
   109  
   110  	query := struct {
   111  		Author    string `schema:"author"`
   112  		Changes   string `schema:"changes"`
   113  		Comment   string `schema:"comment"`
   114  		Container string `schema:"container"`
   115  		//fromSrc   string  # fromSrc is currently unused
   116  		Pause bool   `schema:"pause"`
   117  		Repo  string `schema:"repo"`
   118  		Tag   string `schema:"tag"`
   119  	}{
   120  		// This is where you can override the golang default value for one of fields
   121  	}
   122  
   123  	if err := decoder.Decode(&query, r.URL.Query()); err != nil {
   124  		utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
   125  		return
   126  	}
   127  	rtc, err := runtime.GetConfig()
   128  	if err != nil {
   129  		utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
   130  		return
   131  	}
   132  	sc := image2.GetSystemContext(rtc.Engine.SignaturePolicyPath, "", false)
   133  	tag := "latest"
   134  	options := libpod.ContainerCommitOptions{
   135  		Pause: true,
   136  	}
   137  	options.CommitOptions = buildah.CommitOptions{
   138  		SignaturePolicyPath:   rtc.Engine.SignaturePolicyPath,
   139  		ReportWriter:          os.Stderr,
   140  		SystemContext:         sc,
   141  		PreferredManifestType: manifest.DockerV2Schema2MediaType,
   142  	}
   143  
   144  	input := handlers.CreateContainerConfig{}
   145  	if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
   146  		utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
   147  		return
   148  	}
   149  
   150  	if len(query.Tag) > 0 {
   151  		tag = query.Tag
   152  	}
   153  	options.Message = query.Comment
   154  	options.Author = query.Author
   155  	options.Pause = query.Pause
   156  	options.Changes = strings.Fields(query.Changes)
   157  	ctr, err := runtime.LookupContainer(query.Container)
   158  	if err != nil {
   159  		utils.Error(w, "Something went wrong.", http.StatusNotFound, err)
   160  		return
   161  	}
   162  
   163  	// I know mitr hates this ... but doing for now
   164  	if len(query.Repo) > 1 {
   165  		destImage = fmt.Sprintf("%s:%s", query.Repo, tag)
   166  	}
   167  
   168  	commitImage, err := ctr.Commit(r.Context(), destImage, options)
   169  	if err != nil && !strings.Contains(err.Error(), "is not running") {
   170  		utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "CommitFailure"))
   171  		return
   172  	}
   173  	utils.WriteResponse(w, http.StatusOK, handlers.IDResponse{ID: commitImage.ID()}) // nolint
   174  }
   175  
   176  func CreateImageFromSrc(w http.ResponseWriter, r *http.Request) {
   177  	// 200 no error
   178  	// 404 repo does not exist or no read access
   179  	// 500 internal
   180  	decoder := r.Context().Value("decoder").(*schema.Decoder)
   181  	runtime := r.Context().Value("runtime").(*libpod.Runtime)
   182  
   183  	query := struct {
   184  		FromSrc string   `schema:"fromSrc"`
   185  		Changes []string `schema:"changes"`
   186  	}{
   187  		// This is where you can override the golang default value for one of fields
   188  	}
   189  
   190  	if err := decoder.Decode(&query, r.URL.Query()); err != nil {
   191  		utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
   192  		return
   193  	}
   194  	// fromSrc – Source to import. The value may be a URL from which the image can be retrieved or - to read the image from the request body. This parameter may only be used when importing an image.
   195  	source := query.FromSrc
   196  	if source == "-" {
   197  		f, err := ioutil.TempFile("", "api_load.tar")
   198  		if err != nil {
   199  			utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to create tempfile"))
   200  			return
   201  		}
   202  		source = f.Name()
   203  		if err := SaveFromBody(f, r); err != nil {
   204  			utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to write temporary file"))
   205  		}
   206  	}
   207  	iid, err := runtime.Import(r.Context(), source, "", query.Changes, "", false)
   208  	if err != nil {
   209  		utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to import tarball"))
   210  		return
   211  	}
   212  	tmpfile, err := ioutil.TempFile("", "fromsrc.tar")
   213  	if err != nil {
   214  		utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile"))
   215  		return
   216  	}
   217  	if err := tmpfile.Close(); err != nil {
   218  		utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile"))
   219  		return
   220  	}
   221  	// Success
   222  	utils.WriteResponse(w, http.StatusOK, struct {
   223  		Status         string            `json:"status"`
   224  		Progress       string            `json:"progress"`
   225  		ProgressDetail map[string]string `json:"progressDetail"`
   226  		Id             string            `json:"id"`
   227  	}{
   228  		Status:         iid,
   229  		ProgressDetail: map[string]string{},
   230  		Id:             iid,
   231  	})
   232  
   233  }
   234  
   235  func CreateImageFromImage(w http.ResponseWriter, r *http.Request) {
   236  	// 200 no error
   237  	// 404 repo does not exist or no read access
   238  	// 500 internal
   239  	decoder := r.Context().Value("decoder").(*schema.Decoder)
   240  	runtime := r.Context().Value("runtime").(*libpod.Runtime)
   241  
   242  	query := struct {
   243  		FromImage string `schema:"fromImage"`
   244  		Tag       string `schema:"tag"`
   245  	}{
   246  		// This is where you can override the golang default value for one of fields
   247  	}
   248  
   249  	if err := decoder.Decode(&query, r.URL.Query()); err != nil {
   250  		utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
   251  		return
   252  	}
   253  
   254  	/*
   255  	   fromImage – Name of the image to pull. The name may include a tag or digest. This parameter may only be used when pulling an image. The pull is cancelled if the HTTP connection is closed.
   256  	   repo – Repository name given to an image when it is imported. The repo may include a tag. This parameter may only be used when importing an image.
   257  	   tag – Tag or digest. If empty when pulling an image, this causes all tags for the given image to be pulled.
   258  	*/
   259  	fromImage := query.FromImage
   260  	if len(query.Tag) >= 1 {
   261  		fromImage = fmt.Sprintf("%s:%s", fromImage, query.Tag)
   262  	}
   263  
   264  	// TODO
   265  	// We are eating the output right now because we haven't talked about how to deal with multiple responses yet
   266  	img, err := runtime.ImageRuntime().New(r.Context(), fromImage, "", "", nil, &image2.DockerRegistryOptions{}, image2.SigningOptions{}, nil, util.PullImageMissing)
   267  	if err != nil {
   268  		utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
   269  		return
   270  	}
   271  
   272  	// Success
   273  	utils.WriteResponse(w, http.StatusOK, struct {
   274  		Status         string            `json:"status"`
   275  		Error          string            `json:"error"`
   276  		Progress       string            `json:"progress"`
   277  		ProgressDetail map[string]string `json:"progressDetail"`
   278  		Id             string            `json:"id"`
   279  	}{
   280  		Status:         fmt.Sprintf("pulling image (%s) from %s", img.Tag, strings.Join(img.Names(), ", ")),
   281  		ProgressDetail: map[string]string{},
   282  		Id:             img.ID(),
   283  	})
   284  }
   285  
   286  func GetImage(w http.ResponseWriter, r *http.Request) {
   287  	// 200 no error
   288  	// 404 no such
   289  	// 500 internal
   290  	name := utils.GetName(r)
   291  	newImage, err := utils.GetImage(r, name)
   292  	if err != nil {
   293  		utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Wrapf(err, "Failed to find image %s", name))
   294  		return
   295  	}
   296  	inspect, err := handlers.ImageDataToImageInspect(r.Context(), newImage)
   297  	if err != nil {
   298  		utils.Error(w, "Server error", http.StatusInternalServerError, errors.Wrapf(err, "Failed to convert ImageData to ImageInspect '%s'", inspect.ID))
   299  		return
   300  	}
   301  	utils.WriteResponse(w, http.StatusOK, inspect)
   302  }
   303  
   304  func GetImages(w http.ResponseWriter, r *http.Request) {
   305  	images, err := utils.GetImages(w, r)
   306  	if err != nil {
   307  		utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Failed get images"))
   308  		return
   309  	}
   310  	var summaries = make([]*entities.ImageSummary, len(images))
   311  	for j, img := range images {
   312  		is, err := handlers.ImageToImageSummary(img)
   313  		if err != nil {
   314  			utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Failed transform image summaries"))
   315  			return
   316  		}
   317  		summaries[j] = is
   318  	}
   319  	utils.WriteResponse(w, http.StatusOK, summaries)
   320  }
   321  
   322  func LoadImages(w http.ResponseWriter, r *http.Request) {
   323  	// TODO this is basically wrong
   324  	decoder := r.Context().Value("decoder").(*schema.Decoder)
   325  	runtime := r.Context().Value("runtime").(*libpod.Runtime)
   326  
   327  	query := struct {
   328  		Changes map[string]string `json:"changes"`
   329  		Message string            `json:"message"`
   330  		Quiet   bool              `json:"quiet"`
   331  	}{
   332  		// This is where you can override the golang default value for one of fields
   333  	}
   334  
   335  	if err := decoder.Decode(&query, r.URL.Query()); err != nil {
   336  		utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
   337  		return
   338  	}
   339  
   340  	var (
   341  		err    error
   342  		writer io.Writer
   343  	)
   344  	f, err := ioutil.TempFile("", "api_load.tar")
   345  	if err != nil {
   346  		utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to create tempfile"))
   347  		return
   348  	}
   349  	if err := SaveFromBody(f, r); err != nil {
   350  		utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to write temporary file"))
   351  		return
   352  	}
   353  	id, err := runtime.LoadImage(r.Context(), "", f.Name(), writer, "")
   354  	//id, err := runtime.Import(r.Context())
   355  	if err != nil {
   356  		utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to load image"))
   357  		return
   358  	}
   359  	utils.WriteResponse(w, http.StatusOK, struct {
   360  		Stream string `json:"stream"`
   361  	}{
   362  		Stream: fmt.Sprintf("Loaded image: %s\n", id),
   363  	})
   364  }