{"id":409743,"date":"2024-06-29T20:58:45","date_gmt":"2024-06-29T20:58:45","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=409743"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=409743","title":{"rendered":"<span>\u042d\u0432\u043e\u043b\u044e\u0446\u0438\u044f Docker. \u0427\u0430\u0441\u0442\u044c 2.2<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<h2>\u0412\u0441\u0442\u0443\u043f\u043b\u0435\u043d\u0438\u0435<\/h2>\n<p>\u0414\u0430\u043d\u043d\u0430\u044f \u0441\u0442\u0430\u0442\u044c\u044f \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0442\u0440\u0435\u0442\u044c\u0435\u0439 \u0432 \u0446\u0438\u043a\u043b\u0435 (<a href=\"https:\/\/habr.com\/ru\/post\/573828\/\" rel=\"noopener noreferrer nofollow\">1<\/a>,<a href=\"https:\/\/habr.com\/ru\/post\/574750\/\" rel=\"noopener noreferrer nofollow\">2<\/a>), \u043f\u043e\u0441\u0432\u044f\u0449\u0435\u043d\u043d\u043e\u043c \u0438\u0437\u0443\u0447\u0435\u043d\u0438\u044e \u0438\u0441\u0445\u043e\u0434\u043d\u043e\u0433\u043e \u043a\u043e\u0434\u0430 Docker \u0438 \u043f\u0440\u044f\u043c\u044b\u043c \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0435\u043d\u0438\u0435\u043c <a href=\"https:\/\/habr.com\/ru\/post\/574750\/\" rel=\"noopener noreferrer nofollow\">\u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0439 \u0441\u0442\u0430\u0442\u044c\u0438<\/a>, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u043c\u044b \u043d\u0430\u0447\u0430\u043b\u0438 \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u0442\u044c \u043a\u043e\u0434 \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u043e\u0433\u043e \u0440\u0435\u043b\u0438\u0437\u0430 Docker v0.1.0. \u0412 \u044d\u0442\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0435\u043d\u0430 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0432\u0441\u0435\u0445 \u043a\u043e\u043c\u0430\u043d\u0434, \u0430 \u0432 \u043a\u043e\u043d\u0446\u0435, \u043c\u044b \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043e\u0431\u0440\u0430\u0437 \u0438 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u043c \u0434\u043e\u043a\u0435\u0440 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u043d\u0430 \u0435\u0433\u043e \u043e\u0441\u043d\u043e\u0432\u0435. \u0414\u043b\u044f \u0443\u0434\u043e\u0431\u0441\u0442\u0432\u0430 \u044f \u043f\u043e\u0441\u0442\u0430\u0440\u0430\u043b\u0441\u044f \u0440\u0430\u0437\u0431\u0438\u0442\u044c \u0441\u043f\u0438\u0441\u043e\u043a \u043a\u043e\u043c\u0430\u043d\u0434 \u043d\u0430 \u0443\u0441\u043b\u043e\u0432\u043d\u044b\u0435 \u0433\u0440\u0443\u043f\u043f\u044b: \u0440\u0430\u0431\u043e\u0442\u0430 \u0441 \u043e\u0431\u0440\u0430\u0437\u0430\u043c\u0438, \u0440\u0430\u0431\u043e\u0442\u0430 \u0441 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430\u043c\u0438, \u0441\u0435\u0442\u0435\u0432\u043e\u0439 \u0441\u0442\u0435\u043a \u0438 \u0442.\u0434.\u00a0<\/p>\n<p>\u0410 \u0442\u0435\u043f\u0435\u0440\u044c, \u043a\u0430\u043a \u0433\u043e\u0432\u043e\u0440\u0438\u0442\u0441\u044f, \u201cwithout further ado\u201d, \u043f\u0440\u0438\u0441\u0442\u0443\u043f\u0438\u043c \u043a \u0438\u0437\u0443\u0447\u0435\u043d\u0438\u044e \u043a\u043e\u0434\u0430 \u0438\u0437 \u0444\u0430\u0439\u043b\u0430 commands.go \u043d\u0430\u0447\u0438\u043d\u0430\u044f \u0441 \u043a\u043e\u043c\u0430\u043d\u0434 \u0434\u043b\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043e\u0431\u0440\u0430\u0437\u0430\u043c\u0438 (images).<\/p>\n<h3>\u0423\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043e\u0431\u0440\u0430\u0437\u0430\u043c\u0438<\/h3>\n<h4>Import<\/h4>\n<p>\u041a\u043e\u043c\u0430\u043d\u0434\u0430 import \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043e\u0431\u0440\u0430\u0437 \u0444\u0430\u0439\u043b\u043e\u0432\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u0438\u0437 tar \u0430\u0440\u0445\u0438\u0432\u0430, \u043f\u043e\u0434\u0430\u0432\u0430\u0435\u043c\u043e\u0433\u043e \u043d\u0430 stdin, \u0438\u043b\u0438 \u0436\u0435 \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0435\u0433\u043e \u043f\u043e url:<\/p>\n<details class=\"spoiler\">\n<summary>CmdImport<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">func (srv *Server) CmdImport(stdin io.ReadCloser, stdout io.Writer, args ...string) error { cmd := rcli.Subcmd(stdout, \"import\", \"[OPTIONS] URL|- [REPOSITORY [TAG]]\", \"Create a new filesystem image from the contents of a tarball\") var archive io.Reader var resp *http.Response  if err := cmd.Parse(args); err != nil { return nil } src := cmd.Arg(0) if src == \"\" { return errors.New(\"Not enough arguments\") } else if src == \"-\" { archive = stdin } else { u, err := url.Parse(src) if err != nil { return err } if u.Scheme == \"\" { u.Scheme = \"http\" u.Host = src u.Path = \"\" } fmt.Fprintf(stdout, \"Downloading from %s\\n\", u.String()) \/\/ Download with curl (pretty progress bar) \/\/ If curl is not available, fallback to http.Get() resp, err = Download(u.String(), stdout) if err != nil { return err } archive = ProgressReader(resp.Body, int(resp.ContentLength), stdout) } img, err := srv.runtime.graph.Create(archive, nil, \"Imported from \"+src) if err != nil { return err } \/\/ Optionally register the image at REPO\/TAG if repository := cmd.Arg(1); repository != \"\" { tag := cmd.Arg(2) \/\/ Repository will handle an empty tag properly if err := srv.runtime.repositories.Set(repository, tag, img.Id, true); err != nil { return err } } fmt.Fprintln(stdout, img.Id) return nil }<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u043e\u0433\u043e \u0440\u0430\u0437\u0431\u043e\u0440\u0430 \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442\u043e\u0432 \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442 \u043c\u0435\u0441\u0442\u043e \u043e\u0442\u043a\u0443\u0434\u0430 \u043d\u0443\u0436\u043d\u043e \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043e\u0431\u0440\u0430\u0437: &#171;-&#187; \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442 stdin, \u0432 \u0434\u0440\u0443\u0433\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442 \u0440\u0430\u0441\u0441\u043c\u0430\u0442\u0440\u0438\u0432\u0430\u0435\u0442\u0441\u044f, \u043a\u0430\u043a url. \u0425\u0435\u043b\u043f\u0435\u0440 \u043c\u0435\u0442\u043e\u0434\u044b Download \u0438 ProgressReader \u0434\u043b\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0430\u0440\u0445\u0438\u0432\u0430 \u043f\u043e http \u043d\u0430\u0445\u043e\u0434\u044f\u0442\u0441\u044f \u0432 \u0444\u0430\u0439\u043b\u0435 utils.go:<\/p>\n<details class=\"spoiler\">\n<summary>utils.go<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">\/\/ Request a given URL and return an io.Reader func Download(url string, stderr io.Writer) (*http.Response, error) { var resp *http.Response var err error = nil if resp, err = http.Get(url); err != nil { return nil, err } if resp.StatusCode >= 400 { return nil, errors.New(\"Got HTTP status code >= 400: \" + resp.Status) } return resp, nil }   type progressReader struct { reader        io.ReadCloser \/\/ Stream to read from output        io.Writer     \/\/ Where to send progress bar to read_total    int           \/\/ Expected stream length (bytes) read_progress int           \/\/ How much has been read so far (bytes) last_update   int           \/\/ How many bytes read at least update }  func (r *progressReader) Read(p []byte) (n int, err error) { read, err := io.ReadCloser(r.reader).Read(p) r.read_progress += read  \/\/ Only update progress for every 1% read update_every := int(0.01 * float64(r.read_total)) if r.read_progress-r.last_update > update_every || r.read_progress == r.read_total { fmt.Fprintf(r.output, \"%d\/%d (%.0f%%)\\r\", r.read_progress, r.read_total, float64(r.read_progress)\/float64(r.read_total)*100) r.last_update = r.read_progress } \/\/ Send newline when complete if err == io.EOF { fmt.Fprintf(r.output, \"\\n\") }  return read, err } func (r *progressReader) Close() error { return io.ReadCloser(r.reader).Close() } func ProgressReader(r io.ReadCloser, size int, output io.Writer) *progressReader { return &amp;progressReader{r, output, size, 0, 0} } <\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u0414\u0430\u043b\u0435\u0435 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u0442 \u0432 \u0444\u0443\u043d\u043a\u0446\u0438\u044e graph.Create \u0438\u0437 \u0444\u0430\u0439\u043b\u0430 graph.go:<\/p>\n<details class=\"spoiler\">\n<summary>graph.Create<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">func (graph *Graph) Create(layerData Archive, container *Container, comment string) (*Image, error) { img := &amp;Image{ Id:      GenerateId(), Comment: comment, Created: time.Now(), } if container != nil { img.Parent = container.Image img.Container = container.Id img.ContainerConfig = *container.Config } if err := graph.Register(layerData, img); err != nil { return nil, err } return img, nil }<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u0417\u0434\u0435\u0441\u044c \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u044b\u0439 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u043e\u0431\u0440\u0430\u0437\u0430 \u0438 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 Image, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0434\u0430\u043b\u0435\u0435 \u0432\u043c\u0435\u0441\u0442\u0435 \u0441 \u0434\u0430\u043d\u043d\u044b\u043c\u0438 \u0430\u0440\u0445\u0438\u0432\u0430 \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u0442\u0441\u044f \u0432 \u043c\u0435\u0442\u043e\u0434 graph.Register. \u0415\u0441\u043b\u0438 \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u043d \u0438 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440, \u0442\u043e \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u0435\u0433\u043e \u043e\u0431\u0440\u0430\u0437 \u0431\u0443\u0434\u0435\u0442 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0430 \u0432 \u043f\u043e\u043b\u0435 img.Parent &#8212; \u044d\u0442\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0432 \u043a\u043e\u043c\u0430\u043d\u0434\u0435 Commit, \u0441\u043e\u0437\u0434\u0430\u044e\u0449\u0435\u0439 \u043d\u043e\u0432\u044b\u0439 \u043e\u0431\u0440\u0430\u0437 \u0438\u0437 \u0442\u0435\u043a\u0443\u0449\u0435\u0433\u043e \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430. \u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 Image \u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0434\u043b\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 Id \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 SHA256 \u043d\u0430\u0445\u043e\u0434\u044f\u0442\u0441\u044f \u0432 \u0444\u0430\u0439\u043b\u0435 image.go:<\/p>\n<details class=\"spoiler\">\n<summary>image.go<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">type Image struct { Id              string    `json:\"id\"` Parent          string    `json:\"parent,omitempty\"` Comment         string    `json:\"comment,omitempty\"` Created         time.Time `json:\"created\"` Container       string    `json:\"container,omitempty\"` ContainerConfig Config    `json:\"container_config,omitempty\"` graph           *Graph }  func GenerateId() string { \/\/ FIXME: don't seed every time rand.Seed(time.Now().UTC().UnixNano()) randomBytes := bytes.NewBuffer([]byte(fmt.Sprintf(\"%x\", rand.Int()))) id, _ := ComputeId(randomBytes) \/\/ can't fail return id }  \/\/ ComputeId reads from `content` until EOF, then returns a SHA of what it read, as a string. func ComputeId(content io.Reader) (string, error) { h := sha256.New() if _, err := io.Copy(h, content); err != nil { return \"\", err } return fmt.Sprintf(\"%x\", h.Sum(nil)[:8]), nil } <\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u0414\u0430\u043b\u0435\u0435 \u0432\u0437\u0433\u043b\u044f\u043d\u0435\u043c \u043d\u0430 \u043c\u0435\u0442\u043e\u0434 graph.Register:<\/p>\n<details class=\"spoiler\">\n<summary>graph.Register<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">func (graph *Graph) Register(layerData Archive, img *Image) error { if err := ValidateId(img.Id); err != nil { return err } \/\/ (This is a convenience to save time. Race conditions are taken care of by os.Rename) if graph.Exists(img.Id) { return fmt.Errorf(\"Image %s already exists\", img.Id) } tmp, err := graph.Mktemp(img.Id) defer os.RemoveAll(tmp) if err != nil { return fmt.Errorf(\"Mktemp failed: %s\", err) } if err := StoreImage(img, layerData, tmp); err != nil { return err } \/\/ Commit if err := os.Rename(tmp, graph.imageRoot(img.Id)); err != nil { return err } img.graph = graph return nil }<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438 id \u043d\u0430 \u043d\u0430\u043b\u0438\u0447\u0438\u0435 \u0437\u0430\u043f\u0440\u0435\u0449\u0435\u043d\u043d\u043e\u0433\u043e \u0441\u0438\u043c\u0432\u043e\u043b\u0430 &#171;:&#187; (\u0442\u0430\u043a \u043a\u0430\u043a \u043e\u043d \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0440\u0430\u0437\u0434\u0435\u043b\u0438\u0442\u0435\u043b\u0435\u043c \u0434\u043b\u044f \u0442\u0435\u0433\u0430), \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f \u0432\u0440\u0435\u043c\u0435\u043d\u043d\u0430\u044f \u043f\u0430\u043f\u043a\u0430 \u0434\u043b\u044f \u043c\u043e\u0434\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0439, \u0430 \u0437\u0430\u0442\u0435\u043c \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u044f StoreImage, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043e\u0431\u0440\u0430\u0437\u0430. \u041f\u043e \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044e \u0432\u0440\u0435\u043c\u0435\u043d\u043d\u0430\u044f \u043f\u0430\u043f\u043a\u0430 \u043f\u0435\u0440\u0435\u0438\u043c\u0435\u043d\u043e\u0432\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0432 img.Id:<\/p>\n<details class=\"spoiler\">\n<summary>StoreImage<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">func StoreImage(img *Image, layerData Archive, root string) error { \/\/ Check that root doesn't already exist if _, err := os.Stat(root); err == nil { return fmt.Errorf(\"Image %s already exists\", img.Id) } else if !os.IsNotExist(err) { return err } \/\/ Store the layer layer := layerPath(root) if err := os.MkdirAll(layer, 0700); err != nil { return err } if err := Untar(layerData, layer); err != nil { return err } \/\/ Store the json ball jsonData, err := json.Marshal(img) if err != nil { return err } if err := ioutil.WriteFile(jsonPath(root), jsonData, 0600); err != nil { return err } return nil }  func layerPath(root string) string { return path.Join(root, \"layer\") }  func jsonPath(root string) string { return path.Join(root, \"json\") }<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u0412 StoreImage \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u044f layer, \u0432 \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043f\u043e\u043c\u0435\u0449\u0430\u0435\u0442\u0441\u044f \u0440\u0430\u0441\u043f\u0430\u043a\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u043f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 Untar \u0430\u0440\u0445\u0438\u0432 \u0444\u0430\u0439\u043b\u043e\u0432\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u044b, \u043f\u043e\u0441\u043b\u0435 \u0447\u0435\u0433\u043e \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 Image \u044d\u043a\u0441\u043f\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0432 json \u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u0442\u0441\u044f \u0432 \u0441\u043e\u0441\u0435\u0434\u043d\u0438\u0439 \u0444\u0430\u0439\u043b, \u043a\u0430\u043a \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435 \u0434\u043b\u044f \u043e\u0431\u0440\u0430\u0437\u0430. \u0424\u0443\u043d\u043a\u0446\u0438\u0438 Tar \u0438 Untar \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0430\u0440\u0445\u0438\u0432\u0430\u043c\u0438 \u043d\u0430\u0445\u043e\u0434\u044f\u0442\u0441\u044f \u0432 \u0444\u0430\u0439\u043b\u0435 archive.go \u0438 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u044e\u0442 \u0441\u043e\u0431\u043e\u0439 \u043b\u0438\u0448\u044c \u0443\u0434\u043e\u0431\u043d\u044b\u0435 \u043e\u0431\u0435\u0440\u0442\u043a\u0438 \u043d\u0430\u0434 \u0443\u0442\u0438\u043b\u0438\u0442\u043e\u0439 bsdtar:<\/p>\n<details class=\"spoiler\">\n<summary>archive.go<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">type Archive io.Reader  type Compression uint32  const ( Uncompressed Compression = iota Bzip2 Gzip )  func (compression *Compression) Flag() string { switch *compression { case Bzip2: return \"j\" case Gzip: return \"z\" } return \"\" }  func Tar(path string, compression Compression) (io.Reader, error) { cmd := exec.Command(\"bsdtar\", \"-f\", \"-\", \"-C\", path, \"-c\"+compression.Flag(), \".\") return CmdStream(cmd) }  func Untar(archive io.Reader, path string) error { cmd := exec.Command(\"bsdtar\", \"-f\", \"-\", \"-C\", path, \"-x\") cmd.Stdin = archive output, err := cmd.CombinedOutput() if err != nil { return errors.New(err.Error() + \": \" + string(output)) } return nil }  func CmdStream(cmd *exec.Cmd) (io.Reader, error) { stdout, err := cmd.StdoutPipe() if err != nil { return nil, err } stderr, err := cmd.StderrPipe() if err != nil { return nil, err } pipeR, pipeW := io.Pipe() go func() { _, err := io.Copy(pipeW, stdout) if err != nil { pipeW.CloseWithError(err) } errText, e := ioutil.ReadAll(stderr) if e != nil { errText = []byte(\"(...couldn't fetch stderr: \" + e.Error() + \")\") } if err := cmd.Wait(); err != nil { \/\/ FIXME: can this block if stderr outputs more than the size of StderrPipe()'s buffer? pipeW.CloseWithError(errors.New(err.Error() + \": \" + string(errText))) } else { pipeW.Close() } }() if err := cmd.Start(); err != nil { return nil, err } return pipeR, nil } <\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u041e\u0442\u043c\u0435\u0442\u0438\u043c, \u0447\u0442\u043e \u0444\u0443\u043d\u043a\u0446\u0438\u044f import \u0442\u0430\u043a\u0436\u0435 \u043c\u043e\u0436\u0435\u0442 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0442\u044c tag \u043e\u0431\u0440\u0430\u0437\u0430, \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u044f \u0435\u0433\u043e \u043e\u043f\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u043c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u043c, \u043d\u043e \u044d\u0442\u043e\u0442 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b \u043c\u044b \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u043f\u043e\u0437\u0436\u0435, \u043a\u043e\u0433\u0434\u0430 \u0434\u043e \u043d\u0435\u0433\u043e \u0434\u043e\u0439\u0434\u0435\u0442 \u043e\u0447\u0435\u0440\u0435\u0434\u044c.<\/p>\n<h4>Export<\/h4>\n<p>\u041a\u043e\u043c\u0430\u043d\u0434\u0430 export \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u044d\u043a\u0441\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u0430\u0440\u0445\u0438\u0432 \u0444\u0430\u0439\u043b\u043e\u0432\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430:<\/p>\n<details class=\"spoiler\">\n<summary>CmdExport<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">func (srv *Server) CmdExport(stdin io.ReadCloser, stdout io.Writer, args ...string) error { cmd := rcli.Subcmd(stdout, \"export\", \"CONTAINER\", \"Export the contents of a filesystem as a tar archive\") if err := cmd.Parse(args); err != nil { return nil } name := cmd.Arg(0) if container := srv.runtime.Get(name); container != nil { data, err := container.Export() if err != nil { return err } \/\/ Stream the entire contents of the container (basically a volatile snapshot) if _, err := io.Copy(stdout, data); err != nil { return err } return nil } return errors.New(\"No such container: \" + name) }  func (container *Container) Export() (Archive, error) { if err := container.EnsureMounted(); err != nil { return nil, err } return Tar(container.RootfsPath(), Uncompressed) } <\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u0424\u0443\u043d\u043a\u0446\u0438\u044f \u043f\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u043d\u043e\u043c\u0443 \u0438\u043c\u0435\u043d\u0438 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u0438 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442 \u0443 \u043d\u0435\u0433\u043e \u043c\u0435\u0442\u043e\u0434 container.Export, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432 \u0441\u0432\u043e\u044e \u043e\u0447\u0435\u0440\u0435\u0434\u044c \u043f\u0440\u043e\u0441\u0442\u043e \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u044b\u0439 \u0430\u0440\u0445\u0438\u0432, \u0441\u043c\u043e\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0439 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 Rootfs. \u041a\u043e\u0434 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 Tar \u0431\u044b\u043b \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u043d \u0432\u044b\u0448\u0435 \u0432 \u0444\u0430\u0439\u043b\u0435 archive.go.<\/p>\n<h4>Rmi<\/h4>\n<p>\u0423\u0434\u0430\u043b\u044f\u0435\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a \u043e\u0431\u0440\u0430\u0437\u043e\u0432, \u0432\u044b\u0437\u044b\u0432\u0430\u044f \u043c\u0435\u0442\u043e\u0434 graph.Delete:<\/p>\n<details class=\"spoiler\">\n<summary>graph.Delete<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">func (srv *Server) CmdRmi(stdin io.ReadCloser, stdout io.Writer, args ...string) (err error) { cmd := rcli.Subcmd(stdout, \"rmimage\", \"[OPTIONS] IMAGE\", \"Remove an image\") if cmd.Parse(args) != nil || cmd.NArg() &lt; 1 { cmd.Usage() return nil } for _, name := range cmd.Args() { if err := srv.runtime.graph.Delete(name); err != nil { return err } } return nil }  func (graph *Graph) Delete(id string) error { garbage, err := graph.Garbage() if err != nil { return err } return os.Rename(graph.imageRoot(id), garbage.imageRoot(id)) }  func (graph *Graph) Garbage() (*Graph, error) { return NewGraph(path.Join(graph.Root, \":garbage:\")) } <\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u0412 \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u0438, graph.Delete \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0430\u0435\u0442 \u0438\u0445 \u0432 \u043f\u0430\u043f\u043a\u0443 :garbage:, \u0434\u043b\u044f \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u043f\u043e\u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0433\u043e \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f, \u043d\u043e \u0434\u0430\u043d\u043d\u0430\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0437\u0434\u0435\u0441\u044c \u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f.<\/p>\n<h4>Images<\/h4>\n<p>\u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0442\u0430\u0431\u043b\u0438\u0446\u0443 \u0441\u043e \u0441\u043f\u0438\u0441\u043a\u043e\u043c \u0438\u043c\u0435\u044e\u0449\u0438\u0445\u0441\u044f \u043e\u0431\u0440\u0430\u0437\u043e\u0432:<\/p>\n<details class=\"spoiler\">\n<summary>CmdImages<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">func (srv *Server) CmdImages(stdin io.ReadCloser, stdout io.Writer, args ...string) error { cmd := rcli.Subcmd(stdout, \"images\", \"[OPTIONS] [NAME]\", \"List images\") \/\/limit := cmd.Int(\"l\", 0, \"Only show the N most recent versions of each image\") quiet := cmd.Bool(\"q\", false, \"only show numeric IDs\") fl_a := cmd.Bool(\"a\", false, \"show all images\") if err := cmd.Parse(args); err != nil { return nil } if cmd.NArg() > 1 { cmd.Usage() return nil } var nameFilter string if cmd.NArg() == 1 { nameFilter = cmd.Arg(0) } w := tabwriter.NewWriter(stdout, 20, 1, 3, ' ', 0) if !*quiet { fmt.Fprintf(w, \"REPOSITORY\\tTAG\\tID\\tCREATED\\tPARENT\\n\") } var allImages map[string]*Image var err error if *fl_a { allImages, err = srv.runtime.graph.Map() } else { allImages, err = srv.runtime.graph.Heads() } if err != nil { return err } for name, repository := range srv.runtime.repositories.Repositories { if nameFilter != \"\" &amp;&amp; name != nameFilter { continue } for tag, id := range repository { image, err := srv.runtime.graph.Get(id) if err != nil { log.Printf(\"Warning: couldn't load %s from %s\/%s: %s\", id, name, tag, err) continue } delete(allImages, id) if !*quiet { for idx, field := range []string{ \/* REPOSITORY *\/ name, \/* TAG *\/ tag, \/* ID *\/ id, \/* CREATED *\/ HumanDuration(time.Now().Sub(image.Created)) + \" ago\", \/* PARENT *\/ srv.runtime.repositories.ImageName(image.Parent), } { if idx == 0 { w.Write([]byte(field)) } else { w.Write([]byte(\"\\t\" + field)) } } w.Write([]byte{'\\n'}) } else { stdout.Write([]byte(image.Id + \"\\n\")) } } } \/\/ Display images which aren't part of a if nameFilter == \"\" { for id, image := range allImages { if !*quiet { for idx, field := range []string{ \/* REPOSITORY *\/ \"\", \/* TAG *\/ \"\", \/* ID *\/ id, \/* CREATED *\/ HumanDuration(time.Now().Sub(image.Created)) + \" ago\", \/* PARENT *\/ srv.runtime.repositories.ImageName(image.Parent), } { if idx == 0 { w.Write([]byte(field)) } else { w.Write([]byte(\"\\t\" + field)) } } w.Write([]byte{'\\n'}) } else { stdout.Write([]byte(image.Id + \"\\n\")) } } } if !*quiet { w.Flush() } return nil }<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u0412 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u043f\u0440\u043e\u0441\u0442\u0430\u044f \u0438\u0442\u0435\u0440\u0430\u0446\u0438\u044f \u043f\u043e \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u043c \u043e\u0431\u0440\u0430\u0437\u0430\u043c, \u0444\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u044f \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u0438\u043c\u0435\u043d\u0438 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u044f \u0438 \u0432\u044b\u0432\u043e\u0434 \u043f\u043e\u043b\u0435\u0439 \u0432 \u043a\u043e\u043d\u0441\u043e\u043b\u044c\u043d\u0443\u044e \u0442\u0430\u0431\u043b\u0438\u0446\u0443. Map, Head \u0438 \u0432\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043a \u043d\u0438\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u0438, \u0444\u043e\u0440\u043c\u0438\u0440\u0443\u044e\u0449\u0438\u0435 \u0445\u0435\u0448 \u0442\u0430\u0431\u043b\u0438\u0446\u044b allImages, \u043d\u0430\u0445\u043e\u0434\u044f\u0442\u0441\u044f \u0432 \u0444\u0430\u0439\u043b\u0435 graph.go:<\/p>\n<details class=\"spoiler\">\n<summary>graph.go<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">func (graph *Graph) Map() (map[string]*Image, error) { \/\/ FIXME: this should replace All() all, err := graph.All() if err != nil { return nil, err } images := make(map[string]*Image, len(all)) for _, image := range all { images[image.Id] = image } return images, nil }  func (graph *Graph) All() ([]*Image, error) { var images []*Image err := graph.WalkAll(func(image *Image) { images = append(images, image) }) return images, err }  func (graph *Graph) WalkAll(handler func(*Image)) error { files, err := ioutil.ReadDir(graph.Root) if err != nil { return err } for _, st := range files { if img, err := graph.Get(st.Name()); err != nil { \/\/ Skip image continue } else if handler != nil { handler(img) } } return nil }  func (graph *Graph) ByParent() (map[string][]*Image, error) { byParent := make(map[string][]*Image) err := graph.WalkAll(func(image *Image) { image, err := graph.Get(image.Parent) if err != nil { return } if children, exists := byParent[image.Parent]; exists { byParent[image.Parent] = []*Image{image} } else { byParent[image.Parent] = append(children, image) } }) return byParent, err }  func (graph *Graph) Heads() (map[string]*Image, error) { heads := make(map[string]*Image) byParent, err := graph.ByParent() if err != nil { return nil, err } err = graph.WalkAll(func(image *Image) { \/\/ If it's not in the byParent lookup table, then \/\/ it's not a parent -> so it's a head! if _, exists := byParent[image.Id]; !exists { heads[image.Id] = image } }) return heads, err }<\/code><\/pre>\n<\/div>\n<\/details>\n<h4>History<\/h4>\n<p>\u041e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u0442 \u0438\u0441\u0442\u043e\u0440\u0438\u044e \u043e\u0431\u0440\u0430\u0437\u0430:<\/p>\n<details class=\"spoiler\">\n<summary>CmdHistory<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">func (srv *Server) CmdHistory(stdin io.ReadCloser, stdout io.Writer, args ...string) error { cmd := rcli.Subcmd(stdout, \"history\", \"[OPTIONS] IMAGE\", \"Show the history of an image\") if cmd.Parse(args) != nil || cmd.NArg() != 1 { cmd.Usage() return nil } image, err := srv.runtime.repositories.LookupImage(cmd.Arg(0)) if err != nil { return err } w := tabwriter.NewWriter(stdout, 20, 1, 3, ' ', 0) defer w.Flush() fmt.Fprintf(w, \"ID\\tCREATED\\tCREATED BY\\n\") return image.WalkHistory(func(img *Image) error { fmt.Fprintf(w, \"%s\\t%s\\t%s\\n\", srv.runtime.repositories.ImageName(img.Id), HumanDuration(time.Now().Sub(img.Created))+\" ago\", strings.Join(img.ContainerConfig.Cmd, \" \"), ) return nil }) }  func (img *Image) WalkHistory(handler func(*Image) error) (err error) {     currentImg := img     for currentImg != nil {         if handler != nil {             if err := handler(currentImg); err != nil {                 return err             }         }         currentImg, err = currentImg.GetParent()         if err != nil {             return fmt.Errorf(\"Error while getting parent image: %v\", err)         }     }     return nil }  func (img *Image) GetParent() (*Image, error) {     if img.Parent == \"\" {         return nil, nil     }     if img.graph == nil {         return nil, fmt.Errorf(\"Can't lookup parent of unregistered image\")     }     return img.graph.Get(img.Parent) } <\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u041f\u043e\u0441\u043b\u0435 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b \u043e\u0431\u0440\u0430\u0437\u0430 \u043f\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u043d\u043e\u043c\u0443 \u0438\u043c\u0435\u043d\u0438 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043c\u0435\u0442\u043e\u0434 image.WalkHistory, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u043e \u0446\u0435\u043f\u043e\u0447\u043a\u0435 \u043e\u0431\u0445\u043e\u0434\u0438\u0442 \u0440\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0435 \u043e\u0431\u0440\u0430\u0437\u044b, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u043d\u044b\u0435 \u0441\u0441\u044b\u043b\u043a\u0438 Image.Parent, \u0438 \u0432\u044b\u0432\u043e\u0434\u0438\u0442 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u0432 \u0432\u0438\u0434\u0435 \u0442\u0430\u0431\u043b\u0438\u0446\u044b.<\/p>\n<h4>Commit<\/h4>\n<p>\u0421\u043e\u0437\u0434\u0430\u0435\u0442 \u043d\u043e\u0432\u044b\u0439 \u043e\u0431\u0440\u0430\u0437 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u043d\u044b\u0445 \u0434\u0430\u043d\u043d\u044b\u0445 \u0444\u0430\u0439\u043b\u043e\u0432\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430:<\/p>\n<details class=\"spoiler\">\n<summary>CmdCommit<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...string) error { cmd := rcli.Subcmd(stdout, \"commit\", \"[OPTIONS] CONTAINER [REPOSITORY [TAG]]\", \"Create a new image from a container's changes\") if err := cmd.Parse(args); err != nil { return nil } containerName, repository, tag := cmd.Arg(0), cmd.Arg(1), cmd.Arg(2) if containerName == \"\" { cmd.Usage() return nil } img, err := srv.runtime.Commit(containerName, repository, tag) if err != nil { return err } fmt.Fprintln(stdout, img.Id) return nil }  \/\/ Commit creates a new filesystem image from the current state of a container. \/\/ The image can optionally be tagged into a repository func (runtime *Runtime) Commit(id, repository, tag string) (*Image, error) { container := runtime.Get(id) if container == nil { return nil, fmt.Errorf(\"No such container: %s\", id) } \/\/ FIXME: freeze the container before copying it to avoid data corruption? \/\/ FIXME: this shouldn't be in commands. rwTar, err := container.ExportRw() if err != nil { return nil, err } \/\/ Create a new image from the container's base layers + a new layer from container changes img, err := runtime.graph.Create(rwTar, container, \"\") if err != nil { return nil, err } \/\/ Register the image if needed if repository != \"\" { if err := runtime.repositories.Set(repository, tag, img.Id, true); err != nil { return img, err } } return img, nil }<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u041c\u0435\u0442\u043e\u0434 Commit \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 container \u043f\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u043d\u043e\u043c\u0443 \u0438\u043c\u0435\u043d\u0438, \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442 \u043c\u0435\u0442\u043e\u0434 container.ExportRw, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0430\u0440\u0445\u0438\u0432 \u0441 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0435\u0439 rw, \u043f\u043e\u0441\u043b\u0435 \u0447\u0435\u0433\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u0442 \u0435\u0433\u043e \u0432 \u043c\u0435\u0442\u043e\u0434 graph.Create, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u044b \u0443\u0436\u0435 \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u043b\u0438 \u0432\u044b\u0448\u0435. \u0415\u0441\u043b\u0438 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u043e \u0438\u043c\u044f \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u044f \u0438 tag, \u0442\u043e \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0441\u043e\u0437\u0434\u0430\u043d tag \u043e\u0431\u0440\u0430\u0437\u0430. \u042d\u0442\u043e\u0442 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u043d \u043d\u0438\u0436\u0435 \u0432 \u043a\u043e\u043c\u0430\u043d\u0434\u0435 Tag.<\/p>\n<h4>Tag<\/h4>\n<p>\u0424\u0443\u043d\u043a\u0446\u0438\u044f \u0441\u043e\u0437\u0434\u0430\u0435\u0442 tag \u043e\u0431\u0440\u0430\u0437\u0430 \u0432 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u043c \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438. \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043f\u0440\u0438 \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438 \u043e\u0431\u0440\u0430\u0437\u0430 \u0438 \u043a\u043e\u043c\u043c\u0438\u0442\u0435 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430:<\/p>\n<details class=\"spoiler\">\n<summary>CmdTag<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">func (srv *Server) CmdTag(stdin io.ReadCloser, stdout io.Writer, args ...string) error { cmd := rcli.Subcmd(stdout, \"tag\", \"[OPTIONS] IMAGE REPOSITORY [TAG]\", \"Tag an image into a repository\") force := cmd.Bool(\"f\", false, \"Force\") if err := cmd.Parse(args); err != nil { return nil } if cmd.NArg() &lt; 2 { cmd.Usage() return nil } return srv.runtime.repositories.Set(cmd.Arg(1), cmd.Arg(2), cmd.Arg(0), *force) }<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u041f\u043e\u0441\u043b\u0435 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043c\u0435\u0442\u043e\u0434 repositories.Set \u0438\u0437 \u0444\u0430\u0439\u043b\u0430 tags.go. \u041d\u0438\u0436\u0435 \u044f \u043f\u0440\u0438\u0432\u0435\u0434\u0443 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435 \u0444\u0430\u0439\u043b\u0430 tags.go, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0437\u0430 \u0432\u0435\u0441\u044c \u044d\u0442\u043e\u0442 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b. \u041e\u0431\u0449\u0438\u0439 \u043f\u0440\u0438\u043d\u0446\u0438\u043f \u0440\u0430\u0431\u043e\u0442\u044b \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u043f\u0440\u043e\u0441\u0442\u043e\u0439 &#8212; \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 TagStore \u0438\u043c\u0435\u0435\u0442 \u0445\u0435\u0448 \u0442\u0430\u0431\u043b\u0438\u0446\u0443(map) \u0434\u043b\u044f \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u044f \u0442\u0435\u0433\u043e\u0432 \u0438 \u043e\u0431\u0440\u0430\u0437\u043e\u0432. \u041f\u0440\u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0438 \u043d\u043e\u0432\u043e\u0433\u043e \u0442\u0435\u0433\u0430\u00a0\u043e\u043d \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u0442 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044e \u043d\u0430 \u0437\u0430\u043f\u0440\u0435\u0449\u0435\u043d\u043d\u044b\u0435 \u0441\u0438\u043c\u0432\u043e\u043b\u044b \u0438 \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0432 \u0445\u0435\u0448 \u0442\u0430\u0431\u043b\u0438\u0446\u0443. \u0414\u0430\u043b\u0435\u0435 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 TagStore \u044d\u043a\u0441\u043f\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0432 json \u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u0442\u0441\u044f \u0432 \u0444\u0430\u0439\u043b\u0435 \u043d\u0430 \u0434\u0438\u0441\u043a\u0435.\u00a0\u041f\u0440\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0435 \u0434\u043e\u043a\u0435\u0440 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u0442\u0441\u044f \u0438\u0437 \u044d\u0442\u043e\u0433\u043e \u0444\u0430\u0439\u043b\u0430 \u0438 \u0432 \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u043c \u043d\u0430 \u0435\u0435 \u043e\u0441\u043d\u043e\u0432\u0435 \u043e\u0441\u0443\u0449\u0435\u0441\u0442\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043f\u043e\u0438\u0441\u043a \u0438 \u0444\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u044f \u043e\u0431\u0440\u0430\u0437\u043e\u0432 \u043f\u043e \u0438\u043c\u0435\u043d\u0430\u043c \u0438 \u0442\u0435\u0433\u0430\u043c:<\/p>\n<details class=\"spoiler\">\n<summary>tags.go<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">const DEFAULT_TAG = \"latest\"  type TagStore struct { path         string graph        *Graph Repositories map[string]Repository }  type Repository map[string]string  func NewTagStore(path string, graph *Graph) (*TagStore, error) { abspath, err := filepath.Abs(path) if err != nil { return nil, err } store := &amp;TagStore{ path:         abspath, graph:        graph, Repositories: make(map[string]Repository), } \/\/ Load the json file if it exists, otherwise create it. if err := store.Reload(); os.IsNotExist(err) { if err := store.Save(); err != nil { return nil, err } } else if err != nil { return nil, err } return store, nil }  func (store *TagStore) Save() error { \/\/ Store the json ball jsonData, err := json.Marshal(store) if err != nil { return err } if err := ioutil.WriteFile(store.path, jsonData, 0600); err != nil { return err } return nil }  func (store *TagStore) Reload() error { jsonData, err := ioutil.ReadFile(store.path) if err != nil { return err } if err := json.Unmarshal(jsonData, store); err != nil { return err } return nil }  func (store *TagStore) LookupImage(name string) (*Image, error) { img, err := store.graph.Get(name) if err != nil { \/\/ FIXME: standardize on returning nil when the image doesn't exist, and err for everything else \/\/ (so we can pass all errors here) repoAndTag := strings.SplitN(name, \":\", 2) if len(repoAndTag) == 1 { repoAndTag = append(repoAndTag, DEFAULT_TAG) } if i, err := store.GetImage(repoAndTag[0], repoAndTag[1]); err != nil { return nil, err } else if i == nil { return nil, fmt.Errorf(\"No such image: %s\", name) } else { img = i } } return img, nil }  \/\/ Return a reverse-lookup table of all the names which refer to each image \/\/ Eg. {\"43b5f19b10584\": {\"base:latest\", \"base:v1\"}} func (store *TagStore) ById() map[string][]string { byId := make(map[string][]string) for repoName, repository := range store.Repositories { for tag, id := range repository { name := repoName + \":\" + tag if _, exists := byId[id]; !exists { byId[id] = []string{name} } else { byId[id] = append(byId[id], name) } } } return byId }  func (store *TagStore) ImageName(id string) string { if names, exists := store.ById()[id]; exists &amp;&amp; len(names) > 0 { return names[0] } return id }  func (store *TagStore) Set(repoName, tag, imageName string, force bool) error { img, err := store.LookupImage(imageName) if err != nil { return err } if tag == \"\" { tag = DEFAULT_TAG } if err := validateRepoName(repoName); err != nil { return err } if err := validateTagName(tag); err != nil { return err } if err := store.Reload(); err != nil { return err } var repo Repository if r, exists := store.Repositories[repoName]; exists { repo = r } else { repo = make(map[string]string) if old, exists := store.Repositories[repoName]; exists &amp;&amp; !force { return fmt.Errorf(\"Tag %s:%s is already set to %s\", repoName, tag, old) } store.Repositories[repoName] = repo } repo[tag] = img.Id return store.Save() }  func (store *TagStore) Get(repoName string) (Repository, error) { if err := store.Reload(); err != nil { return nil, err } if r, exists := store.Repositories[repoName]; exists { return r, nil } return nil, nil }  func (store *TagStore) GetImage(repoName, tag string) (*Image, error) { repo, err := store.Get(repoName) if err != nil { return nil, err } else if repo == nil { return nil, nil } if revision, exists := repo[tag]; exists { return store.graph.Get(revision) } return nil, nil }  \/\/ Validate the name of a repository func validateRepoName(name string) error { if name == \"\" { return fmt.Errorf(\"Repository name can't be empty\") } if strings.Contains(name, \":\") { return fmt.Errorf(\"Illegal repository name: %s\", name) } return nil }  \/\/ Validate the name of a tag func validateTagName(name string) error { if name == \"\" { return fmt.Errorf(\"Tag name can't be empty\") } if strings.Contains(name, \"\/\") || strings.Contains(name, \":\") { return fmt.Errorf(\"Illegal tag name: %s\", name) } return nil }<\/code><\/pre>\n<\/div>\n<\/details>\n<h3>\u0423\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430\u043c\u0438<\/h3>\n<h4>Run<\/h4>\n<p>\u0421\u043e\u0437\u0434\u0430\u0435\u0442 \u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u0437\u0430\u0434\u0430\u043d\u043d\u043e\u0433\u043e \u043e\u0431\u0440\u0430\u0437\u0430:<\/p>\n<details class=\"spoiler\">\n<summary>CmdRun<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string) error { config, err := ParseRun(args) if err != nil { return err } if config.Image == \"\" { return fmt.Errorf(\"Image not specified\") } if len(config.Cmd) == 0 { return fmt.Errorf(\"Command not specified\") } \/\/ Create new container container, err := srv.runtime.Create(config) if err != nil { return errors.New(\"Error creating container: \" + err.Error()) } if config.OpenStdin { cmd_stdin, err := container.StdinPipe() if err != nil { return err } if !config.Detach { Go(func() error { _, err := io.Copy(cmd_stdin, stdin) cmd_stdin.Close() return err }) } } \/\/ Run the container if !config.Detach { cmd_stderr, err := container.StderrPipe() if err != nil { return err } cmd_stdout, err := container.StdoutPipe() if err != nil { return err } if err := container.Start(); err != nil { return err } sending_stdout := Go(func() error { _, err := io.Copy(stdout, cmd_stdout) return err }) sending_stderr := Go(func() error { _, err := io.Copy(stdout, cmd_stderr) return err }) err_sending_stdout := &lt;-sending_stdout err_sending_stderr := &lt;-sending_stderr if err_sending_stdout != nil { return err_sending_stdout } if err_sending_stderr != nil { return err_sending_stderr } container.Wait() } else { if err := container.Start(); err != nil { return err } fmt.Fprintln(stdout, container.Id) } return nil }<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u0412 \u043d\u0430\u0447\u0430\u043b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u044f ParseRun \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442 \u0440\u0430\u0437\u0431\u043e\u0440 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u0438 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b Config:<\/p>\n<details class=\"spoiler\">\n<summary>ParseRun<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">func ParseRun(args []string) (*Config, error) { cmd := flag.NewFlagSet(\"\", flag.ContinueOnError) cmd.SetOutput(ioutil.Discard) fl_user := cmd.String(\"u\", \"\", \"Username or UID\") fl_detach := cmd.Bool(\"d\", false, \"Detached mode: leave the container running in the background\") fl_stdin := cmd.Bool(\"i\", false, \"Keep stdin open even if not attached\") fl_tty := cmd.Bool(\"t\", false, \"Allocate a pseudo-tty\") fl_memory := cmd.Int64(\"m\", 0, \"Memory limit (in bytes)\") var fl_ports ports  cmd.Var(&amp;fl_ports, \"p\", \"Map a network port to the container\") var fl_env ListOpts cmd.Var(&amp;fl_env, \"e\", \"Set environment variables\") if err := cmd.Parse(args); err != nil { return nil, err } config := &amp;Config{ Ports:     fl_ports, User:      *fl_user, Tty:       *fl_tty, OpenStdin: *fl_stdin, Memory:    *fl_memory, Detach:    *fl_detach, Env:       fl_env, Cmd:       cmd.Args()[1:], Image:     cmd.Arg(0), } return config, nil }<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u041d\u0430 \u043e\u0441\u043d\u043e\u0432\u0430\u043d\u0438\u0438 Config \u0444\u0443\u043d\u043a\u0446\u0438\u044f runtime.Create \u0441\u043e\u0437\u0434\u0430\u0435\u0442 \u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u043d\u043e\u0432\u044b\u0439 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440, \u043f\u043e\u0441\u043b\u0435 \u0447\u0435\u0433\u043e \u043f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u0438 \u043f\u0430\u0439\u043f\u043e\u0432 \u0438 \u043a\u0430\u043d\u0430\u043b\u043e\u0432 \u043f\u0435\u0440\u0435\u043d\u0430\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u043f\u043e\u0442\u043e\u043a\u0438 stdin, stdout, stderr, \u0430 \u0437\u0430\u0442\u0435\u043c \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442 \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u044b\u0439 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u043c\u0435\u0442\u043e\u0434\u043e\u043c container.Start.<\/p>\n<details class=\"spoiler\">\n<summary>runtime.Create<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">func (runtime *Runtime) Create(config *Config) (*Container, error) { \/\/ Lookup image img, err := runtime.repositories.LookupImage(config.Image) if err != nil { return nil, err } container := &amp;Container{ \/\/ FIXME: we should generate the ID here instead of receiving it as an argument Id:              GenerateId(), Created:         time.Now(), Path:            config.Cmd[0], Args:            config.Cmd[1:], \/\/FIXME: de-duplicate from config Config:          config, Image:           img.Id, \/\/ Always use the resolved image id NetworkSettings: &amp;NetworkSettings{}, \/\/ FIXME: do we need to store this in the container? SysInitPath: sysInitPath, } container.root = runtime.containerRoot(container.Id) \/\/ Step 1: create the container directory. \/\/ This doubles as a barrier to avoid race conditions. if err := os.Mkdir(container.root, 0700); err != nil { return nil, err } \/\/ Step 2: save the container json if err := container.ToDisk(); err != nil { return nil, err } \/\/ Step 3: register the container if err := runtime.Register(container); err != nil { return nil, err } return container, nil }<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u0417\u0434\u0435\u0441\u044c \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b Container, \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0440\u0430\u0431\u043e\u0447\u0435\u0439 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430 \u0438 \u044d\u043a\u0441\u043f\u043e\u0440\u0442 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b \u0432 json \u0444\u043e\u0440\u043c\u0430\u0442\u0435. \u0417\u0430\u0432\u0435\u0440\u0448\u0430\u0435\u0442 \u044d\u0442\u043e \u0432\u044b\u0437\u043e\u0432 \u043c\u0435\u0442\u043e\u0434\u0430 runtime.Register, \u043a\u043e\u0434 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u043c\u044b \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u043b\u0438 \u0432 \u043f\u0440\u043e\u0448\u043b\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435. \u0424\u0443\u043d\u043a\u0446\u0438\u044f GenerateId \u0431\u044b\u043b\u0430 \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0435\u043d\u0430 \u0440\u0430\u043d\u0435\u0435 \u0432 \u0440\u0430\u0437\u0434\u0435\u043b\u0435 \u043f\u043e \u043a\u043e\u043c\u0430\u043d\u0434\u0435 import. \u0422\u0435\u043f\u0435\u0440\u044c \u043f\u0435\u0440\u0435\u0439\u0434\u0435\u043c \u043a \u043c\u0435\u0442\u043e\u0434\u0443 container.Start:<\/p>\n<details class=\"spoiler\">\n<summary>container.Start<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">func (container *Container) Start() error { if err := container.EnsureMounted(); err != nil { return err } if err := container.allocateNetwork(); err != nil { return err } if err := container.generateLXCConfig(); err != nil { return err } params := []string{ \"-n\", container.Id, \"-f\", container.lxcConfigPath(), \"--\", \"\/sbin\/init\", }  \/\/ Networking params = append(params, \"-g\", container.network.Gateway.String())  \/\/ User if container.Config.User != \"\" { params = append(params, \"-u\", container.Config.User) }  \/\/ Program params = append(params, \"--\", container.Path) params = append(params, container.Args...)  container.cmd = exec.Command(\"\/usr\/bin\/lxc-start\", params...)  \/\/ Setup environment container.cmd.Env = append( []string{ \"HOME=\/\", \"PATH=\/usr\/local\/sbin:\/usr\/local\/bin:\/usr\/sbin:\/usr\/bin:\/sbin:\/bin\", }, container.Config.Env..., )  var err error if container.Config.Tty { err = container.startPty() } else { err = container.start() } if err != nil { return err } \/\/ FIXME: save state on disk *first*, then converge \/\/ this way disk state is used as a journal, eg. we can restore after crash etc. container.State.setRunning(container.cmd.Process.Pid) container.ToDisk() go container.monitor() return nil }<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u041e\u0441\u043d\u043e\u0432\u043d\u0430\u044f \u043b\u043e\u0433\u0438\u043a\u0430 \u0435\u0433\u043e \u0440\u0430\u0431\u043e\u0442\u044b \u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043d\u0435 \u0438\u0437\u043c\u0435\u043d\u0438\u043b\u0430\u0441\u044c \u0441\u043e \u0432\u0440\u0435\u043c\u0435\u043d \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u043a\u043e\u043c\u043c\u0438\u0442\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u044b \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u043b\u0438 \u0432 <a href=\"https:\/\/habr.com\/ru\/post\/573828\/\" rel=\"noopener noreferrer nofollow\">\u043f\u0435\u0440\u0432\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435<\/a>. \u0418\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u043a\u043e\u0441\u043d\u0443\u043b\u0438\u0441\u044c \u0447\u0430\u0441\u0442\u0438 \u043e\u0442\u0432\u0435\u0447\u0430\u044e\u0449\u0435\u0439 \u0437\u0430 \u043c\u043e\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0444\u0430\u0439\u043b\u043e\u0432\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u044b, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0431\u044b\u043b \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d \u043a\u043e\u0434 \u0434\u043b\u044f \u0441\u0435\u0442\u0435\u0432\u043e\u0433\u043e \u0441\u0442\u0435\u043a\u0430 \u0438 \u043d\u043e\u0432\u044b\u0439 \u043c\u0435\u0442\u043e\u0434 \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430 \u0432 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0435. \u0420\u0430\u0437\u0431\u0435\u0440\u0435\u043c \u0432\u0441\u0435 \u043f\u043e \u043f\u043e\u0440\u044f\u0434\u043a\u0443, \u043d\u0430\u0447\u0438\u043d\u0430\u044f \u0441 \u043c\u043e\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0444\u0430\u0439\u043b\u043e\u0432\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u044b:<\/p>\n<details class=\"spoiler\">\n<summary>EnsureMounted<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">func (container *Container) EnsureMounted() error { if mounted, err := container.Mounted(); err != nil { return err } else if mounted { return nil } return container.Mount() }   func (container *Container) Mounted() (bool, error) { return Mounted(container.RootfsPath()) }   func Mounted(mountpoint string) (bool, error) { mntpoint, err := os.Stat(mountpoint) if err != nil { if os.IsNotExist(err) { return false, nil } return false, err } parent, err := os.Stat(filepath.Join(mountpoint, \"..\")) if err != nil { return false, err } mntpointSt := mntpoint.Sys().(*syscall.Stat_t) parentSt := parent.Sys().(*syscall.Stat_t) return mntpointSt.Dev != parentSt.Dev, nil }<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u041c\u0435\u0442\u043e\u0434 container.EnsureMounted \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442 \u0431\u044b\u043b\u0430 \u043b\u0438 \u0441\u043c\u043e\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0430 \u0444\u0430\u0439\u043b\u043e\u0432\u0430\u044f \u0441\u0438\u0441\u0442\u0435\u043c\u0430, \u0432 \u043f\u0440\u043e\u0442\u0438\u0432\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 &#8212; \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442 \u043c\u043e\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0432\u044b\u0437\u043e\u0432\u043e\u043c container.Mount():<\/p>\n<details class=\"spoiler\">\n<summary>container.Mount<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">func (container *Container) Mount() error { image, err := container.GetImage() if err != nil { return err } return image.Mount(container.RootfsPath(), container.rwPath()) }  func (image *Image) Mount(root, rw string) error { if mounted, err := Mounted(root); err != nil { return err } else if mounted { return fmt.Errorf(\"%s is already mounted\", root) } layers, err := image.layers() if err != nil { return err } \/\/ Create the target directories if they don't exist if err := os.Mkdir(root, 0755); err != nil &amp;&amp; !os.IsExist(err) { return err } if err := os.Mkdir(rw, 0755); err != nil &amp;&amp; !os.IsExist(err) { return err } \/\/ FIXME: @creack shouldn't we do this after going over changes? if err := MountAUFS(layers, rw, root); err != nil { return err } \/\/ FIXME: Create tests for deletion \/\/ FIXME: move this part to change.go \/\/ Retrieve the changeset from the parent and apply it to the container \/\/  - Retrieve the changes changes, err := Changes(layers, layers[0]) if err != nil { return err } \/\/ Iterate on changes for _, c := range changes { \/\/ If there is a delete if c.Kind == ChangeDelete { \/\/ Make sure the directory exists file_path, file_name := path.Dir(c.Path), path.Base(c.Path) if err := os.MkdirAll(path.Join(rw, file_path), 0755); err != nil { return err } \/\/ And create the whiteout (we just need to create empty file, discard the return) if _, err := os.Create(path.Join(path.Join(rw, file_path), \".wh.\"+path.Base(file_name))); err != nil { return err } } } return nil }<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u041f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0430 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u0434\u043b\u044f \u043c\u043e\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0441\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u0435\u0439 MountAUFS, \u043b\u043e\u0433\u0438\u043a\u0430 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u0430\u043d\u0430\u043b\u043e\u0433\u0438\u0447\u043d\u0430 \u043f\u0435\u0440\u0432\u043e\u0439 \u0432\u0435\u0440\u0441\u0438\u0438, \u0442\u043e\u043b\u044c\u043a\u043e \u0442\u0435\u043f\u0435\u0440\u044c \u0432\u043c\u0435\u0441\u0442\u043e \u0443\u0442\u0438\u043b\u0438\u0442\u044b mount \u043c\u043e\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0441\u044f \u0441\u0438\u0441\u0442\u0435\u043c\u043d\u044b\u043c \u0432\u044b\u0437\u043e\u0432\u043e\u043c:<\/p>\n<details class=\"spoiler\">\n<summary>MountAUFS<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">func MountAUFS(ro []string, rw string, target string) error { \/\/ FIXME: Now mount the layers rwBranch := fmt.Sprintf(\"%v=rw\", rw) roBranches := \"\" for _, layer := range ro { roBranches += fmt.Sprintf(\"%v=ro:\", layer) } branches := fmt.Sprintf(\"br:%v:%v\", rwBranch, roBranches) return mount(\"none\", target, \"aufs\", 0, branches) }  func mount(source string, target string, fstype string, flags uintptr, data string) (err error) { return syscall.Mount(source, target, fstype, flags, data) }<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u041f\u043e\u0441\u043b\u0435 \u043c\u043e\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0444\u0430\u0439\u043b\u043e\u0432\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043a\u043e\u043c\u0430\u043d\u0434\u044b Changes \u0438 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0444\u0430\u0439\u043b\u043e\u0432 c .wh. \u043f\u0440\u0435\u0444\u0438\u043a\u0441\u043e\u043c \u0432 \u043f\u0430\u043f\u043a\u0435 rw, \u0435\u0441\u043b\u0438 \u043e\u043d\u0438 \u0431\u044b\u043b\u0438 \u0443\u0434\u0430\u043b\u0435\u043d\u044b \u0432 \u0432\u0435\u0440\u0445\u043d\u0435\u043c \u0441\u043b\u043e\u0435. \u0410\u043b\u0433\u043e\u0440\u0438\u0442\u043c \u0440\u0430\u0431\u043e\u0442\u044b \u044d\u0442\u043e\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0435\u043d \u0432 \u0440\u0430\u0437\u0434\u0435\u043b\u0435 \u043a\u043e\u043c\u0430\u043d\u0434\u044b Diff, \u0432\u044b\u0447\u0438\u0441\u043b\u044f\u044e\u0449\u0435\u0439 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0432 \u0444\u0430\u0439\u043b\u043e\u0432\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u0435. \u0422\u0435\u043f\u0435\u0440\u044c \u043f\u0435\u0440\u0435\u0439\u0434\u0435\u043c \u043a \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0441\u0435\u0442\u0438 \u0432\u044b\u0437\u043e\u0432\u043e\u043c container.allocateNetwork:<\/p>\n<details class=\"spoiler\">\n<summary>container.allocateNetwork<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">func (container *Container) allocateNetwork() error { iface, err := container.runtime.networkManager.Allocate() if err != nil { return err } container.NetworkSettings.PortMapping = make(map[string]string) for _, port := range container.Config.Ports { if extPort, err := iface.AllocatePort(port); err != nil { iface.Release() return err } else { container.NetworkSettings.PortMapping[strconv.Itoa(port)] = strconv.Itoa(extPort) } } container.network = iface container.NetworkSettings.IpAddress = iface.IPNet.IP.String() container.NetworkSettings.IpPrefixLen, _ = iface.IPNet.Mask.Size() container.NetworkSettings.Gateway = iface.Gateway.String() return nil }<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u0412 \u043d\u0435\u043c \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0441\u0435\u0442\u0435\u0432\u043e\u0433\u043e \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430, \u043f\u0440\u0438\u0441\u0432\u043e\u0435\u043d\u0438\u0435 ip \u0430\u0434\u0440\u0435\u0441\u0430, \u043c\u0430\u0441\u043a\u0438 \u0438 \u0448\u043b\u044e\u0437\u0430, \u0430 \u0442\u0430\u043a\u0436\u0435 \u043f\u0440\u043e\u0431\u0440\u043e\u0441 \u043f\u043e\u0440\u0442\u043e\u0432. \u042f \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e \u0440\u0430\u0437\u0431\u0435\u0440\u0443 \u0432\u0435\u0441\u044c \u044d\u0442\u043e\u0442 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b \u043d\u0438\u0436\u0435, \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u043f\u043e \u0440\u0430\u0431\u043e\u0442\u0435 \u0441 \u0441\u0435\u0442\u0435\u0432\u044b\u043c \u0441\u0442\u0435\u043a\u043e\u043c. \u0414\u0430\u043b\u0435\u0435 \u0438\u0434\u0435\u0442 \u0432\u044b\u0437\u043e\u0432 \u043c\u0435\u0442\u043e\u0434\u0430 generateLXCConfig. \u041e\u043d \u0431\u044b\u043b \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u043d \u0432 \u043f\u0435\u0440\u0432\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u0438 \u043e\u0441\u0442\u0430\u043b\u0441\u044f \u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0431\u0435\u0437 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439.\u00a0\u0421\u0442\u043e\u0438\u0442 \u043b\u0438\u0448\u044c \u043e\u0442\u043c\u0435\u0442\u0438\u0442\u044c, \u0447\u0442\u043e \u0442\u0435\u043f\u0435\u0440\u044c \u0432 lxc_template.go \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u044b \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0441\u0435\u0442\u0438, \u043c\u043e\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \/etc\/resolv.conf \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b dns \u0438, \u0433\u043b\u0430\u0432\u043d\u043e\u0435, \u043c\u043e\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0438\u0441\u043f\u043e\u043b\u043d\u044f\u0435\u043c\u043e\u0433\u043e \u0444\u0430\u0439\u043b\u0430 docker \u0432 \u0442\u043e\u0447\u043a\u0443 \/sbin\/init, \u0442\u0430\u043a \u043a\u0430\u043a \u0442\u0435\u043f\u0435\u0440\u044c \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430 \u0431\u0443\u0434\u0435\u0442 \u043d\u0430\u0447\u0438\u043d\u0430\u0442\u044c\u0441\u044f \u0441 \u043d\u0435\u0433\u043e. \u042f \u0443\u0436\u0435 \u043e\u0431\u0440\u0430\u0449\u0430\u043b \u043d\u0430 \u044d\u0442\u043e \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u0432 <a href=\"https:\/\/habr.com\/ru\/post\/574750\/\" rel=\"noopener noreferrer nofollow\">\u0447\u0430\u0441\u0442\u0438 2.1<\/a>. \u041d\u0438\u0436\u0435 \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u043d\u044b \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0432 lxc_template:<\/p>\n<details class=\"spoiler\">\n<summary>lxc_template.go<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"bash\"># network configuration lxc.network.type = veth lxc.network.flags = up lxc.network.link = lxcbr0 lxc.network.name = eth0 lxc.network.mtu = 1500 lxc.network.ipv4 = {{.NetworkSettings.IpAddress}}\/{{.NetworkSettings.IpPrefixLen}}  # Inject docker-init lxc.mount.entry = {{.SysInitPath}} {{$ROOTFS}}\/sbin\/init none bind,ro 0 0  # In order to get a working DNS environment, mount bind (ro) the host's \/etc\/resolv.conf into the container lxc.mount.entry = \/etc\/resolv.conf {{$ROOTFS}}\/etc\/resolv.conf none bind,ro 0 0 <\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043e\u0441\u0442\u0430\u043b\u043e\u0441\u044c \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u0442\u044c \u0444\u0430\u0439\u043b sysinit.go, \u0441 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u0441\u0442\u0430\u0440\u0442\u0443\u0435\u0442 \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u044b\u0439 lxc \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440:  <\/p>\n<details class=\"spoiler\">\n<summary>sysinit.go<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">\/\/ Setup networking func setupNetworking(gw string) { if gw == \"\" { return } cmd := exec.Command(\"\/sbin\/route\", \"add\", \"default\", \"gw\", gw) if err := cmd.Run(); err != nil { log.Fatalf(\"Unable to set up networking: %v\", err) } }  \/\/ Takes care of dropping privileges to the desired user func changeUser(u string) { if u == \"\" { return } userent, err := user.LookupId(u) if err != nil { userent, err = user.Lookup(u) } if err != nil { log.Fatalf(\"Unable to find user %v: %v\", u, err) }  uid, err := strconv.Atoi(userent.Uid) if err != nil { log.Fatalf(\"Invalid uid: %v\", userent.Uid) } gid, err := strconv.Atoi(userent.Gid) if err != nil { log.Fatalf(\"Invalid gid: %v\", userent.Gid) }  if err := syscall.Setgid(gid); err != nil { log.Fatalf(\"setgid failed: %v\", err) } if err := syscall.Setuid(uid); err != nil { log.Fatalf(\"setuid failed: %v\", err) } }  func executeProgram(name string, args []string) { path, err := exec.LookPath(name) if err != nil { log.Printf(\"Unable to locate %v\", name) os.Exit(127) }  if err := syscall.Exec(path, args, os.Environ()); err != nil { panic(err) } }  \/\/ Sys Init code \/\/ This code is run INSIDE the container and is responsible for setting \/\/ up the environment before running the actual process func SysInit() { if len(os.Args) &lt;= 1 { fmt.Println(\"You should not invoke docker-init manually\") os.Exit(1) } var u = flag.String(\"u\", \"\", \"username or uid\") var gw = flag.String(\"g\", \"\", \"gateway address\")  flag.Parse()  setupNetworking(*gw) changeUser(*u) executeProgram(flag.Arg(0), flag.Args()) }<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u041a\u0430\u043a \u0432\u0438\u0434\u0438\u043c, \u0432 SysInit \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f \u043f\u0435\u0440\u0435\u0434 \u0437\u0430\u043f\u0443\u0441\u043a\u043e\u043c \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430. \u0414\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435 default gateway \u0432 \u0442\u0430\u0431\u043b\u0438\u0446\u0443 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0446\u0438\u0438, \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438 \u0433\u0440\u0443\u043f\u043f\u044b, \u043f\u043e\u0434 \u043a\u043e\u0442\u043e\u0440\u044b\u043c\u0438 \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0442\u044c\u0441\u044f \u043f\u0440\u043e\u0446\u0435\u0441\u0441, \u0438 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u0437\u0430\u043f\u0443\u0441\u043a \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u043c \u043c\u0435\u0442\u043e\u0434\u043e\u043c Exec.<\/p>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430 \u0432 \u043c\u0435\u0442\u043e\u0434\u0435 Start \u0438\u0434\u0435\u0442 \u043f\u0435\u0440\u0435\u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0445 \u043f\u043e\u0442\u043e\u043a\u043e\u0432 \u0438 \u0437\u0430\u043f\u0443\u0441\u043a \u0433\u043e\u0440\u0443\u0442\u0438\u043d\u044b container.monitor, \u0440\u0430\u0431\u043e\u0442\u0443 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u043c\u044b \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u043b\u0438 \u0432 <a href=\"https:\/\/habr.com\/ru\/post\/573828\/\" rel=\"noopener noreferrer nofollow\">\u043f\u0435\u0440\u0432\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435<\/a>. \u041c\u043e\u0436\u043d\u043e \u043b\u0438\u0448\u044c \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c, \u0447\u0442\u043e \u0442\u0435\u043f\u0435\u0440\u044c \u0432 \u043d\u0435\u0439 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u043e\u0441\u0432\u043e\u0431\u043e\u0436\u0434\u0435\u043d\u0438\u0435 \u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u043e\u0433\u043e ip \u0430\u0434\u0440\u0435\u0441\u0430,\u00a0\u043f\u0440\u043e\u0431\u0440\u043e\u0448\u0435\u043d\u043d\u044b\u0445 \u043f\u043e\u0440\u0442\u043e\u0432 \u0438 \u0440\u0430\u0437\u043c\u043e\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0444\u0430\u0439\u043b\u043e\u0432\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u044b.<\/p>\n<details class=\"spoiler\">\n<summary>container.monitor<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">func (container *Container) monitor() { \/\/ Wait for the program to exit container.cmd.Wait() exitCode := container.cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus()  \/\/ Cleanup if err := container.releaseNetwork(); err != nil { log.Printf(\"%v: Failed to release network: %v\", container.Id, err) } container.stdout.Close() container.stderr.Close() if err := container.Unmount(); err != nil { log.Printf(\"%v: Failed to umount filesystem: %v\", container.Id, err) }  \/\/ Re-create a brand new stdin pipe once the container exited if container.Config.OpenStdin { container.stdin, container.stdinPipe = io.Pipe() }  \/\/ Report status back container.State.setStopped(exitCode) container.ToDisk() }  func (container *Container) releaseNetwork() error { err := container.network.Release() container.network = nil container.NetworkSettings = &amp;NetworkSettings{} return err }  func (container *Container) Unmount() error { return Unmount(container.RootfsPath()) }  func Unmount(target string) error { if err := syscall.Unmount(target, 0); err != nil { return err } \/\/ Even though we just unmounted the filesystem, AUFS will prevent deleting the mntpoint \/\/ for some time. We'll just keep retrying until it succeeds. for retries := 0; retries &lt; 1000; retries++ { err := os.Remove(target) if err == nil { \/\/ rm mntpoint succeeded return nil } if os.IsNotExist(err) { \/\/ mntpoint doesn't exist anymore. Success. return nil } \/\/ fmt.Printf(\"(%v) Remove %v returned: %v\\n\", retries, target, err) time.Sleep(10 * time.Millisecond) } return fmt.Errorf(\"Umount: Failed to umount %v\", target) }<\/code><\/pre>\n<\/div>\n<\/details>\n<h3>\u041d\u0435\u043e\u0436\u0438\u0434\u0430\u043d\u043d\u043e\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u0435<\/h3>\n<p>\u0412\u0432\u0438\u0434\u0443 \u0442\u043e\u0433\u043e, \u0447\u0442\u043e \u0440\u0435\u0434\u0430\u043a\u0442\u043e\u0440 \u0445\u0430\u0431\u0440\u0430 \u043d\u0430\u0447\u0430\u043b \u0441\u0438\u043b\u044c\u043d\u043e \u0442\u043e\u0440\u043c\u043e\u0437\u0438\u0442\u044c, \u0430 \u043c\u0435\u0441\u0442\u0430\u043c\u0438 \u0438 \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u0437\u0430\u0432\u0438\u0441\u0430\u0442\u044c (\u0432\u0438\u0434\u0438\u043c\u043e \u0441\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0431\u043e\u043b\u044c\u0448\u043e\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0441\u043d\u0438\u043f\u0435\u0442\u043e\u0432 \u043a\u043e\u0434\u0430), \u044f \u0432\u044b\u043d\u0443\u0436\u0434\u0435\u043d \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044c \u044d\u0442\u0443 \u0447\u0430\u0441\u0442\u044c, \u043d\u043e \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0443 \u0432 <a href=\"https:\/\/habr.com\/ru\/post\/575296\/\" rel=\"noopener noreferrer nofollow\">\u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0439<\/a>, \u043f\u0440\u044f\u043c\u043e \u0441 \u044d\u0442\u043e\u0433\u043e \u043c\u0435\u0441\u0442\u0430. <\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/575256\/\"> https:\/\/habr.com\/ru\/articles\/575256\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<h2>\u0412\u0441\u0442\u0443\u043f\u043b\u0435\u043d\u0438\u0435<\/h2>\n<p>\u0414\u0430\u043d\u043d\u0430\u044f \u0441\u0442\u0430\u0442\u044c\u044f \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0442\u0440\u0435\u0442\u044c\u0435\u0439 \u0432 \u0446\u0438\u043a\u043b\u0435 (<a href=\"https:\/\/habr.com\/ru\/post\/573828\/\" rel=\"noopener noreferrer nofollow\">1<\/a>,<a href=\"https:\/\/habr.com\/ru\/post\/574750\/\" rel=\"noopener noreferrer nofollow\">2<\/a>), \u043f\u043e\u0441\u0432\u044f\u0449\u0435\u043d\u043d\u043e\u043c \u0438\u0437\u0443\u0447\u0435\u043d\u0438\u044e \u0438\u0441\u0445\u043e\u0434\u043d\u043e\u0433\u043e \u043a\u043e\u0434\u0430 Docker \u0438 \u043f\u0440\u044f\u043c\u044b\u043c \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0435\u043d\u0438\u0435\u043c <a href=\"https:\/\/habr.com\/ru\/post\/574750\/\" rel=\"noopener noreferrer nofollow\">\u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0439 \u0441\u0442\u0430\u0442\u044c\u0438<\/a>, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u043c\u044b \u043d\u0430\u0447\u0430\u043b\u0438 \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u0442\u044c \u043a\u043e\u0434 \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u043e\u0433\u043e \u0440\u0435\u043b\u0438\u0437\u0430 Docker v0.1.0. \u0412 \u044d\u0442\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0435\u043d\u0430 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0432\u0441\u0435\u0445 \u043a\u043e\u043c\u0430\u043d\u0434, \u0430 \u0432 \u043a\u043e\u043d\u0446\u0435, \u043c\u044b \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043e\u0431\u0440\u0430\u0437 \u0438 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u043c \u0434\u043e\u043a\u0435\u0440 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u043d\u0430 \u0435\u0433\u043e \u043e\u0441\u043d\u043e\u0432\u0435. \u0414\u043b\u044f \u0443\u0434\u043e\u0431\u0441\u0442\u0432\u0430 \u044f \u043f\u043e\u0441\u0442\u0430\u0440\u0430\u043b\u0441\u044f \u0440\u0430\u0437\u0431\u0438\u0442\u044c \u0441\u043f\u0438\u0441\u043e\u043a \u043a\u043e\u043c\u0430\u043d\u0434 \u043d\u0430 \u0443\u0441\u043b\u043e\u0432\u043d\u044b\u0435 \u0433\u0440\u0443\u043f\u043f\u044b: \u0440\u0430\u0431\u043e\u0442\u0430 \u0441 \u043e\u0431\u0440\u0430\u0437\u0430\u043c\u0438, \u0440\u0430\u0431\u043e\u0442\u0430 \u0441 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430\u043c\u0438, \u0441\u0435\u0442\u0435\u0432\u043e\u0439 \u0441\u0442\u0435\u043a \u0438 \u0442.\u0434.\u00a0<\/p>\n<p>\u0410 \u0442\u0435\u043f\u0435\u0440\u044c, \u043a\u0430\u043a \u0433\u043e\u0432\u043e\u0440\u0438\u0442\u0441\u044f, \u201cwithout further ado\u201d, \u043f\u0440\u0438\u0441\u0442\u0443\u043f\u0438\u043c \u043a \u0438\u0437\u0443\u0447\u0435\u043d\u0438\u044e \u043a\u043e\u0434\u0430 \u0438\u0437 \u0444\u0430\u0439\u043b\u0430 commands.go \u043d\u0430\u0447\u0438\u043d\u0430\u044f \u0441 \u043a\u043e\u043c\u0430\u043d\u0434 \u0434\u043b\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043e\u0431\u0440\u0430\u0437\u0430\u043c\u0438 (images).<\/p>\n<h3>\u0423\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043e\u0431\u0440\u0430\u0437\u0430\u043c\u0438<\/h3>\n<h4>Import<\/h4>\n<p>\u041a\u043e\u043c\u0430\u043d\u0434\u0430 import \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043e\u0431\u0440\u0430\u0437 \u0444\u0430\u0439\u043b\u043e\u0432\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u0438\u0437 tar \u0430\u0440\u0445\u0438\u0432\u0430, \u043f\u043e\u0434\u0430\u0432\u0430\u0435\u043c\u043e\u0433\u043e \u043d\u0430 stdin, \u0438\u043b\u0438 \u0436\u0435 \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0435\u0433\u043e \u043f\u043e url:<\/p>\n<details class=\"spoiler\">\n<summary>CmdImport<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">func (srv *Server) CmdImport(stdin io.ReadCloser, stdout io.Writer, args ...string) error { cmd := rcli.Subcmd(stdout, \"import\", \"[OPTIONS] URL|- [REPOSITORY [TAG]]\", \"Create a new filesystem image from the contents of a tarball\") var archive io.Reader var resp *http.Response  if err := cmd.Parse(args); err != nil { return nil } src := cmd.Arg(0) if src == \"\" { return errors.New(\"Not enough arguments\") } else if src == \"-\" { archive = stdin } else { u, err := url.Parse(src) if err != nil { return err } if u.Scheme == \"\" { u.Scheme = \"http\" u.Host = src u.Path = \"\" } fmt.Fprintf(stdout, \"Downloading from %s\\n\", u.String()) \/\/ Download with curl (pretty progress bar) \/\/ If curl is not available, fallback to http.Get() resp, err = Download(u.String(), stdout) if err != nil { return err } archive = ProgressReader(resp.Body, int(resp.ContentLength), stdout) } img, err := srv.runtime.graph.Create(archive, nil, \"Imported from \"+src) if err != nil { return err } \/\/ Optionally register the image at REPO\/TAG if repository := cmd.Arg(1); repository != \"\" { tag := cmd.Arg(2) \/\/ Repository will handle an empty tag properly if err := srv.runtime.repositories.Set(repository, tag, img.Id, true); err != nil { return err } } fmt.Fprintln(stdout, img.Id) return nil }<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u043e\u0433\u043e \u0440\u0430\u0437\u0431\u043e\u0440\u0430 \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442\u043e\u0432 \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442 \u043c\u0435\u0441\u0442\u043e \u043e\u0442\u043a\u0443\u0434\u0430 \u043d\u0443\u0436\u043d\u043e \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043e\u0431\u0440\u0430\u0437: &#171;-&#187; \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442 stdin, \u0432 \u0434\u0440\u0443\u0433\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442 \u0440\u0430\u0441\u0441\u043c\u0430\u0442\u0440\u0438\u0432\u0430\u0435\u0442\u0441\u044f, \u043a\u0430\u043a url. \u0425\u0435\u043b\u043f\u0435\u0440 \u043c\u0435\u0442\u043e\u0434\u044b Download \u0438 ProgressReader \u0434\u043b\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0430\u0440\u0445\u0438\u0432\u0430 \u043f\u043e http \u043d\u0430\u0445\u043e\u0434\u044f\u0442\u0441\u044f \u0432 \u0444\u0430\u0439\u043b\u0435 utils.go:<\/p>\n<details class=\"spoiler\">\n<summary>utils.go<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">\/\/ Request a given URL and return an io.Reader func Download(url string, stderr io.Writer) (*http.Response, error) { var resp *http.Response var err error = nil if resp, err = http.Get(url); err != nil { return nil, err } if resp.StatusCode >= 400 { return nil, errors.New(\"Got HTTP status code >= 400: \" + resp.Status) } return resp, nil }   type progressReader struct { reader        io.ReadCloser \/\/ Stream to read from output        io.Writer     \/\/ Where to send progress bar to read_total    int           \/\/ Expected stream length (bytes) read_progress int           \/\/ How much has been read so far (bytes) last_update   int           \/\/ How many bytes read at least update }  func (r *progressReader) Read(p []byte) (n int, err error) { read, err := io.ReadCloser(r.reader).Read(p) r.read_progress += read  \/\/ Only update progress for every 1% read update_every := int(0.01 * float64(r.read_total)) if r.read_progress-r.last_update > update_every || r.read_progress == r.read_total { fmt.Fprintf(r.output, \"%d\/%d (%.0f%%)\\r\", r.read_progress, r.read_total, float64(r.read_progress)\/float64(r.read_total)*100) r.last_update = r.read_progress } \/\/ Send newline when complete if err == io.EOF { fmt.Fprintf(r.output, \"\\n\") }  return read, err } func (r *progressReader) Close() error { return io.ReadCloser(r.reader).Close() } func ProgressReader(r io.ReadCloser, size int, output io.Writer) *progressReader { return &amp;progressReader{r, output, size, 0, 0} } <\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u0414\u0430\u043b\u0435\u0435 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u0442 \u0432 \u0444\u0443\u043d\u043a\u0446\u0438\u044e graph.Create \u0438\u0437 \u0444\u0430\u0439\u043b\u0430 graph.go:<\/p>\n<details class=\"spoiler\">\n<summary>graph.Create<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">func (graph *Graph) Create(layerData Archive, container *Container, comment string) (*Image, error) { img := &amp;Image{ Id:      GenerateId(), Comment: comment, Created: time.Now(), } if container != nil { img.Parent = container.Image img.Container = container.Id img.ContainerConfig = *container.Config } if err := graph.Register(layerData, img); err != nil { return nil, err } return img, nil }<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u0417\u0434\u0435\u0441\u044c \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u044b\u0439 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u043e\u0431\u0440\u0430\u0437\u0430 \u0438 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 Image, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0434\u0430\u043b\u0435\u0435 \u0432\u043c\u0435\u0441\u0442\u0435 \u0441 \u0434\u0430\u043d\u043d\u044b\u043c\u0438 \u0430\u0440\u0445\u0438\u0432\u0430 \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u0442\u0441\u044f \u0432 \u043c\u0435\u0442\u043e\u0434 graph.Register. \u0415\u0441\u043b\u0438 \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u043d \u0438 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440, \u0442\u043e \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u0435\u0433\u043e \u043e\u0431\u0440\u0430\u0437 \u0431\u0443\u0434\u0435\u0442 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0430 \u0432 \u043f\u043e\u043b\u0435 img.Parent &#8212; \u044d\u0442\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0432 \u043a\u043e\u043c\u0430\u043d\u0434\u0435 Commit, \u0441\u043e\u0437\u0434\u0430\u044e\u0449\u0435\u0439 \u043d\u043e\u0432\u044b\u0439 \u043e\u0431\u0440\u0430\u0437 \u0438\u0437 \u0442\u0435\u043a\u0443\u0449\u0435\u0433\u043e \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430. \u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 Image \u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0434\u043b\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 Id \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 SHA256 \u043d\u0430\u0445\u043e\u0434\u044f\u0442\u0441\u044f \u0432 \u0444\u0430\u0439\u043b\u0435 image.go:<\/p>\n<details class=\"spoiler\">\n<summary>image.go<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">type Image struct { Id              string    `json:\"id\"` Parent          string    `json:\"parent,omitempty\"` Comment         string    `json:\"comment,omitempty\"` Created         time.Time `json:\"created\"` Container       string    `json:\"container,omitempty\"` ContainerConfig Config    `json:\"container_config,omitempty\"` graph           *Graph }  func GenerateId() string { \/\/ FIXME: don't seed every time rand.Seed(time.Now().UTC().UnixNano()) randomBytes := bytes.NewBuffer([]byte(fmt.Sprintf(\"%x\", rand.Int()))) id, _ := ComputeId(randomBytes) \/\/ can't fail return id }  \/\/ ComputeId reads from `content` until EOF, then returns a SHA of what it read, as a string. func ComputeId(content io.Reader) (string, error) { h := sha256.New() if _, err := io.Copy(h, content); err != nil { return \"\", err } return fmt.Sprintf(\"%x\", h.Sum(nil)[:8]), nil } <\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u0414\u0430\u043b\u0435\u0435 \u0432\u0437\u0433\u043b\u044f\u043d\u0435\u043c \u043d\u0430 \u043c\u0435\u0442\u043e\u0434 graph.Register:<\/p>\n<details class=\"spoiler\">\n<summary>graph.Register<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">func (graph *Graph) Register(layerData Archive, img *Image) error { if err := ValidateId(img.Id); err != nil { return err } \/\/ (This is a convenience to save time. Race conditions are taken care of by os.Rename) if graph.Exists(img.Id) { return fmt.Errorf(\"Image %s already exists\", img.Id) } tmp, err := graph.Mktemp(img.Id) defer os.RemoveAll(tmp) if err != nil { return fmt.Errorf(\"Mktemp failed: %s\", err) } if err := StoreImage(img, layerData, tmp); err != nil { return err } \/\/ Commit if err := os.Rename(tmp, graph.imageRoot(img.Id)); err != nil { return err } img.graph = graph return nil }<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438 id \u043d\u0430 \u043d\u0430\u043b\u0438\u0447\u0438\u0435 \u0437\u0430\u043f\u0440\u0435\u0449\u0435\u043d\u043d\u043e\u0433\u043e \u0441\u0438\u043c\u0432\u043e\u043b\u0430 &#171;:&#187; (\u0442\u0430\u043a \u043a\u0430\u043a \u043e\u043d \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0440\u0430\u0437\u0434\u0435\u043b\u0438\u0442\u0435\u043b\u0435\u043c \u0434\u043b\u044f \u0442\u0435\u0433\u0430), \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f \u0432\u0440\u0435\u043c\u0435\u043d\u043d\u0430\u044f \u043f\u0430\u043f\u043a\u0430 \u0434\u043b\u044f \u043c\u043e\u0434\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0439, \u0430 \u0437\u0430\u0442\u0435\u043c \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u044f StoreImage, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043e\u0431\u0440\u0430\u0437\u0430. \u041f\u043e \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044e \u0432\u0440\u0435\u043c\u0435\u043d\u043d\u0430\u044f \u043f\u0430\u043f\u043a\u0430 \u043f\u0435\u0440\u0435\u0438\u043c\u0435\u043d\u043e\u0432\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0432 img.Id:<\/p>\n<details class=\"spoiler\">\n<summary>StoreImage<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">func StoreImage(img *Image, layerData Archive, root string) error { \/\/ Check that root doesn't already exist if _, err := os.Stat(root); err == nil { return fmt.Errorf(\"Image %s already exists\", img.Id) } else if !os.IsNotExist(err) { return err } \/\/ Store the layer layer := layerPath(root) if err := os.MkdirAll(layer, 0700); err != nil { return err } if err := Untar(layerData, layer); err != nil { return err } \/\/ Store the json ball jsonData, err := json.Marshal(img) if err != nil { return err } if err := ioutil.WriteFile(jsonPath(root), jsonData, 0600); err != nil { return err } return nil }  func layerPath(root string) string { return path.Join(root, \"layer\") }  func jsonPath(root string) string { return path.Join(root, \"json\") }<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u0412 StoreImage \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u044f layer, \u0432 \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043f\u043e\u043c\u0435\u0449\u0430\u0435\u0442\u0441\u044f \u0440\u0430\u0441\u043f\u0430\u043a\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u043f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 Untar \u0430\u0440\u0445\u0438\u0432 \u0444\u0430\u0439\u043b\u043e\u0432\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u044b, \u043f\u043e\u0441\u043b\u0435 \u0447\u0435\u0433\u043e \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 Image \u044d\u043a\u0441\u043f\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0432 json \u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u0442\u0441\u044f \u0432 \u0441\u043e\u0441\u0435\u0434\u043d\u0438\u0439 \u0444\u0430\u0439\u043b, \u043a\u0430\u043a \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435 \u0434\u043b\u044f \u043e\u0431\u0440\u0430\u0437\u0430. \u0424\u0443\u043d\u043a\u0446\u0438\u0438 Tar \u0438 Untar \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0430\u0440\u0445\u0438\u0432\u0430\u043c\u0438 \u043d\u0430\u0445\u043e\u0434\u044f\u0442\u0441\u044f \u0432 \u0444\u0430\u0439\u043b\u0435 archive.go \u0438 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u044e\u0442 \u0441\u043e\u0431\u043e\u0439 \u043b\u0438\u0448\u044c \u0443\u0434\u043e\u0431\u043d\u044b\u0435 \u043e\u0431\u0435\u0440\u0442\u043a\u0438 \u043d\u0430\u0434 \u0443\u0442\u0438\u043b\u0438\u0442\u043e\u0439 bsdtar:<\/p>\n<details class=\"spoiler\">\n<summary>archive.go<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">type Archive io.Reader  type Compression uint32  const ( Uncompressed Compression = iota Bzip2 Gzip )  func (compression *Compression) Flag() string { switch *compression { case Bzip2: return \"j\" case Gzip: return \"z\" } return \"\" }  func Tar(path string, compression Compression) (io.Reader, error) { cmd := exec.Command(\"bsdtar\", \"-f\", \"-\", \"-C\", path, \"-c\"+compression.Flag(), \".\") return CmdStream(cmd) }  func Untar(archive io.Reader, path string) error { cmd := exec.Command(\"bsdtar\", \"-f\", \"-\", \"-C\", path, \"-x\") cmd.Stdin = archive output, err := cmd.CombinedOutput() if err != nil { return errors.New(err.Error() + \": \" + string(output)) } return nil }  func CmdStream(cmd *exec.Cmd) (io.Reader, error) { stdout, err := cmd.StdoutPipe() if err != nil { return nil, err } stderr, err := cmd.StderrPipe() if err != nil { return nil, err } pipeR, pipeW := io.Pipe() go func() { _, err := io.Copy(pipeW, stdout) if err != nil { pipeW.CloseWithError(err) } errText, e := ioutil.ReadAll(stderr) if e != nil { errText = []byte(\"(...couldn't fetch stderr: \" + e.Error() + \")\") } if err := cmd.Wait(); err != nil { \/\/ FIXME: can this block if stderr outputs more than the size of StderrPipe()'s buffer? pipeW.CloseWithError(errors.New(err.Error() + \": \" + string(errText))) } else { pipeW.Close() } }() if err := cmd.Start(); err != nil { return nil, err } return pipeR, nil } <\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u041e\u0442\u043c\u0435\u0442\u0438\u043c, \u0447\u0442\u043e \u0444\u0443\u043d\u043a\u0446\u0438\u044f import \u0442\u0430\u043a\u0436\u0435 \u043c\u043e\u0436\u0435\u0442 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0442\u044c tag \u043e\u0431\u0440\u0430\u0437\u0430, \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u044f \u0435\u0433\u043e \u043e\u043f\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u043c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u043c, \u043d\u043e \u044d\u0442\u043e\u0442 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b \u043c\u044b \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u043f\u043e\u0437\u0436\u0435, \u043a\u043e\u0433\u0434\u0430 \u0434\u043e \u043d\u0435\u0433\u043e \u0434\u043e\u0439\u0434\u0435\u0442 \u043e\u0447\u0435\u0440\u0435\u0434\u044c.<\/p>\n<h4>Export<\/h4>\n<p>\u041a\u043e\u043c\u0430\u043d\u0434\u0430 export \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u044d\u043a\u0441\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u0430\u0440\u0445\u0438\u0432 \u0444\u0430\u0439\u043b\u043e\u0432\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430:<\/p>\n<details class=\"spoiler\">\n<summary>CmdExport<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">func (srv *Server) CmdExport(stdin io.ReadCloser, stdout io.Writer, args ...string) error { cmd := rcli.Subcmd(stdout, \"export\", \"CONTAINER\", \"Export the contents of a filesystem as a tar archive\") if err := cmd.Parse(args); err != nil { return nil } name := cmd.Arg(0) if container := srv.runtime.Get(name); container != nil { data, err := container.Export() if err != nil { return err } \/\/ Stream the entire contents of the container (basically a volatile snapshot) if _, err := io.Copy(stdout, data); err != nil { return err } return nil } return errors.New(\"No such container: \" + name) }  func (container *Container) Export() (Archive, error) { if err := container.EnsureMounted(); err != nil { return nil, err } return<\/code><\/pre>\n<\/div>\n<\/details>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-409743","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/409743","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=409743"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/409743\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=409743"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=409743"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=409743"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}