root.go 2.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. // Package cmd holds the cobra command tree.
  2. package cmd
  3. import (
  4. "fmt"
  5. "os"
  6. "github.com/spf13/cobra"
  7. "schlundtech-dns/internal/api"
  8. )
  9. // global flags shared by every command.
  10. var (
  11. flagOutput string
  12. flagEndpoint string
  13. flagDryRun bool
  14. )
  15. var rootCmd = &cobra.Command{
  16. Use: "schlundtech-dns",
  17. Short: "Manage Schlundtech DNS zones and records",
  18. Long: `schlundtech-dns talks to the Schlundtech DNS gateway and lets you list your
  19. zones, read their records and change them.
  20. Credentials are read from a .env file in the working directory, falling back to
  21. the environment. Real environment variables win over .env, so a one-off
  22. override needs no edit:
  23. SCHLUNDTECH_CONTEXT=1 ./schlundtech-dns zones
  24. Copy .env.example to .env and fill it in:
  25. SCHLUNDTECH_USER the gateway user
  26. SCHLUNDTECH_PASSWORD the gateway password
  27. SCHLUNDTECH_CONTEXT the project the records belong to (Schlundtech uses 10)
  28. SCHLUNDTECH_TOKEN six-digit 2FA code, if two-factor authentication is on
  29. SCHLUNDTECH_ENDPOINT override the gateway URL, e.g. the demo system
  30. Keep .env out of git and readable only by you: chmod 600 .env
  31. DNS changes are public and propagate within seconds, so every command that
  32. writes has a --dry-run flag. Use it first.`,
  33. Example: ` cp .env.example .env && chmod 600 .env
  34. ./schlundtech-dns zones
  35. ./schlundtech-dns records list example.com
  36. ./schlundtech-dns records set example.com --name @ --type TXT --value 'v=spf1 -all' --dry-run`,
  37. SilenceUsage: true,
  38. SilenceErrors: true,
  39. }
  40. func init() {
  41. rootCmd.PersistentFlags().StringVar(&flagOutput, "output", "table", "output format: table or json")
  42. rootCmd.PersistentFlags().StringVar(&flagEndpoint, "endpoint", "", "override the gateway URL (default "+api.DefaultEndpoint+")")
  43. rootCmd.PersistentFlags().BoolVar(&flagDryRun, "dry-run", false, "show what would change without sending it")
  44. }
  45. // Execute runs the command tree. Errors go to stderr so that stdout stays
  46. // pipeable.
  47. func Execute() {
  48. if err := rootCmd.Execute(); err != nil {
  49. fmt.Fprintln(os.Stderr, "error:", err)
  50. os.Exit(1)
  51. }
  52. }
  53. // client builds an API client from the environment. Commands that do not talk
  54. // to the gateway never call it, so --help works without credentials.
  55. func client() (*api.Client, error) {
  56. creds, warning, err := loadCredentials()
  57. if warning != "" {
  58. // A warning, not an error: the credentials are still usable.
  59. fmt.Fprintln(os.Stderr, "warning:", warning)
  60. }
  61. if err != nil {
  62. return nil, err
  63. }
  64. endpoint := flagEndpoint
  65. if endpoint == "" {
  66. endpoint = creds.Endpoint
  67. }
  68. opts := []api.Option{}
  69. if endpoint != "" {
  70. opts = append(opts, api.WithEndpoint(endpoint))
  71. }
  72. return api.New(creds, opts...), nil
  73. }