cmd/age: revamp CLI with -r/-i, long options, and input arg

This commit is contained in:
Filippo Valsorda 2019-11-27 19:40:41 -04:00
parent 779b033c8b
commit 5ea3ee2f63

View file

@ -8,6 +8,7 @@ package main
import ( import (
"flag" "flag"
"fmt"
"io" "io"
"log" "log"
"os" "os"
@ -15,61 +16,95 @@ import (
"github.com/FiloSottile/age/internal/age" "github.com/FiloSottile/age/internal/age"
) )
type multiFlag []string
func (f *multiFlag) String() string { return fmt.Sprint(*f) }
func (f *multiFlag) Set(value string) error {
*f = append(*f, value)
return nil
}
func main() { func main() {
log.SetFlags(0) log.SetFlags(0)
decryptFlag := flag.Bool("d", false, "decrypt the input") var (
outFlag := flag.String("o", "", "output to `FILE` (default stdout)") outFlag string
inFlag := flag.String("i", "", "read from `FILE` (default stdin)") decryptFlag, armorFlag bool
armorFlag := flag.Bool("a", false, "generate an armored file") recipientFlags, identityFlags multiFlag
)
flag.BoolVar(&decryptFlag, "d", false, "decrypt the input")
flag.BoolVar(&decryptFlag, "decrypt", false, "decrypt the input")
flag.StringVar(&outFlag, "o", "", "output to `FILE` (default stdout)")
flag.BoolVar(&armorFlag, "a", false, "generate an armored file")
flag.BoolVar(&armorFlag, "armor", false, "generate an armored file")
flag.Var(&recipientFlags, "r", "recipient (can be repeated)")
flag.Var(&recipientFlags, "recipient", "recipient (can be repeated)")
flag.Var(&identityFlags, "i", "identity (can be repeated)")
flag.Var(&identityFlags, "identity", "identity (can be repeated)")
flag.Parse() flag.Parse()
if flag.NArg() > 1 {
log.Printf("Error: too many arguments.")
log.Fatalf("age accepts a single optional argument for the input file.")
}
switch { switch {
case *decryptFlag: case decryptFlag:
if *armorFlag { if armorFlag {
log.Fatalf("Invalid flag combination") log.Printf("Error: -a/--armor can't be used with -d/--decrypt.")
log.Fatalf("Note that armored files are detected automatically.")
}
if len(recipientFlags) > 0 {
log.Printf("Error: -r/--recipient can't be used with -d/--decrypt.")
log.Fatalf("Did you mean to use -i/--identity to specify a private key?")
} }
default: // encrypt default: // encrypt
if len(identityFlags) > 0 {
log.Printf("Error: -i/--identity can't be used in encryption mode.")
log.Fatalf("Did you forget to specify -d/--decrypt?")
}
if len(recipientFlags) == 0 {
log.Printf("Error: missing recipients.")
log.Fatalf("Did you forget to specify -r/--recipient?")
}
} }
in, out := os.Stdin, os.Stdout in, out := os.Stdin, os.Stdout
if name := *inFlag; name != "" { if name := flag.Arg(0); name != "" && name != "-" {
f, err := os.Open(name) f, err := os.Open(name)
if err != nil { if err != nil {
log.Fatalf("Failed to open input file %q: %v", name, err) log.Fatalf("Error: failed to open input file %q: %v", name, err)
} }
defer f.Close() defer f.Close()
in = f in = f
} }
if name := *outFlag; name != "" { if name := outFlag; name != "" && name != "-" {
f, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0666) f, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0666)
if err != nil { if err != nil {
log.Fatalf("Failed to open output file %q: %v", name, err) log.Fatalf("Error: failed to open output file %q: %v", name, err)
} }
defer f.Close() defer f.Close()
out = f out = f
} }
switch { switch {
case *decryptFlag: case decryptFlag:
decrypt(in, out) decrypt(identityFlags, in, out)
default: default:
encrypt(in, out, *armorFlag) encrypt(recipientFlags, in, out, armorFlag)
} }
} }
func encrypt(in io.Reader, out io.Writer, armor bool) { func encrypt(args []string, in io.Reader, out io.Writer, armor bool) {
var recipients []age.Recipient var recipients []age.Recipient
for _, arg := range flag.Args() { for _, arg := range args {
r, err := parseRecipient(arg) r, err := parseRecipient(arg)
if err != nil { if err != nil {
log.Fatalf("Error: %v", err) log.Fatalf("Error: %v", err)
} }
recipients = append(recipients, r) recipients = append(recipients, r)
} }
if len(recipients) == 0 {
log.Fatalf("Missing recipients!")
}
ageEncrypt := age.Encrypt ageEncrypt := age.Encrypt
if armor { if armor {
@ -77,21 +112,21 @@ func encrypt(in io.Reader, out io.Writer, armor bool) {
} }
w, err := ageEncrypt(out, recipients...) w, err := ageEncrypt(out, recipients...)
if err != nil { if err != nil {
log.Fatalf("Error initializing encryption: %v", err) log.Fatalf("Error: %v", err)
} }
if _, err := io.Copy(w, in); err != nil { if _, err := io.Copy(w, in); err != nil {
log.Fatalf("Error encrypting the input: %v", err) log.Fatalf("Error: %v", err)
} }
if err := w.Close(); err != nil { if err := w.Close(); err != nil {
log.Fatalf("Error finalizing encryption: %v", err) log.Fatalf("Error: %v", err)
} }
} }
func decrypt(in io.Reader, out io.Writer) { func decrypt(args []string, in io.Reader, out io.Writer) {
var identities []age.Identity var identities []age.Identity
// TODO: use the default location if no arguments are provided: // TODO: use the default location if no arguments are provided:
// os.UserConfigDir()/age/keys.txt, ~/.ssh/id_rsa, ~/.ssh/id_ed25519 // os.UserConfigDir()/age/keys.txt, ~/.ssh/id_rsa, ~/.ssh/id_ed25519
for _, name := range flag.Args() { for _, name := range args {
ids, err := parseIdentitiesFile(name) ids, err := parseIdentitiesFile(name)
if err != nil { if err != nil {
log.Fatalf("Error: %v", err) log.Fatalf("Error: %v", err)
@ -101,9 +136,9 @@ func decrypt(in io.Reader, out io.Writer) {
r, err := age.Decrypt(in, identities...) r, err := age.Decrypt(in, identities...)
if err != nil { if err != nil {
log.Fatalf("Error initializing decryption: %v", err) log.Fatalf("Error: %v", err)
} }
if _, err := io.Copy(out, r); err != nil { if _, err := io.Copy(out, r); err != nil {
log.Fatalf("Error decrypting the input: %v", err) log.Fatalf("Error: %v", err)
} }
} }