Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions condition/shell.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ func (c *shellCondition) Met(ctx context.Context) (bool, error) {
stdout, err := shell.Run(ctx, c.shellName, c.command, nil)

if c.expectedOutput != "" {
if err != nil {
return false, err
}
return stdout == c.expectedOutput, nil
}

Expand Down
13 changes: 7 additions & 6 deletions condition/shell_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,19 @@ func TestShell_Met_OutputMatch(t *testing.T) {
command string
expect string
wantMet bool
wantErr bool
}{
{"output matches", "echo -n hello", "hello", true},
{"output differs", "echo -n world", "hello", false},
{"trailing newline trimmed", "echo hello", "hello", true},
{"command fails, no match", "false", "hello", false},
{"output matches", "echo -n hello", "hello", true, false},
{"output differs", "echo -n world", "hello", false, false},
{"trailing newline trimmed", "echo hello", "hello", true, false},
{"command fails, returns error", "false", "hello", false, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := Shell(tt.command).In("bash").Match(tt.expect)
met, err := c.Met(ctx)
if err != nil {
t.Fatalf("Met() error = %v", err)
if (err != nil) != tt.wantErr {
t.Fatalf("Met() error = %v, wantErr %v", err, tt.wantErr)
}
if met != tt.wantMet {
t.Errorf("Met() = %v, want %v", met, tt.wantMet)
Expand Down
5 changes: 3 additions & 2 deletions docs/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ All option structs share these common fields directly on each Opts struct:

**Auto-edges:** Dependencies are detected automatically: `service:X` depends on `package:X`, files depend on parent directories, services depend on config files. Use typed resource conditions (e.g. `condition.Package`, `condition.File`) for dependencies auto-edges cannot detect.

**Auto-grouping:** Package resources with the same manager and state are automatically batched into a single install/remove transaction (e.g., `apt install git curl neovim` instead of three separate calls). Set `AutoGroup` to a `*bool` pointing to `false` to opt out (e.g., `falseVal := false; Meta{AutoGroup: &falseVal}`).
**Auto-grouping:** Package resources with the same manager and state are automatically batched into a single install/remove transaction (e.g., `apt install git curl neovim` instead of three separate calls). Set `AutoGroup` to a `*bool` pointing to `false` to opt out (e.g., `falseVal := false; dsl.PackageOpts{State: dsl.Present, AutoGroup: &falseVal}`).

Use typed resource conditions (e.g. `condition.Package`, `condition.File`) for dependencies auto-edges cannot detect:

Expand Down Expand Up @@ -555,7 +555,7 @@ r.Exec(name string, opts dsl.ExecOpts)
| `Retries` | `int` | `0` | Number of retry attempts on failure. |
| `RetryDelay` | `time.Duration` | `0` | Delay between retries. |

**Idempotency:** Not inherently idempotent. Always use `condition.Shell("...")` on `Meta.Condition` to make it conditional.
**Idempotency:** Not inherently idempotent. Always use `condition.Shell("...")` on the `Condition` field to make it conditional.

**Examples:**

Expand Down Expand Up @@ -1388,4 +1388,5 @@ r.Exec("complex-gate", dsl.ExecOpts{
| `condition.RegistryValueExists(key, value)` | Windows | Named value exists under key | RegNotifyChangeKeyValue on nearest existing ancestor |
| `condition.RegistryValueEquals(key, value, data)` | Windows | Named value's data equals the string form of data | RegNotifyChangeKeyValue on nearest existing ancestor |

All resource types have typed constructors. See [condition/resource.go](../condition/resource.go) for the full list.

7 changes: 4 additions & 3 deletions dsl/dsl.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ type FileOpts struct {
Checksum string // expected SHA-256 hex digest (only with URL)
BlockName string // when set, manages a tagged block within the file instead of owning the entire file
BlockComment string // comment prefix for block markers (default: "#")
State ResourceState
Critical bool
Noop bool
Retry int
Expand Down Expand Up @@ -87,11 +88,11 @@ type ExecOpts struct {
ShellParams []string // when set, replaces default shell flags
Dir string
Env []string
Retries int
RetryDelay time.Duration
Retries int // exec-specific: retries within a single Apply call
RetryDelay time.Duration // delay between exec-specific retries
Critical bool
Noop bool
Retry int
Retry int // engine-level: how many times the daemon retries after Check/Apply failure
Limit float64
AutoEdge *bool
AutoGroup *bool
Expand Down
7 changes: 7 additions & 0 deletions dsl/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ import (
)

func newFileExtension(path string, opts FileOpts) extensions.Extension {
state := ""
if opts.State == Absent {
state = "absent"
} else if opts.State == Present {
state = "present"
}
return extfile.New(path, extfile.Opts{
Content: opts.Content,
Mode: opts.Mode,
Expand All @@ -25,6 +31,7 @@ func newFileExtension(path string, opts FileOpts) extensions.Extension {
Checksum: opts.Checksum,
BlockName: opts.BlockName,
BlockComment: opts.BlockComment,
State: state,
Critical: opts.Critical,
})
}
Expand Down
3 changes: 3 additions & 0 deletions extensions/cron/cron.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,5 +72,8 @@ func (c *Cron) validate() error {
return fmt.Errorf("cron %s: %s contains invalid character (newline, carriage return, or null)", c.Name, pair.name)
}
}
if strings.Contains(c.Name, "..") {
return fmt.Errorf("cron %s: Name contains path traversal sequence", c.Name)
}
return nil
}
Loading
Loading