-
Notifications
You must be signed in to change notification settings - Fork 37
/
terraformer.go
152 lines (124 loc) · 5.03 KB
/
terraformer.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
// SPDX-FileCopyrightText: 2020 SAP SE or an SAP affiliate company and Gardener contributors
//
// SPDX-License-Identifier: Apache-2.0
package app
import (
"errors"
"flag"
"fmt"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
runtimelog "sigs.k8s.io/controller-runtime/pkg/log"
logzap "sigs.k8s.io/controller-runtime/pkg/log/zap"
terraformercmd "github.com/gardener/terraformer/pkg/cmd"
"github.com/gardener/terraformer/pkg/terraformer"
versionpkg "github.com/gardener/terraformer/pkg/version"
)
// NewTerraformerCommand creates a new terraformer cobra command
func NewTerraformerCommand() *cobra.Command {
var (
version = versionpkg.Get()
zapOpts = &logzap.Options{}
tfOpts = terraformercmd.NewOptions()
)
// root command
cmd := &cobra.Command{
Use: "terraformer",
Short: "terraformer executes terraform commands inside a Kubernetes cluster",
Long: `terraformer executes terraform commands inside a Kubernetes cluster and handles Pod lifecycle event (e.g. shutdown signals).
It reads and stores terraform config and state from/to Kubernetes resources (ConfigMaps and Secrets).
Also, it continuously updates the state ConfigMap by running a file watcher and updating the ConfigMap as soon as the state file changes.`,
Example: exampleForCommand(""),
Version: fmt.Sprintf("%#v", version),
Hidden: true,
PersistentPreRun: func(cmd *cobra.Command, args []string) {
runtimelog.SetLogger(logzap.New(
// use configuration passed via flags
logzap.UseFlagOptions(zapOpts),
// and overwrite some stuff
func(o *logzap.Options) {
if !o.Development {
encCfg := zap.NewProductionEncoderConfig()
// overwrite time encoding to human readable format for production logs
encCfg.EncodeTime = zapcore.ISO8601TimeEncoder
o.Encoder = zapcore.NewJSONEncoder(encCfg)
}
// don't print stacktrace for warning level logs
o.StacktraceLevel = zapcore.ErrorLevel
},
))
runtimelog.Log.Info("Starting terraformer...", "version", version.GitVersion, "provider", version.Provider)
cmd.Flags().VisitAll(func(flag *pflag.Flag) {
runtimelog.Log.Info(fmt.Sprintf("FLAG: --%s=%s", flag.Name, flag.Value))
})
},
RunE: func(cmd *cobra.Command, args []string) error {
// root command called without any terraform command is invalid and should exit with error code
return errors.New("no command was specified")
},
}
cmd.SetUsageTemplate(cosmeticUsageTemplate)
// setup a subcommand for every supported terraform command
for command := range terraformer.SupportedCommands {
addSubcommand(cmd, command, tfOpts)
}
// setup flags
tfOpts.AddFlags(cmd.PersistentFlags())
zapOpts.BindFlags(flag.CommandLine)
cmd.PersistentFlags().AddGoFlagSet(flag.CommandLine)
return cmd
}
func addSubcommand(cmd *cobra.Command, command terraformer.Command, opts *terraformercmd.Options) {
cmd.AddCommand(&cobra.Command{
Use: string(command),
Short: fmt.Sprintf("execute `terraform %s`", command),
Long: fmt.Sprintf("terraformer %s executes the terraform %s command with the given configuration", command, command),
Args: cobra.NoArgs,
Example: exampleForCommand(string(command)),
RunE: func(cmd *cobra.Command, args []string) error {
if err := opts.Complete(); err != nil {
return err
}
// don't output usage on further errors raised during terraform execution
cmd.SilenceUsage = true
// further errors will be logged properly, don't duplicate
cmd.SilenceErrors = true
tf, err := terraformer.NewDefaultTerraformer(opts.Completed())
if err != nil {
return err
}
return tf.Run(command)
},
})
}
// custom usage template:
// - exclude `terraformer [flags]` if command is hidden (e.g. root command)
// - indicate [flags] after `terraformer [command]` (for subcommands)
const cosmeticUsageTemplate = `Usage:{{if and .Runnable (not .Hidden)}}
{{.UseLine}}{{end}}{{if .HasAvailableSubCommands}}
{{.CommandPath}} [command] [flags]{{end}}{{if gt (len .Aliases) 0}}
Aliases:
{{.NameAndAliases}}{{end}}{{if .HasExample}}
Examples:
{{.Example}}{{end}}{{if .HasAvailableSubCommands}}
Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}}
{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}
Flags:
{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}
Global Flags:
{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}}
Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}}
{{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}}
Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}}
`
func exampleForCommand(command string) string {
if command == "" {
command = "apply"
}
return "terraformer " + command + ` \
--configuration-configmap-name=example.infra.tf-config \
--state-configmap-name=example.infra.tf-state \
--variables-secret-name=example.infra.tf-vars`
}