Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

holo-files: Support patch files (#5) #42

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions cmd/holo-files/internal/impl/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,21 @@ func (resource rawResource) EntityPath() string { return resource.entityPath
func NewResource(path string) Resource {
relPath, _ := filepath.Rel(common.ResourceDirectory(), path)
segments := strings.SplitN(relPath, string(filepath.Separator), 2)
ext := filepath.Ext(segments[1])
raw := rawResource{
path: path,
disambiguator: segments[0],
entityPath: strings.TrimSuffix(segments[1], ".holoscript"),
entityPath: strings.TrimSuffix(segments[1], ext),
}
if strings.HasSuffix(raw.path, ".holoscript") {
switch ext {
case ".holoscript":
return Holoscript{raw}
case ".patch":
return Patchfile{raw}
default:
raw.entityPath += ext
return StaticResource{raw}
}
return StaticResource{raw}
}

//Resources holds a slice of Resource instances, and implements some methods
Expand Down
111 changes: 111 additions & 0 deletions cmd/holo-files/internal/impl/resource_patch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*******************************************************************************
*
* Copyright 2017-2018 Luke Shumaker <[email protected]>
*
* This file is part of Holo.
*
* Holo is free software: you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later
* version.
*
* Holo is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* Holo. If not, see <http://www.gnu.org/licenses/>.
*
*******************************************************************************/

package impl

import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"

"github.com/holocm/holo/cmd/holo-files/internal/common"
)

// Patchfile is a Resource that is a `patch(1)` file that edits the
// current version of the entity.
type Patchfile struct{ rawResource }

// ApplicationStrategy implements the Resource interface.
func (resource Patchfile) ApplicationStrategy() string { return "patch" }

// DiscardsPreviousBuffer implements the Resource interface.
func (resource Patchfile) DiscardsPreviousBuffer() bool { return false }

// ApplyTo implements the Resource interface.
func (resource Patchfile) ApplyTo(entityBuffer common.FileBuffer) (common.FileBuffer, error) {
// `patch` requires that the file it's operating on be a real
// file (not a pipe). So, we'll write entityBuffer to a
// temporary file, run `patch`, then read it back.

// We really only normally need 1 temporary file, but:
// 1. since common.FileBuffer.Write removes the file and then
// re-creates it, that's a bit racy
// 2. The only way to limit patch to operating on a single
// file is to name that file on the command line, but
// doing that prevents it from unlinking the file, which
// prevents type changes.
//
// Using a temporary directory lets us easily work around both
// of these issues. Unfortunately, this allows the patch to
// create new files other than the one for the entity we are
// applying. However, it can't escape the temporary
// directory, so we'll just "allow" that, and document that we
// ignore those files.
targetDir, err := ioutil.TempDir(os.Getenv("HOLO_CACHE_DIR"), "patch-target.")
if err != nil {
return common.FileBuffer{}, err
}
defer os.RemoveAll(targetDir)
targetPath := filepath.Join(targetDir, filepath.Base(entityBuffer.Path))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

targetPath is a bit confusing since we already have a "target path" in our jargon at some other point. Maybe workDir and workPath, or tempDir and tempPath?


// Write entityBuffer to the temporary file
err = entityBuffer.Write(targetPath)
if err != nil {
return common.FileBuffer{}, err
}

// Run `patch` on the temporary file
patchfile, err := filepath.Abs(resource.Path())
if err != nil {
return common.FileBuffer{}, err
}
cmd := exec.Command("patch",
"-N",
"-i", patchfile,
)
cmd.Dir = targetDir
cmd.Stdout = os.Stderr
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should swallow the success-case output (patching file $filename) here, instead of just sending it to our stderr. Writing something on stderr causes Holo (the frontend) to always show the full "Working on $entity" display, even if we're not changing the entity.

Swallowing the output in the happy path would also make the tests slightly more portable since we can match and swallow both the GNU patch success message and the Busybox patch success message (and later also the BSD one).

cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
return common.FileBuffer{}, fmt.Errorf("execution failed: %s: %s", strings.Join(cmd.Args, " "), err.Error())
}

// Read the result back
//
// Allow `patch` to override everything but the filepath:
// - file type (changable with git-style "deleted file
// mode"/"new file mode" lines, which are implemented by at
// least GNU patch, if not in strict POSIX mode)
// - file permissions (changable with git-style "new mode"
// lines, which are implemented by at least GNU patch)
// - UID/GID (I don't know of a patch syntax that does this,
// but maybe it will exist in the future)
// - contents (obviously)
targetBuffer, err := common.NewFileBuffer(targetPath)
if err != nil {
return common.FileBuffer{}, err
}
targetBuffer.Path = entityBuffer.Path
return targetBuffer, nil
}
17 changes: 15 additions & 2 deletions doc/holo-files.8.pod
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,25 @@ default configuration for L<pacman(8)>, but enables the "Color" and
passthru /usr/share/holo/files/20-enable-color/etc/pacman.conf.holoscript

This allows the file contents to be modified; however, the file permissions and
ownership are not changed, and are inherited from the target base.
ownership are not changed, and are inherited from the target base or the
previous resource application step.

=item C<.patch> The resource file is understood to be a patch file that can be
fed to the L<patch(1)> program. Some patch formats have the ability to change
file type and file permissions; this is respected, making this is the only
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/this is the/this the/

resource format that can change file permissions.

The filename to modify is not passed to L<patch(1)>; instead, a copy of the
target is available as the only file in the directory passed to the C<-d> flag.
This means that the filename used within the patch file must have the same
basename as the entity being operated on. Any other files that the patch file
may create are ignored.

=item Otherwise, the resource file is a plain file or symlink that will just
overwrite the contents of the target base (and all previous resource application
steps). The ownership and file permissions are not set from the resource file,
and are inherited from the target base.
and are inherited from the target base or the previous resource application
step.

=back

Expand Down
43 changes: 43 additions & 0 deletions test/files/17-patches/expected-apply-output
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@

Working on file:/etc/symlink
store at target/var/lib/holo/files/base/etc/symlink
patch target/usr/share/holo/files/17-patches/etc/symlink.patch

patching symbolic link symlink

Working on file:/etc/symlink-to-plain
store at target/var/lib/holo/files/base/etc/symlink-to-plain
patch target/usr/share/holo/files/17-patches/etc/symlink-to-plain.patch

patching symbolic link symlink-to-plain
patching file symlink-to-plain

Working on file:/etc/txtfile
store at target/var/lib/holo/files/base/etc/txtfile
patch target/usr/share/holo/files/17-patches/etc/txtfile.patch

patching file txtfile

Working on file:/etc/txtfile-to-symlink
store at target/var/lib/holo/files/base/etc/txtfile-to-symlink
patch target/usr/share/holo/files/17-patches/etc/txtfile-to-symlink.patch

patching file txtfile-to-symlink
patching symbolic link txtfile-to-symlink

Working on file:/etc/txtfile-with-fuzz
store at target/var/lib/holo/files/base/etc/txtfile-with-fuzz
patch target/usr/share/holo/files/17-patches/etc/txtfile-with-fuzz.patch

patching file txtfile-with-fuzz
Hunk #1 succeeded at 1 with fuzz 1.

Working on file:/etc/txtfile-with-garbage
store at target/var/lib/holo/files/base/etc/txtfile-with-garbage
patch target/usr/share/holo/files/17-patches/etc/txtfile-with-garbage.patch

patching file txtfile-with-garbage
patching file garbage
patching file ls
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know how hard it would be to implement, but to avoid confusion and/or surprises, I'd rather have the apply step fail if some parts of the patch are not applicable to the target file.


exit status 0
59 changes: 59 additions & 0 deletions test/files/17-patches/expected-diff-output
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
diff --holo target/var/lib/holo/files/provisioned/etc/symlink target/etc/symlink
new file mode 120000
--- /dev/null
+++ target/etc/symlink
@@ -0,0 +1 @@
+txtfile
\ No newline at end of file
diff --holo target/var/lib/holo/files/provisioned/etc/symlink-to-plain target/etc/symlink-to-plain
new file mode 120000
--- /dev/null
+++ target/etc/symlink-to-plain
@@ -0,0 +1 @@
+txtfile
\ No newline at end of file
diff --holo target/var/lib/holo/files/provisioned/etc/txtfile target/etc/txtfile
new file mode 100644
--- /dev/null
+++ target/etc/txtfile
@@ -0,0 +1,6 @@
+foo
+foo
+foo
+baz
+bar
+bar
diff --holo target/var/lib/holo/files/provisioned/etc/txtfile-to-symlink target/etc/txtfile-to-symlink
new file mode 100644
--- /dev/null
+++ target/etc/txtfile-to-symlink
@@ -0,0 +1,6 @@
+foo
+foo
+foo
+baz
+bar
+bar
diff --holo target/var/lib/holo/files/provisioned/etc/txtfile-with-fuzz target/etc/txtfile-with-fuzz
new file mode 100644
--- /dev/null
+++ target/etc/txtfile-with-fuzz
@@ -0,0 +1,6 @@
+foo
+foo
+foo
+baz
+bar
+bar
diff --holo target/var/lib/holo/files/provisioned/etc/txtfile-with-garbage target/etc/txtfile-with-garbage
new file mode 100644
--- /dev/null
+++ target/etc/txtfile-with-garbage
@@ -0,0 +1,6 @@
+foo
+foo
+foo
+baz
+bar
+bar
exit status 0
26 changes: 26 additions & 0 deletions test/files/17-patches/expected-scan-output
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@

file:/etc/symlink
store at target/var/lib/holo/files/base/etc/symlink
patch target/usr/share/holo/files/17-patches/etc/symlink.patch

file:/etc/symlink-to-plain
store at target/var/lib/holo/files/base/etc/symlink-to-plain
patch target/usr/share/holo/files/17-patches/etc/symlink-to-plain.patch

file:/etc/txtfile
store at target/var/lib/holo/files/base/etc/txtfile
patch target/usr/share/holo/files/17-patches/etc/txtfile.patch

file:/etc/txtfile-to-symlink
store at target/var/lib/holo/files/base/etc/txtfile-to-symlink
patch target/usr/share/holo/files/17-patches/etc/txtfile-to-symlink.patch

file:/etc/txtfile-with-fuzz
store at target/var/lib/holo/files/base/etc/txtfile-with-fuzz
patch target/usr/share/holo/files/17-patches/etc/txtfile-with-fuzz.patch

file:/etc/txtfile-with-garbage
store at target/var/lib/holo/files/base/etc/txtfile-with-garbage
patch target/usr/share/holo/files/17-patches/etc/txtfile-with-garbage.patch

exit status 0
Loading