Skip to content

Commit

Permalink
Add mkdir and symlink actions. (cashapp#366)
Browse files Browse the repository at this point in the history
I think it makes sense to have these as actions rather than requiring
`run { cmd = "/bin/ln ..." }`
  • Loading branch information
alecthomas authored Mar 9, 2023
1 parent 8b038d9 commit 33034b6
Show file tree
Hide file tree
Showing 15 changed files with 198 additions and 8 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.henv
.venv
/docs/.hugo_build.lock
testdata/env
build
Expand Down
15 changes: 15 additions & 0 deletions docs/docs/packaging/schema/mkdir.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
title: "on > mkdir"
---

Create a directory and any missing parents.

Used by: [on](../on#blocks)


## Attributes

| Attribute | Type | Description |
|-----------|------|-------------|
| `dir` | `string` | The absolute directory to create. |
| `mode` | `number?` | File mode of directory. |
2 changes: 2 additions & 0 deletions docs/docs/packaging/schema/on.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,7 @@ Used by: [channel](../channel#blocks) [darwin](../darwin#blocks) [linux](../linu
| [`copy { … }`](../copy) | A file to copy when the event is triggered. |
| [`delete { … }`](../delete) | Delete files. |
| [`message { … }`](../message) | Display a message to the user. |
| [`mkdir { … }`](../mkdir) | Create a directory and any missing parents. |
| [`rename { … }`](../rename) | Rename a file. |
| [`run { … }`](../run) | A command to run when the event is triggered. |
| [`symlink { … }`](../symlink) | Create a symbolic link. |
15 changes: 15 additions & 0 deletions docs/docs/packaging/schema/symlink.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
title: "on > symlink"
---

Create a symbolic link.

Used by: [on](../on#blocks)


## Attributes

| Attribute | Type | Description |
|-----------|------|-------------|
| `from` | `string` | The absolute source file to symlink from. |
| `to` | `string` | The absolute destination to symlink to. |
14 changes: 8 additions & 6 deletions docs/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,19 +49,21 @@ nav:
- packaging/schema/index.md
- packaging/schema/auto-version.md
- packaging/schema/channel.md
- packaging/schema/chmod.md
- packaging/schema/copy.md
- packaging/schema/darwin.md
- packaging/schema/delete.md
- packaging/schema/html.md
- packaging/schema/linux.md
- packaging/schema/manifest.md
- packaging/schema/message.md
- packaging/schema/on.md
- packaging/schema/platform.md
- packaging/schema/version.md
- packaging/schema/on.md
- packaging/schema/chmod.md
- packaging/schema/copy.md
- packaging/schema/delete.md
- packaging/schema/message.md
- packaging/schema/mkdir.md
- packaging/schema/rename.md
- packaging/schema/run.md
- packaging/schema/version.md
- packaging/schema/symlink.md

theme:
name: material
Expand Down
9 changes: 9 additions & 0 deletions integration/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,15 @@ func TestIntegration(t *testing.T) {
`,
expectations: exp{outputContains("testbin1-1.0.0 hook"), outputContains("testbin1-1.0.1 hook")},
},
{name: "SymlinkAndMkdirActionsWork",
preparations: prep{fixture("testenv3"), activate(".")},
script: `
hermit install testbin1
testbin1
testbin2
`,
expectations: exp{outputContains("testbin1 1.0.1")},
},
}

checkForShells(t)
Expand Down
7 changes: 7 additions & 0 deletions integration/testdata/testenv3/bin/README.hermit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Hermit environment

This is a [Hermit](https://github.com/cashapp/hermit) bin directory.

The symlinks in this directory are managed by Hermit and will automatically
download and install Hermit itself as well as packages. These packages are
local to this environment.
21 changes: 21 additions & 0 deletions integration/testdata/testenv3/bin/activate-hermit
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/bin/bash
# This file must be used with "source bin/activate-hermit" from bash or zsh.
# You cannot run it directly
#
# THIS FILE IS GENERATED; DO NOT MODIFY

if [ "${BASH_SOURCE-}" = "$0" ]; then
echo "You must source this script: \$ source $0" >&2
exit 33
fi

BIN_DIR="$(dirname "${BASH_SOURCE[0]:-${(%):-%x}}")"
if "${BIN_DIR}/hermit" noop > /dev/null; then
eval "$("${BIN_DIR}/hermit" activate "${BIN_DIR}/..")"

if [ -n "${BASH-}" ] || [ -n "${ZSH_VERSION-}" ]; then
hash -r 2>/dev/null
fi

echo "Hermit environment $("${HERMIT_ENV}"/bin/hermit env HERMIT_ENV) activated"
fi
43 changes: 43 additions & 0 deletions integration/testdata/testenv3/bin/hermit
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/bin/bash
#
# THIS FILE IS GENERATED; DO NOT MODIFY

set -eo pipefail

export HERMIT_USER_HOME=~

if [ -z "${HERMIT_STATE_DIR}" ]; then
case "$(uname -s)" in
Darwin)
export HERMIT_STATE_DIR="${HERMIT_USER_HOME}/Library/Caches/hermit"
;;
Linux)
export HERMIT_STATE_DIR="${XDG_CACHE_HOME:-${HERMIT_USER_HOME}/.cache}/hermit"
;;
esac
fi

export HERMIT_DIST_URL="${HERMIT_DIST_URL:-https://github.com/cashapp/hermit/releases/download/stable}"
HERMIT_CHANNEL="$(basename "${HERMIT_DIST_URL}")"
export HERMIT_CHANNEL
export HERMIT_EXE=${HERMIT_EXE:-${HERMIT_STATE_DIR}/pkg/hermit@${HERMIT_CHANNEL}/hermit}

if [ ! -x "${HERMIT_EXE}" ]; then
echo "Bootstrapping ${HERMIT_EXE} from ${HERMIT_DIST_URL}" 1>&2
INSTALL_SCRIPT="$(mktemp)"
# This value must match that of the install script
INSTALL_SCRIPT_SHA256="180e997dd837f839a3072a5e2f558619b6d12555cd5452d3ab19d87720704e38"
if [ "${INSTALL_SCRIPT_SHA256}" = "BYPASS" ]; then
curl -fsSL "${HERMIT_DIST_URL}/install.sh" -o "${INSTALL_SCRIPT}"
else
# Install script is versioned by its sha256sum value
curl -fsSL "${HERMIT_DIST_URL}/install-${INSTALL_SCRIPT_SHA256}.sh" -o "${INSTALL_SCRIPT}"
# Verify install script's sha256sum
openssl dgst -sha256 "${INSTALL_SCRIPT}" | \
awk -v EXPECTED="$INSTALL_SCRIPT_SHA256" \
'$2!=EXPECTED {print "Install script sha256 " $2 " does not match " EXPECTED; exit 1}'
fi
/bin/bash "${INSTALL_SCRIPT}" 1>&2
fi

exec "${HERMIT_EXE}" --level=fatal exec "$0" -- "$@"
4 changes: 4 additions & 0 deletions integration/testdata/testenv3/bin/hermit.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
env = {
TESTENV3: "yes",
}
sources = ["env:///packages"]
11 changes: 11 additions & 0 deletions integration/testdata/testenv3/packages/testbin1.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
description = "Test package one"
env = {FOO: "bar", TESTBIN1VERSION: "${version}"}
source = "${env}/packages/testbin1.tgz"
binaries = ["testbin1", "dir/testbin2"]

on unpack {
mkdir { dir = "${root}/dir" }
symlink { from = "${root}/testbin1" to = "${root}/dir/testbin2" }
}
version "1.0.0" "1.0.1" {}
Binary file added integration/testdata/testenv3/packages/testbin1.tgz
Binary file not shown.
47 changes: 46 additions & 1 deletion manifest/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,12 @@ type DeleteAction struct {
}

func (d *DeleteAction) position() hcl.Position { return d.Pos }
func (d *DeleteAction) String() string { return fmt.Sprintf("rm %s", strings.Join(d.Files, " ")) }
func (d *DeleteAction) String() string {
if d.Recursive {
return fmt.Sprintf("rm -r %s", strings.Join(d.Files, " "))
}
return fmt.Sprintf("rm %s", strings.Join(d.Files, " "))
}
func (d *DeleteAction) Apply(*Package) error { // nolint
for _, file := range d.Files {
if d.Recursive {
Expand Down Expand Up @@ -160,3 +165,43 @@ func (c *CopyAction) Apply(p *Package) error { // nolint
}
return os.Chmod(c.To, mode)
}

// MkdirAction is an action for creating a directory.
type MkdirAction struct {
Pos hcl.Position `hcl:"-"`

Dir string `hcl:"dir" help:"The absolute directory to create."`
Mode os.FileMode `hcl:"mode,optional" help:"File mode of directory."`
}

func (m *MkdirAction) position() hcl.Position { return m.Pos }
func (m *MkdirAction) String() string {
mode := m.Mode
if mode == 0 {
mode = 0755
}
return fmt.Sprintf("mkdir -m %04o -p %s", mode, shell.Quote(m.Dir))
}
func (m *MkdirAction) Apply(*Package) error { // nolint
mode := m.Mode
if mode == 0 {
mode = 0750
}
return os.MkdirAll(m.Dir, mode)
}

// SymlinkAction is an action for creating symlinks.
type SymlinkAction struct {
Pos hcl.Position `hcl:"-"`

From string `hcl:"from" help:"The absolute source file to symlink from."`
To string `hcl:"to" help:"The absolute destination to symlink to."`
}

func (s *SymlinkAction) position() hcl.Position { return s.Pos }
func (s *SymlinkAction) String() string {
return fmt.Sprintf("ln -sf %s %s", shell.Quote(s.From), shell.Quote(s.To))
}
func (s *SymlinkAction) Apply(*Package) error { // nolint
return os.Symlink(s.From, s.To)
}
9 changes: 8 additions & 1 deletion manifest/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -679,8 +679,15 @@ func newPackage(manifest *AnnotatedManifest, config Config, selector Selector) (
case *MessageAction:
action.Text = expand(action.Text, false)

case *SymlinkAction:
action.From = expand(action.From, false)
action.To = expand(action.To, false)

case *MkdirAction:
action.Dir = expand(action.Dir, false)

default:
panic("??")
panic(fmt.Sprintf("unsupported action %T", action))
}
}
}
Expand Down
8 changes: 8 additions & 0 deletions manifest/triggers.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ type Trigger struct {
Rename []*RenameAction `hcl:"rename,block" help:"Rename a file."`
Delete []*DeleteAction `hcl:"delete,block" help:"Delete files."`
Message []*MessageAction `hcl:"message,block" help:"Display a message to the user."`
Mkdir []*MkdirAction `hcl:"mkdir,block" help:"Create a directory and any missing parents."`
Symlink []*SymlinkAction `hcl:"symlink,block" help:"Create a symbolic link."`
}

// Ordered list of actions.
Expand All @@ -70,6 +72,12 @@ func (a *Trigger) Ordered() []Action {
for _, action := range a.Message {
out = append(out, action)
}
for _, action := range a.Mkdir {
out = append(out, action)
}
for _, action := range a.Symlink {
out = append(out, action)
}
sort.Slice(out, func(i, j int) bool {
return out[i].position().Line < out[j].position().Line
})
Expand Down

0 comments on commit 33034b6

Please sign in to comment.