Description
Let an @optional item whose resolver cannot run come out as undefined (with a warning) instead of failing the whole load, as an opt-in. And let a plugin function that is not loaded sit in a branch that never runs.
Motivation
In a public repository, contributors and CI jobs have no access to the maintainers' 1Password, but the maintainers want signing credentials to come from it. Two things stand in the way, both reproduced on 1.21.0:
- An
@optional item whose value is op() fails the load when there is no token and no app:
# @plugin(@varlock/1password-plugin)
# @initOp(token=$OP_TOKEN, allowAppAuth=false, useCliWithServiceAccount=true)
# @defaultSensitive=false
# ---
# @type=opServiceAccountToken @internal @optional
OP_TOKEN=
# @optional @sensitive
SIGNING_PASSWORD=op("op://Vault/Item/password")
⛔ SIGNING_PASSWORD 🔐sensitive
└ undefined
- error resolving value: SchemaError: op(): Unable to authenticate with 1Password
- Gating it with
if() does not help when the plugin is only imported where it can authenticate:
# @type=boolean
SIGN=false
# @optional @sensitive
SIGNING_PASSWORD=if($SIGN, op("op://Vault/Item/password"))
- Unknown resolver function: op()
The workaround is to put every op() reference in separate files, imported only when a flag is set (enabled=...), and to make every script set the right flag. It works, but it's a file per use case and a flag per script, just to stop lookups from running where they can't.
Proposed Solution
- Opt-in per item or per plugin instance, e.g.
@optional={onResolveError=undefined} or @initOp(..., optionalOnAuthFailure=true): an optional item whose resolver fails for a missing credential resolves to undefined and prints a warning. Required items still fail as today, so @required=forEnv(production) (or a flag) keeps releases strict.
- Resolve function names lazily, so a plugin function in an
if() branch that is not taken does not error when the plugin is not loaded.
Alternatives
- Separate files per use case, imported behind flags (what we do now).
--filter per script to scope resolution, which moves the same bookkeeping into every script.
Additional Information
Typical case: code-signing credentials for an open-source app, where builds without credentials are expected to produce unsigned output.
Description
Let an
@optionalitem whose resolver cannot run come out as undefined (with a warning) instead of failing the whole load, as an opt-in. And let a plugin function that is not loaded sit in a branch that never runs.Motivation
In a public repository, contributors and CI jobs have no access to the maintainers' 1Password, but the maintainers want signing credentials to come from it. Two things stand in the way, both reproduced on 1.21.0:
@optionalitem whose value isop()fails the load when there is no token and no app:if()does not help when the plugin is only imported where it can authenticate:The workaround is to put every
op()reference in separate files, imported only when a flag is set (enabled=...), and to make every script set the right flag. It works, but it's a file per use case and a flag per script, just to stop lookups from running where they can't.Proposed Solution
@optional={onResolveError=undefined}or@initOp(..., optionalOnAuthFailure=true): an optional item whose resolver fails for a missing credential resolves to undefined and prints a warning. Required items still fail as today, so@required=forEnv(production)(or a flag) keeps releases strict.if()branch that is not taken does not error when the plugin is not loaded.Alternatives
--filterper script to scope resolution, which moves the same bookkeeping into every script.Additional Information
Typical case: code-signing credentials for an open-source app, where builds without credentials are expected to produce unsigned output.