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

Add the idea of "env-flags" #886

Merged
merged 2 commits into from
Jun 13, 2024
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
54 changes: 27 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,8 @@ OPTIONS

Many options can be specified as either a commandline flag or an environment
variable, but flags are preferred because a misspelled flag is a fatal
error while a misspelled environment variable is silently ignored.
error while a misspelled environment variable is silently ignored. Some
options can only be specified as an environment variable.

--add-user, $GITSYNC_ADD_USER
Add a record to /etc/passwd for the current UID/GID. This is
Expand All @@ -161,11 +162,12 @@ OPTIONS

--credential <string>, $GITSYNC_CREDENTIAL
Make one or more credentials available for authentication (see git
help credential). This is similar to --username and --password or
--password-file, but for specific URLs, for example when using
submodules. The value for this flag is either a JSON-encoded
object (see the schema below) or a JSON-encoded list of that same
object type. This flag may be specified more than once.
help credential). This is similar to --username and
$GITSYNC_PASSWORD or --password-file, but for specific URLs, for
example when using submodules. The value for this flag is either a
JSON-encoded object (see the schema below) or a JSON-encoded list
of that same object type. This flag may be specified more than
once.

Object schema:
- url: string, required
Expand Down Expand Up @@ -251,7 +253,7 @@ OPTIONS
useful in cases where data produced by git-sync is used by a
different UID. This replaces the older --change-permissions flag.

-h, --help
-?, -h, --help
Print help text and exit.

--http-bind <string>, $GITSYNC_HTTP_BIND
Expand Down Expand Up @@ -294,16 +296,14 @@ OPTIONS
--one-time, $GITSYNC_ONE_TIME
Exit after one sync.

--password <string>, $GITSYNC_PASSWORD
$GITSYNC_PASSWORD
The password or personal access token (see github docs) to use for
git authentication (see --username). NOTE: for security reasons,
users should prefer --password-file or $GITSYNC_PASSWORD_FILE for
specifying the password.
git authentication (see --username). See also --password-file.

--password-file <string>, $GITSYNC_PASSWORD_FILE
The file from which the password or personal access token (see
github docs) to use for git authentication (see --username) will be
read.
read. See also $GITSYNC_PASSWORD.

--period <duration>, $GITSYNC_PERIOD
How long to wait between sync attempts. This must be at least
Expand Down Expand Up @@ -376,8 +376,8 @@ OPTIONS

--username <string>, $GITSYNC_USERNAME
The username to use for git authentication (see --password-file or
--password). If more than one username and password is required
(e.g. with submodules), use --credential.
$GITSYNC_PASSWORD). If more than one username and password is
required (e.g. with submodules), use --credential.

-v, --verbose <int>, $GITSYNC_VERBOSE
Set the log verbosity level. Logs at this level and lower will be
Expand Down Expand Up @@ -435,31 +435,31 @@ AUTHENTICATION
and "git@example.com:repo" will try to use SSH.

username/password
The --username (GITSYNC_USERNAME) and --password-file
(GITSYNC_PASSWORD_FILE) or --password (GITSYNC_PASSWORD) flags
will be used. To prevent password leaks, the --password-file flag
or GITSYNC_PASSWORD environment variable is almost always
preferred to the --password flag.
The --username ($GITSYNC_USERNAME) and $GITSYNC_PASSWORD or
--password-file ($GITSYNC_PASSWORD_FILE) flags will be used. To
prevent password leaks, the --password-file flag or
$GITSYNC_PASSWORD environment variable is almost always preferred
to the --password flag, which is deprecated.

A variant of this is --askpass-url (GITSYNC_ASKPASS_URL), which
A variant of this is --askpass-url ($GITSYNC_ASKPASS_URL), which
consults a URL (e.g. http://metadata) to get credentials on each
sync.

When using submodules it may be necessary to specify more than one
username and password, which can be done with --credential
(GITSYNC_CREDENTIAL). All of the username+password pairs, from
both --username/--password and --credential are fed into 'git
credential approve'.
($GITSYNC_CREDENTIAL). All of the username+password pairs, from
both --username/$GITSYNC_PASSWORD and --credential are fed into
'git credential approve'.

SSH
When an SSH transport is specified, the key(s) defined in
--ssh-key-file (GITSYNC_SSH_KEY_FILE) will be used. Users are
--ssh-key-file ($GITSYNC_SSH_KEY_FILE) will be used. Users are
strongly advised to also use --ssh-known-hosts
(GITSYNC_SSH_KNOWN_HOSTS) and --ssh-known-hosts-file
(GITSYNC_SSH_KNOWN_HOSTS_FILE) when using SSH.
($GITSYNC_SSH_KNOWN_HOSTS) and --ssh-known-hosts-file
($GITSYNC_SSH_KNOWN_HOSTS_FILE) when using SSH.

cookies
When --cookie-file (GITSYNC_COOKIE_FILE) is specified, the
When --cookie-file ($GITSYNC_COOKIE_FILE) is specified, the
associated cookies can contain authentication information.

HOOKS
Expand Down
101 changes: 95 additions & 6 deletions env.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,16 @@ limitations under the License.
package main

import (
"cmp"
"fmt"
"io"
"os"
"slices"
"strconv"
"strings"
"time"

"github.com/spf13/pflag"
)

func envString(def string, key string, alts ...string) string {
Expand All @@ -30,12 +35,25 @@ func envString(def string, key string, alts ...string) string {
}
for _, alt := range alts {
if val := os.Getenv(alt); val != "" {
fmt.Fprintf(os.Stderr, "env %s has been deprecated, use %s instead\n", alt, key)
fmt.Fprintf(os.Stderr, "env $%s has been deprecated, use $%s instead\n", alt, key)
return val
}
}
return def
}
func envFlagString(key string, def string, usage string, alts ...string) *string {
registerEnvFlag(key, "string", usage)
val := envString(def, key, alts...)
// also expose it as a flag, for easier testing
flName := "__env__" + key
flHelp := "DO NOT SET THIS FLAG EXCEPT IN TESTS; use $" + key
newExplicitFlag(&val, flName, flHelp, pflag.String)
if err := pflag.CommandLine.MarkHidden(flName); err != nil {
fmt.Fprintf(os.Stderr, "FATAL: %v\n", err)
os.Exit(1)
}
return &val
}

func envStringArray(def string, key string, alts ...string) []string {
parse := func(s string) []string {
Expand All @@ -47,7 +65,7 @@ func envStringArray(def string, key string, alts ...string) []string {
}
for _, alt := range alts {
if val := os.Getenv(alt); val != "" {
fmt.Fprintf(os.Stderr, "env %s has been deprecated, use %s instead\n", alt, key)
fmt.Fprintf(os.Stderr, "env $%s has been deprecated, use $%s instead\n", alt, key)
return parse(val)
}
}
Expand All @@ -68,7 +86,7 @@ func envBoolOrError(def bool, key string, alts ...string) (bool, error) {
}
for _, alt := range alts {
if val := os.Getenv(alt); val != "" {
fmt.Fprintf(os.Stderr, "env %s has been deprecated, use %s instead\n", alt, key)
fmt.Fprintf(os.Stderr, "env $%s has been deprecated, use $%s instead\n", alt, key)
return parse(alt, val)
}
}
Expand Down Expand Up @@ -98,7 +116,7 @@ func envIntOrError(def int, key string, alts ...string) (int, error) {
}
for _, alt := range alts {
if val := os.Getenv(alt); val != "" {
fmt.Fprintf(os.Stderr, "env %s has been deprecated, use %s instead\n", alt, key)
fmt.Fprintf(os.Stderr, "env $%s has been deprecated, use $%s instead\n", alt, key)
return parse(alt, val)
}
}
Expand Down Expand Up @@ -128,7 +146,7 @@ func envFloatOrError(def float64, key string, alts ...string) (float64, error) {
}
for _, alt := range alts {
if val := os.Getenv(alt); val != "" {
fmt.Fprintf(os.Stderr, "env %s has been deprecated, use %s instead\n", alt, key)
fmt.Fprintf(os.Stderr, "env $%s has been deprecated, use $%s instead\n", alt, key)
return parse(alt, val)
}
}
Expand Down Expand Up @@ -158,7 +176,7 @@ func envDurationOrError(def time.Duration, key string, alts ...string) (time.Dur
}
for _, alt := range alts {
if val := os.Getenv(alt); val != "" {
fmt.Fprintf(os.Stderr, "env %s has been deprecated, use %s instead\n", alt, key)
fmt.Fprintf(os.Stderr, "env $%s has been deprecated, use $%s instead\n", alt, key)
return parse(alt, val)
}
}
Expand All @@ -173,3 +191,74 @@ func envDuration(def time.Duration, key string, alts ...string) time.Duration {
}
return val
}

// explicitFlag is a pflag.Value which only sets the real value if the flag is
// set to a non-zero-value.
type explicitFlag[T comparable] struct {
pflag.Value
realPtr *T
flagPtr *T
}

// newExplicitFlag allocates an explicitFlag
func newExplicitFlag[T comparable](ptr *T, name, usage string, fn func(name string, value T, usage string) *T) {
h := &explicitFlag[T]{
realPtr: ptr,
}
var zero T
h.flagPtr = fn(name, zero, usage)
fl := pflag.CommandLine.Lookup(name)
// wrap the original pflag.Value with our own
h.Value = fl.Value
fl.Value = h
}

func (h *explicitFlag[T]) Set(val string) error {
if err := h.Value.Set(val); err != nil {
return err
}
var zero T
if v := *h.flagPtr; v != zero {
*h.realPtr = v
}
return nil
}

// envFlag is like a flag in that it is declared with a type, validated, and
// shows up in help messages, but can only be set by env-var, not on the CLI.
// This is useful for things like passwords, which should not be on the CLI
// because it can be seen in `ps`.
type envFlag struct {
name string
typ string
help string
}

var allEnvFlags = []envFlag{}

// registerEnvFlag is internal. Use functions like envFlagString to actually
// create envFlags.
func registerEnvFlag(name, typ, help string) {
for _, ef := range allEnvFlags {
if ef.name == name {
fmt.Fprintf(os.Stderr, "FATAL: duplicate env var declared: %q\n", name)
os.Exit(1)
}
}
allEnvFlags = append(allEnvFlags, envFlag{name, typ, help})
}

// printEnvFlags prints "usage" for all registered envFlags.
func printEnvFlags(out io.Writer) {
width := 0
for _, ef := range allEnvFlags {
if n := len(ef.name); n > width {
width = n
}
}
slices.SortFunc(allEnvFlags, func(l, r envFlag) int { return cmp.Compare(l.name, r.name) })

for _, ef := range allEnvFlags {
fmt.Fprintf(out, "% *s %s %*s%s\n", width+2, ef.name, ef.typ, max(8, 32-width), "", ef.help)
}
}
Loading
Loading