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 5 commits
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
143 changes: 45 additions & 98 deletions cmd/holo-files/internal/impl/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,118 +22,65 @@
package impl

import (
"bytes"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"

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

//Resource represents a single file in $HOLO_RESOURCE_DIR. The string
//stored in it is the path to the repo file (also accessible as Path()).
type Resource string

//NewResource creates a Resource instance when its path in the file system is
//known.
func NewResource(path string) Resource {
return Resource(path)
//Resource represents a single file in $HOLO_RESOURCE_DIR.
type Resource interface {
// Path returns the path to this resource in the file system.
Path() string

// Disambiguator returns the disambiguator, i.e. the Path()
// element before the EntityPath() that disambiguates multiple
// resources for the same entity.
Disambiguator() string

// EntityPath returns the path to the corresponding entity.
EntityPath() string

// ApplicationStrategy returns the human-readable name for the
// strategy that will be employed to apply this resource.
ApplicationStrategy() string

// DiscardsPreviousBuffer indicates whether applying this
// resource will discard the previous file buffer (and thus
// the effect of all previous resources). This is used as a
// hint by the application algorithm to decide whether
// application steps can be skipped completely.
DiscardsPreviousBuffer() bool

// ApplyTo applies this Resource to a file buffer, as part of
// the `holo apply` algorithm.
ApplyTo(entityBuffer common.FileBuffer) (common.FileBuffer, error)
}

//Path returns the path to this resource in the file system.
func (resource Resource) Path() string {
return string(resource)
type rawResource struct {
path string
disambiguator string
entityPath string
}

//EntityPath returns the path to the corresponding entity.
func (resource Resource) EntityPath() string {
//the optional ".holoscript" suffix appears only on resources
path := resource.Path()
path = strings.TrimSuffix(path, ".holoscript")
func (resource rawResource) Path() string { return resource.path }
func (resource rawResource) Disambiguator() string { return resource.disambiguator }
func (resource rawResource) EntityPath() string { return resource.entityPath }

//make path relative
//NewResource creates a Resource instance when its path in the file system is
//known.
func NewResource(path string) Resource {
relPath, _ := filepath.Rel(common.ResourceDirectory(), path)
//remove the disambiguation path element to get to the relPath for the ConfigFile
//e.g. path = '/usr/share/holo/files/23-foo/etc/foo.conf'
// -> relPath = '23-foo/etc/foo.conf'
// -> relPath = 'etc/foo.conf'
segments := strings.SplitN(relPath, fmt.Sprintf("%c", filepath.Separator), 2)
relPath = segments[1]

return relPath
}

//Disambiguator returns the disambiguator, i.e. the Path() element before the
//EntityPath() that disambiguates multiple resources for the same entity.
func (resource Resource) Disambiguator() string {
//make path relative to ResourceDirectory()
relPath, _ := filepath.Rel(common.ResourceDirectory(), resource.Path())
//the disambiguator is the first path element in there
segments := strings.SplitN(relPath, fmt.Sprintf("%c", filepath.Separator), 2)
return segments[0]
}

//ApplicationStrategy returns the human-readable name for the strategy that
//will be employed to apply this repo file.
func (resource Resource) ApplicationStrategy() string {
if strings.HasSuffix(resource.Path(), ".holoscript") {
return "passthru"
segments := strings.SplitN(relPath, string(filepath.Separator), 2)
raw := rawResource{
path: path,
disambiguator: segments[0],
entityPath: strings.TrimSuffix(segments[1], ".holoscript"),
}
return "apply"
}

//DiscardsPreviousBuffer indicates whether applying this file will discard the
//previous file buffer (and thus the effect of all previous application steps).
//This is used as a hint by the application algorithm to decide whether
//application steps can be skipped completely.
func (resource Resource) DiscardsPreviousBuffer() bool {
return resource.ApplicationStrategy() == "apply"
}

//ApplyTo applies this Resource to a file buffer, as part of the `holo apply`
//algorithm. Regular repofiles will replace the file buffer, while a holoscript
//will be executed on the file buffer to obtain the new buffer.
func (resource Resource) ApplyTo(entityBuffer common.FileBuffer) (common.FileBuffer, error) {
if resource.ApplicationStrategy() == "apply" {
resourceBuffer, err := common.NewFileBuffer(resource.Path())
if err != nil {
return common.FileBuffer{}, err
}
entityBuffer.Contents = resourceBuffer.Contents
entityBuffer.Mode = (entityBuffer.Mode &^ os.ModeType) | (resourceBuffer.Mode & os.ModeType)

//since Linux disregards mode flags on symlinks and always reports 0777 perms,
//normalize the mode thusly to make FileBuffer.EqualTo() work reliably
if entityBuffer.Mode&os.ModeSymlink != 0 {
entityBuffer.Mode = os.ModeSymlink | os.ModePerm
}
return entityBuffer, nil
if strings.HasSuffix(raw.path, ".holoscript") {
return Holoscript{raw}
}

//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
return StaticResource{raw}
}

//Resources holds a slice of Resource instances, and implements some methods
Expand Down
68 changes: 68 additions & 0 deletions cmd/holo-files/internal/impl/resource_script.go
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
}
55 changes: 55 additions & 0 deletions cmd/holo-files/internal/impl/resource_static.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*******************************************************************************
*
* 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 (
"os"

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

// StaticResource is a Resource that is a plain static file that
// replaces the current version of the entity.
type StaticResource struct{ rawResource }

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

// DiscardsPreviousBuffer implements the Resource interface.
func (resource StaticResource) DiscardsPreviousBuffer() bool { return true }

// ApplyTo implements the Resource interface.
func (resource StaticResource) ApplyTo(entityBuffer common.FileBuffer) (common.FileBuffer, error) {
resourceBuffer, err := common.NewFileBuffer(resource.Path())
if err != nil {
return common.FileBuffer{}, err
}
entityBuffer.Contents = resourceBuffer.Contents
entityBuffer.Mode = (entityBuffer.Mode &^ os.ModeType) | (resourceBuffer.Mode & os.ModeType)

//since Linux disregards mode flags on symlinks and always reports 0777 perms,
//normalize the mode thusly to make FileBuffer.EqualTo() work reliably
if entityBuffer.Mode&os.ModeSymlink != 0 {
entityBuffer.Mode = os.ModeSymlink | os.ModePerm
}
return entityBuffer, nil
}
39 changes: 27 additions & 12 deletions doc/holo-files.8.pod
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,20 @@ Resource files are applied on the B<target base>, the initial version of the
target file that was found during the first C<holo apply> run. This target base
is saved at F</var/lib/holo/files/base/$target>.

Resource files that are plain files or symlinks will just overwrite the target
base (or all previous entries), whereas executable resource files with an extra
C<.holoscript> suffix can be used to modify the target base (or the result of a
previous application step). The target contents will be piped through the
script. This is typically used when the default configuration for an
application shall be used, but with some minor modifications. The following
example uses the default configuration for L<pacman(8)>, but enables the
"Color" and "TotalDownload" options:
How resource files are applied to the target depends on each resource's file
extension:

=over 4

=item C<.holoscript> The resource file is understood to be an executable program
that can be used to modify the target. The target contents (either the original
target base, or the result of a previous resource application) will be piped
through the holoscript program.

This is typically used when the default configuration for an application shall
be used, but with some minor modifications. The following example uses the
default configuration for L<pacman(8)>, but enables the "Color" and
"TotalDownload" options:

$ cat /usr/share/holo/files/20-enable-color/etc/pacman.conf.holoscript
#!/bin/sh
Expand All @@ -59,10 +65,19 @@ example uses the default configuration for L<pacman(8)>, but enables the
store at /var/lib/holo/files/base/etc/pacman.conf
passthru /usr/share/holo/files/20-enable-color/etc/pacman.conf.holoscript

When writing the new target file, ownership and permissions will be copied from
the target base, and thus from the original target file. Furthermore, a copy of
the provisioned target file is written to
F</var/lib/holo/files/provisioned/$target> for use by C<holo diff file:$target>.
This allows the file contents to be modified; however, the file permissions and
ownership are not changed, and are inherited from the target base.

=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.
Copy link
Contributor

Choose a reason for hiding this comment

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

Nitpick: "but are inherited"


=back

When writing the new target file, a copy of the provisioned target file is
written to F</var/lib/holo/files/provisioned/$target> for use by C<holo diff
file:$target>.

In normal operation, holo-files will refuse to operate on a target file that
has been modified or deleted by the user or by another program. Apply
Expand Down
2 changes: 1 addition & 1 deletion util/dump-to-tree.sh
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ while read_and_inc FILE_TYPE FILE_MODE FILE_PATH; do
file)
install -D -m "${FILE_MODE}" /dev/null "${FILE_PATH}"
# header is followed by file content, terminated by a separator line like "---------------"
while IFS='' read_and_inc_or_fail LINE; do
while IFS='' read_and_inc_or_fail -r LINE; do
if [[ "${LINE}" =~ ^-+$ ]]; then
break
fi
Expand Down