-
Notifications
You must be signed in to change notification settings - Fork 5
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
base: master
Are you sure you want to change the base?
Changes from all commits
dc0aebd
cf1d60b
aa26621
610d23e
52364c3
d3c9cba
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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)) | ||
|
||
// 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should swallow the success-case output ( 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 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
/******************************************************************************* | ||
* | ||
* Copyright 2015 Stefan Majewsky <[email protected]> | ||
* Copyright 2017 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 ( | ||
"bytes" | ||
"fmt" | ||
"os" | ||
"os/exec" | ||
"strings" | ||
|
||
"github.com/holocm/holo/cmd/holo-files/internal/common" | ||
) | ||
|
||
// Holoscript is a Resource that is a script that edits the current | ||
// version of the entity. | ||
type Holoscript struct{ rawResource } | ||
|
||
// ApplicationStrategy implements the Resource interface. | ||
func (resource Holoscript) ApplicationStrategy() string { return "passthru" } | ||
|
||
// DiscardsPreviousBuffer implements the Resource interface. | ||
func (resource Holoscript) DiscardsPreviousBuffer() bool { return false } | ||
|
||
// ApplyTo implements the Resource interface. | ||
func (resource Holoscript) ApplyTo(entityBuffer common.FileBuffer) (common.FileBuffer, error) { | ||
//application of a holoscript requires file contents | ||
entityBuffer, err := entityBuffer.ResolveSymlink() | ||
if err != nil { | ||
return common.FileBuffer{}, err | ||
} | ||
|
||
//run command, fetch result file into buffer (not into the entity | ||
//directly, in order not to corrupt the file there if the script run fails) | ||
var stdout bytes.Buffer | ||
cmd := exec.Command(resource.Path()) | ||
cmd.Stdin = strings.NewReader(entityBuffer.Contents) | ||
cmd.Stdout = &stdout | ||
cmd.Stderr = os.Stderr | ||
err = cmd.Run() | ||
if err != nil { | ||
return common.FileBuffer{}, fmt.Errorf("execution of %s failed: %s", resource.Path(), err.Error()) | ||
} | ||
|
||
//result is the stdout of the script | ||
entityBuffer.Mode &^= os.ModeType | ||
entityBuffer.Contents = stdout.String() | ||
return entityBuffer, nil | ||
} |
There was a problem hiding this comment.
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. MaybeworkDir
andworkPath
, ortempDir
andtempPath
?