lang, engine: Add a metaparam for catching accidental dollar signs

Let's make our life easier for users!
This commit is contained in:
James Shubin
2024-08-22 20:41:48 -04:00
parent 8dc0d44513
commit a0972c0752
10 changed files with 72 additions and 1 deletions

View File

@@ -274,6 +274,29 @@ and it can't guarantee it if the resource is blocked because of a failed
pre-requisite resource. pre-requisite resource.
*XXX: This is currently not implemented!* *XXX: This is currently not implemented!*
#### Dollar
Boolean. Dollar allows you to have a resource name that starts with a `$` sign.
This is false by default. This helps you catch cases when you write code like:
```mcl
$foo = "/tmp/file1"
file "$foo" {} # incorrect!
```
The above code would ignore the `$foo` variable and attempt to make a file named
`$foo` which would obviously not work. To correctly interpolate a variable, you
need to surround the name with curly braces.
```mcl
$foo = "/tmp/file1"
file "${foo}" {} # correct!
```
This meta param is a safety measure to make your life easier. It works for all
resources. If someone comes up with a resource which would routinely start with
a dollar sign, then we can revisit the default for this resource kind.
#### Reverse #### Reverse
Boolean. Reverse is a property that some resources can implement that specifies Boolean. Reverse is a property that some resources can implement that specifies

View File

@@ -52,6 +52,7 @@ var DefaultMetaParams = &MetaParams{
//Sema: []string{}, //Sema: []string{},
Rewatch: false, Rewatch: false,
Realize: false, // true would be more awesome, but unexpected for users Realize: false, // true would be more awesome, but unexpected for users
Dollar: false,
} }
// MetaRes is the interface a resource must implement to support meta params. // MetaRes is the interface a resource must implement to support meta params.
@@ -132,6 +133,13 @@ type MetaParams struct {
// the resource is blocked because of a failed pre-requisite resource. // the resource is blocked because of a failed pre-requisite resource.
// XXX: Not implemented! // XXX: Not implemented!
Realize bool `yaml:"realize"` Realize bool `yaml:"realize"`
// Dollar allows you to name a resource to start with the dollar
// character. We don't allow this by default since it's probably not
// needed, and is more likely to be a typo where the user forgot to
// interpolate a variable name. In the rare case when it's needed, you
// can disable that check with this meta param.
Dollar bool `yaml:"dollar"`
} }
// Cmp compares two AutoGroupMeta structs and determines if they're equivalent. // Cmp compares two AutoGroupMeta structs and determines if they're equivalent.
@@ -178,6 +186,9 @@ func (obj *MetaParams) Cmp(meta *MetaParams) error {
if obj.Realize != meta.Realize { if obj.Realize != meta.Realize {
return fmt.Errorf("values for Realize are different") return fmt.Errorf("values for Realize are different")
} }
if obj.Dollar != meta.Dollar {
return fmt.Errorf("values for Dollar are different")
}
return nil return nil
} }
@@ -218,6 +229,7 @@ func (obj *MetaParams) Copy() *MetaParams {
Sema: sema, Sema: sema,
Rewatch: obj.Rewatch, Rewatch: obj.Rewatch,
Realize: obj.Realize, Realize: obj.Realize,
Dollar: obj.Dollar,
} }
} }

View File

@@ -33,6 +33,7 @@ import (
"context" "context"
"encoding/gob" "encoding/gob"
"fmt" "fmt"
"strings"
"github.com/purpleidea/mgmt/engine/local" "github.com/purpleidea/mgmt/engine/local"
"github.com/purpleidea/mgmt/pgraph" "github.com/purpleidea/mgmt/pgraph"
@@ -272,6 +273,12 @@ func Validate(res Res) error {
return errwrap.Wrapf(err, "the Res has an invalid meta param") return errwrap.Wrapf(err, "the Res has an invalid meta param")
} }
// TODO: pull dollar prefix from a constant
// This catches typos where the user meant to use ${var} interpolation.
if !res.MetaParams().Dollar && strings.HasPrefix(res.Name(), "$") {
return fmt.Errorf("the Res name starts with a $")
}
return res.Validate() return res.Validate()
} }

View File

@@ -1087,6 +1087,9 @@ func (obj *StmtRes) metaparams(table map[interfaces.Func]types.Value, res engine
case "realize": case "realize":
meta.Realize = v.Bool() // must not panic meta.Realize = v.Bool() // must not panic
case "dollar":
meta.Dollar = v.Bool() // must not panic
case "reverse": case "reverse":
if rm != nil { if rm != nil {
rm.Disabled = !v.Bool() // must not panic rm.Disabled = !v.Bool() // must not panic
@@ -1150,6 +1153,9 @@ func (obj *StmtRes) metaparams(table map[interfaces.Func]types.Value, res engine
if val, exists := v.Struct()["realize"]; exists { if val, exists := v.Struct()["realize"]; exists {
meta.Realize = val.Bool() // must not panic meta.Realize = val.Bool() // must not panic
} }
if val, exists := v.Struct()["dollar"]; exists {
meta.Dollar = val.Bool() // must not panic
}
if val, exists := v.Struct()["reverse"]; exists && rm != nil { if val, exists := v.Struct()["reverse"]; exists && rm != nil {
rm.Disabled = !val.Bool() // must not panic rm.Disabled = !val.Bool() // must not panic
} }
@@ -1757,6 +1763,7 @@ func (obj *StmtResMeta) Init(data *interfaces.Data) error {
case "sema": case "sema":
case "rewatch": case "rewatch":
case "realize": case "realize":
case "dollar":
case "reverse": case "reverse":
case "autoedge": case "autoedge":
case "autogroup": case "autogroup":
@@ -1966,6 +1973,9 @@ func (obj *StmtResMeta) TypeCheck(kind string) ([]*interfaces.UnificationInvaria
case "realize": case "realize":
typExpr = types.TypeBool typExpr = types.TypeBool
case "dollar":
typExpr = types.TypeBool
case "reverse": case "reverse":
// TODO: We might want more parameters about how to reverse. // TODO: We might want more parameters about how to reverse.
typExpr = types.TypeBool typExpr = types.TypeBool
@@ -1982,7 +1992,7 @@ func (obj *StmtResMeta) TypeCheck(kind string) ([]*interfaces.UnificationInvaria
// FIXME: allow partial subsets of this struct, and in any order // FIXME: allow partial subsets of this struct, and in any order
// FIXME: we might need an updated unification engine to do this // FIXME: we might need an updated unification engine to do this
wrap := func(reverse *types.Type) *types.Type { wrap := func(reverse *types.Type) *types.Type {
return types.NewType(fmt.Sprintf("struct{noop bool; retry int; retryreset bool; delay int; poll int; limit float; burst int; reset bool; sema []str; rewatch bool; realize bool; reverse %s; autoedge bool; autogroup bool}", reverse.String())) return types.NewType(fmt.Sprintf("struct{noop bool; retry int; retryreset bool; delay int; poll int; limit float; burst int; reset bool; sema []str; rewatch bool; realize bool; dollar bool; reverse %s; autoedge bool; autogroup bool}", reverse.String()))
} }
// TODO: We might want more parameters about how to reverse. // TODO: We might want more parameters about how to reverse.
typExpr = wrap(types.TypeBool) typExpr = wrap(types.TypeBool)

View File

@@ -41,6 +41,7 @@ import (
// Parse performs string interpolation on the input. It returns the list of // Parse performs string interpolation on the input. It returns the list of
// tokens found. It looks for variables of the format ${foo}. The curly braces // tokens found. It looks for variables of the format ${foo}. The curly braces
// are required. // are required.
// XXX: Pull dollar sign and curly chars from VarPrefix and other constants.
func Parse(data string) (out Stream, _ error) { func Parse(data string) (out Stream, _ error) {
var ( var (
// variables used by Ragel // variables used by Ragel

View File

@@ -14,6 +14,7 @@ test "test" {
sema => ["foo:1", "bar:3",], sema => ["foo:1", "bar:3",],
rewatch => false, rewatch => false,
realize => true, realize => true,
dollar => false,
reverse => true, reverse => true,
autoedge => true, autoedge => true,
autogroup => true, autogroup => true,

View File

@@ -14,6 +14,7 @@ test "test" {
sema => ["foo:1", "bar:3",], sema => ["foo:1", "bar:3",],
rewatch => false, rewatch => false,
realize => true, realize => true,
dollar => false,
reverse => true, reverse => true,
autoedge => true, autoedge => true,
autogroup => true, autogroup => true,
@@ -31,6 +32,7 @@ Edge: const -> composite # autoedge
Edge: const -> composite # autogroup Edge: const -> composite # autogroup
Edge: const -> composite # burst Edge: const -> composite # burst
Edge: const -> composite # delay Edge: const -> composite # delay
Edge: const -> composite # dollar
Edge: const -> composite # limit Edge: const -> composite # limit
Edge: const -> composite # noop Edge: const -> composite # noop
Edge: const -> composite # poll Edge: const -> composite # poll
@@ -60,3 +62,4 @@ Vertex: const
Vertex: const Vertex: const
Vertex: const Vertex: const
Vertex: const Vertex: const
Vertex: const

View File

@@ -14,6 +14,7 @@ test "t1" {
# sema => ["foo:1", "bar:3",], # sema => ["foo:1", "bar:3",],
# rewatch => false, # rewatch => false,
# realize => true, # realize => true,
# dollar => false,
# reverse => true, # reverse => true,
# autoedge => true, # autoedge => true,
# autogroup => true, # autogroup => true,
@@ -29,6 +30,7 @@ test "t1" {
Meta:sema => ["foo:1", "bar:3",], Meta:sema => ["foo:1", "bar:3",],
Meta:rewatch => false, Meta:rewatch => false,
Meta:realize => true, Meta:realize => true,
Meta:dollar => false,
Meta:reverse => true, Meta:reverse => true,
Meta:autoedge => true, Meta:autoedge => true,
Meta:autogroup => true, Meta:autogroup => true,

View File

@@ -0,0 +1,5 @@
-- main.mcl --
$foo = "/tmp/file1"
test "$foo" {} # catch simple typos! should be "${foo}"
-- OUTPUT --
# err: errValidate: test[$foo] did not Validate: the Res name starts with a $

View File

@@ -0,0 +1,7 @@
-- main.mcl --
$foo = "/tmp/file1"
test "$foo" {
Meta:dollar => true, # allow what would normally be a typo!
}
-- OUTPUT --
Vertex: test[$foo]