// cmdImport implements IMPORT command // TODO: document behavior of cmdImport func (builder *Builder) cmdImport(args []string, attributes map[string]bool, flags map[string]string, original string) (err error) { if len(args) == 0 { return fmt.Errorf("Command is missing value: %s", original) } if builder.lastExportImageID == "" { return fmt.Errorf("You have to EXPORT something first in order to: %s", original) } if builder.exportsContainerID == "" { return fmt.Errorf("Something went wrong, missing exports container: %s", original) } // If only one argument was given to IMPORT, use the same path for destination // IMPORT /my/dir/file.tar --> ADD ./EXPORT_VOLUME/my/dir/file.tar /my/dir/file.tar if len(args) < 2 { args = []string{args[0], "/"} } dest := args[len(args)-1] // last one is always the dest // prepare builder mount builder.addMount(builderMount{ dest: exportsVolume, containerID: builder.exportsContainerID, }) defer builder.removeLastMount() // TODO: rsync doesn't work as expected if ENTRYPOINT is inherited by parent image // STILL RELEVANT? cmd := []string{"/opt/rsync/bin/rsync", "-a"} for _, arg := range args[0 : len(args)-1] { argResolved, err := util.ResolvePath(exportsVolume, arg) if err != nil { return fmt.Errorf("Invalid IMPORT source: %s", arg) } cmd = append(cmd, argResolved) } cmd = append(cmd, dest) // For caching builder.addLabels(map[string]string{ "rocker-lastExportImageId": builder.lastExportImageID, }) // Configure container temporarily, only for this execution resetFunc := builder.temporaryConfig(func() { builder.Config.Entrypoint = []string{} }) defer resetFunc() fmt.Fprintf(builder.OutStream, "[Rocker] run: %s\n", strings.Join(cmd, " ")) return builder.runAndCommit(cmd, "import") }
// Execute runs the command func (c *CommandImport) Execute(b *Build) (s State, err error) { s = b.state args := c.cfg.args if len(args) == 0 { return s, fmt.Errorf("IMPORT requires at least one argument") } if len(b.exports) == 0 { return s, fmt.Errorf("You have to EXPORT something first in order to IMPORT") } // TODO: EXPORT and IMPORT cache is not invalidated properly in between // different tracks of the same build. The EXPORT may be cached // because it was built earlier with the same prerequisites, but the actual // data in the exports container may be from the latest EXPORT of different // build. So we need to prefix ~/.rocker_exports dir with some id somehow. exportsContainer, err := b.getExportsContainer() if err != nil { return s, err } log.Infof("| Import from %s (%.12s)", b.exportsContainerName(), exportsContainer.ID) // If only one argument was given to IMPORT, use the same path for destination // IMPORT /my/dir/file.tar --> ADD ./EXPORT_VOLUME/my/dir/file.tar /my/dir/file.tar if len(args) < 2 { args = []string{args[0], "/"} } dest := args[len(args)-1] // last one is always the dest src := []string{} for _, arg := range args[0 : len(args)-1] { argResolved, err := util.ResolvePath(ExportsPath, arg) if err != nil { return s, fmt.Errorf("Invalid IMPORT source: %s", arg) } src = append(src, argResolved) } sort.Strings(b.exports) s.Commit("IMPORT %q : %q %s", b.exports, src, dest) // Check cache s, hit, err := b.probeCache(s) if err != nil { return s, err } if hit { return s, nil } // Remember original stuff so we can restore it when we finished origState := s var importID string defer func() { s = origState s.NoCache.ContainerID = importID }() cmd := []string{"/opt/rsync/bin/rsync", "-a"} if b.cfg.Verbose { cmd = append(cmd, "--verbose") } cmd = append(cmd, src...) cmd = append(cmd, dest) s.Config.Cmd = cmd s.Config.Entrypoint = []string{} // Append exports container as a volume s.NoCache.HostConfig.Binds = append(s.NoCache.HostConfig.Binds, mountsToBinds(exportsContainer.Mounts)...) if importID, err = b.client.CreateContainer(s); err != nil { return s, err } log.Infof("| Running in %.12s: %s", importID, strings.Join(cmd, " ")) if err = b.client.RunContainer(importID, false); err != nil { return s, err } // TODO: if b.exportsCacheBusted and IMPORT cache was invalidated, // CommitCommand then caches it anyway. return s, nil }
// cmdExport implements EXPORT command // TODO: document behavior of cmdExport func (builder *Builder) cmdExport(args []string, attributes map[string]bool, flags map[string]string, original string) error { if len(args) == 0 { return fmt.Errorf("Command is missing value: %s", original) } // If only one argument was given to EXPORT, use basename of a file // EXPORT /my/dir/file.tar --> /EXPORT_VOLUME/file.tar if len(args) < 2 { args = []string{args[0], "/"} } dest := args[len(args)-1] // last one is always the dest // EXPORT /my/dir my_dir --> /EXPORT_VOLUME/my_dir // EXPORT /my/dir /my_dir --> /EXPORT_VOLUME/my_dir // EXPORT /my/dir stuff/ --> /EXPORT_VOLUME/stuff/my_dir // EXPORT /my/dir /stuff/ --> /EXPORT_VOLUME/stuff/my_dir // EXPORT /my/dir/* / --> /EXPORT_VOLUME/stuff/my_dir exportsContainerID, err := builder.makeExportsContainer() if err != nil { return err } // prepare builder mount builder.addMount(builderMount{ dest: exportsVolume, containerID: exportsContainerID, }) defer builder.removeLastMount() cmdDestPath, err := util.ResolvePath(exportsVolume, dest) if err != nil { return fmt.Errorf("Invalid EXPORT destination: %s", dest) } // TODO: rsync doesn't work as expected if ENTRYPOINT is inherited by parent image // STILL RELEVANT? // build the command cmd := []string{"/opt/rsync/bin/rsync", "-a", "--delete-during"} cmd = append(cmd, args[0:len(args)-1]...) cmd = append(cmd, cmdDestPath) // For caching builder.addLabels(map[string]string{ "rocker-exportsContainerId": exportsContainerID, }) // Configure container temporarily, only for this execution resetFunc := builder.temporaryConfig(func() { builder.Config.Entrypoint = []string{} }) defer resetFunc() fmt.Fprintf(builder.OutStream, "[Rocker] run: %s\n", strings.Join(cmd, " ")) if err := builder.runAndCommit(cmd, "import"); err != nil { return err } builder.lastExportImageID = builder.imageID return nil }
// Execute runs the command func (c *CommandExport) Execute(b *Build) (s State, err error) { s = b.state args := c.cfg.args if len(args) == 0 { return s, fmt.Errorf("EXPORT requires at least one argument") } // If only one argument was given to EXPORT, use basename of a file // EXPORT /my/dir/file.tar --> /EXPORT_VOLUME/file.tar if len(args) < 2 { args = []string{args[0], "/"} } src := args[0 : len(args)-1] dest := args[len(args)-1] // last one is always the dest // EXPORT /my/dir my_dir --> /EXPORT_VOLUME/my_dir // EXPORT /my/dir /my_dir --> /EXPORT_VOLUME/my_dir // EXPORT /my/dir stuff/ --> /EXPORT_VOLUME/stuff/my_dir // EXPORT /my/dir /stuff/ --> /EXPORT_VOLUME/stuff/my_dir // EXPORT /my/dir/* / --> /EXPORT_VOLUME/stuff/my_dir exportsContainer, err := b.getExportsContainer() if err != nil { return s, err } // build the command cmdDestPath, err := util.ResolvePath(ExportsPath, dest) if err != nil { return s, fmt.Errorf("Invalid EXPORT destination: %s", dest) } s.Commit("EXPORT %q to %.12s:%s", src, exportsContainer.ID, dest) s, hit, err := b.probeCache(s) if err != nil { return s, err } if hit { b.exports = append(b.exports, s.ExportsID) return s, nil } // Remember original stuff so we can restore it when we finished var exportsID string origState := s defer func() { s = origState s.ExportsID = exportsID b.exports = append(b.exports, exportsID) }() // Append exports container as a volume s.NoCache.HostConfig.Binds = append(s.NoCache.HostConfig.Binds, mountsToBinds(exportsContainer.Mounts)...) cmd := []string{"/opt/rsync/bin/rsync", "-a", "--delete-during"} if b.cfg.Verbose { cmd = append(cmd, "--verbose") } cmd = append(cmd, src...) cmd = append(cmd, cmdDestPath) s.Config.Cmd = cmd s.Config.Entrypoint = []string{} if exportsID, err = b.client.CreateContainer(s); err != nil { return s, err } defer b.client.RemoveContainer(exportsID) log.Infof("| Running in %.12s: %s", exportsID, strings.Join(cmd, " ")) if err = b.client.RunContainer(exportsID, false); err != nil { return s, err } return s, nil }