/*
Copyright 2020 The Flux authors
Licensed under the Apache License, Version 2.0 (the “License”);
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an “AS IS” BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
“bufio”
“fmt”
“log”
“os”
“strings”
“time”
"github.com/go-logr/logr"
"github.com/spf13/cobra"
"golang.org/x/term"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/validation"
"k8s.io/cli-runtime/pkg/genericclioptions"
_ "k8s.io/client-go/plugin/pkg/client/auth"
ctrllog "sigs.k8s.io/controller-runtime/pkg/log"
runclient "github.com/fluxcd/pkg/runtime/client"
"github.com/fluxcd/flux2/v2/pkg/manifestgen/install"
)
var VERSION = “0.0.0-dev.0”
var rootCmd = &cobra.Command{
Use: “flux”,
Version: VERSION,
SilenceUsage: true,
SilenceErrors: true,
Short: “Command line utility for assembling Kubernetes CD pipelines”,
Long: Command line utility for assembling Kubernetes CD pipelines the GitOps way.
,
Example: ` # Check prerequisites
flux check –pre
Install the latest version of Flux
flux install
Create a source for a public Git repository
flux create source git webapp-latest
–url=https://github.com/stefanprodan/podinfo
–branch=master
–interval=3m
List GitRepository sources and their status
flux get sources git
Trigger a GitRepository source reconciliation
flux reconcile source git flux-system
Export GitRepository sources in YAML format
flux export source git –all > sources.yaml
Create a Kustomization for deploying a series of microservices
flux create kustomization webapp-dev
–source=webapp-latest
–path=”./deploy/webapp/”
–prune=true
–interval=5m
–health-check=”Deployment/backend.webapp”
–health-check=”Deployment/frontend.webapp”
–health-check-timeout=2m
Trigger a git sync of the Kustomization’s source and apply changes
flux reconcile kustomization webapp-dev –with-source
Suspend a Kustomization reconciliation
flux suspend kustomization webapp-dev
Export Kustomizations in YAML format
flux export kustomization –all > kustomizations.yaml
Resume a Kustomization reconciliation
flux resume kustomization webapp-dev
Delete a Kustomization
flux delete kustomization webapp-dev
Delete a GitRepository source
flux delete source git webapp-latest
Uninstall Flux and delete CRDs
flux uninstall`,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
ns, err := cmd.Flags().GetString(“namespace”)
if err != nil {
return fmt.Errorf(“error getting namespace: %w”, err)
}
if e := validation.IsDNS1123Label(ns); len(e) > 0 {
return fmt.Errorf("namespace must be a valid DNS label: %q", ns)
}
return nil
},
}
var logger = stderrLogger{stderr: os.Stderr}
type rootFlags struct {
timeout time.Duration
verbose bool
pollInterval time.Duration
defaults install.Options
}
// RequestError is a custom error type that wraps an error returned by the flux api.
type RequestError struct {
StatusCode int
Err error
}
func (r *RequestError) Error() string {
return r.Err.Error()
}
var rootArgs = NewRootFlags()
var kubeconfigArgs = genericclioptions.NewConfigFlags(false)
var kubeclientOptions = new(runclient.Options)
func init() {
rootCmd.PersistentFlags().DurationVar(&rootArgs.timeout, “timeout”, 5*time.Minute, “timeout for this operation”)
rootCmd.PersistentFlags().BoolVar(&rootArgs.verbose, “verbose”, false, “print generated objects”)
configureDefaultNamespace()
kubeconfigArgs.APIServer = nil // prevent AddFlags from configuring --server flag
kubeconfigArgs.Timeout = nil // prevent AddFlags from configuring --request-timeout flag, we have --timeout instead
kubeconfigArgs.AddFlags(rootCmd.PersistentFlags())
// Since some subcommands use the -s
flag as a short version for --silent
, we manually configure the server flag
// without the -s
short version. While we’re no longer on par with kubectl’s flags, we maintain backwards compatibility
// on the CLI interface.
apiServer := “”
kubeconfigArgs.APIServer = &apiServer
rootCmd.PersistentFlags().StringVar(kubeconfigArgs.APIServer, “server”, *kubeconfigArgs.APIServer, “The address and port of the Kubernetes API server”)
// Update the description for kubeconfig TLS flags so that user’s don’t mistake it for a Flux specific flag
rootCmd.Flag(“insecure-skip-tls-verify”).Usage = “If true, the Kubernetes API server’s certificate will not be checked for validity. This will make your HTTPS connections insecure”
rootCmd.Flag(“client-certificate”).Usage = “Path to a client certificate file for TLS authentication to the Kubernetes API server”
rootCmd.Flag(“certificate-authority”).Usage = “Path to a cert file for the certificate authority to authenticate the Kubernetes API server”
rootCmd.Flag(“client-key”).Usage = “Path to a client key file for TLS authentication to the Kubernetes API server”
kubeclientOptions.BindFlags(rootCmd.PersistentFlags())
rootCmd.RegisterFlagCompletionFunc(“context”, contextsCompletionFunc)
rootCmd.RegisterFlagCompletionFunc(“namespace”, resourceNamesCompletionFunc(corev1.SchemeGroupVersion.WithKind(“Namespace”)))
rootCmd.DisableAutoGenTag = true
rootCmd.SetOut(os.Stdout)
}
func NewRootFlags() rootFlags {
rf := rootFlags{
pollInterval: 2 * time.Second,
defaults: install.MakeDefaultOptions(),
}
rf.defaults.Version = “v” + VERSION
return rf
}
func main() {
log.SetFlags(0)
// This is required because controller-runtime expects its consumers to
// set a logger through log.SetLogger within 30 seconds of the program's
// initalization. If not set, the entire debug stack is printed as an
// error, see: https://github.com/kubernetes-sigs/controller-runtime/blob/ed8be90/pkg/log/log.go#L59
// Since we have our own logging and don't care about controller-runtime's
// logger, we configure it's logger to do nothing.
ctrllog.SetLogger(logr.New(ctrllog.NullLogSink{}))
if err := rootCmd.Execute(); err != nil {
if err, ok := err.(*RequestError); ok {
if err.StatusCode == 1 {
logger.Warningf("%v", err)
} else {
logger.Failuref("%v", err)
}
os.Exit(err.StatusCode)
}
logger.Failuref("%v", err)
os.Exit(1)
}
}
func configureDefaultNamespace() {
*kubeconfigArgs.Namespace = rootArgs.defaults.Namespace
fromEnv := os.Getenv(“FLUX_SYSTEM_NAMESPACE”)
if fromEnv != “” {
// namespace must be a valid DNS label. Assess against validation
// used upstream, and ignore invalid values as environment vars
// may not be actively provided by end-user.
if e := validation.IsDNS1123Label(fromEnv); len(e) > 0 {
logger.Warningf(” ignoring invalid FLUX_SYSTEM_NAMESPACE: %q”, fromEnv)
return
}
kubeconfigArgs.Namespace = &fromEnv
}
}
// readPasswordFromStdin reads a password from stdin and returns the input
// with trailing newline and/or carriage return removed. It also makes sure that terminal
// echoing is turned off if stdin is a terminal.
func readPasswordFromStdin(prompt string) (string, error) {
var out string
var err error
fmt.Fprint(os.Stdout, prompt)
stdinFD := int(os.Stdin.Fd())
if term.IsTerminal(stdinFD) {
var inBytes []byte
inBytes, err = term.ReadPassword(int(os.Stdin.Fd()))
out = string(inBytes)
} else {
out, err = bufio.NewReader(os.Stdin).ReadString(‘\n’)
}
if err != nil {
return “”, fmt.Errorf(“could not read from stdin: %w”, err)
}
fmt.Println()
return strings.TrimRight(out, “\r\n”), nil
}
func withPreviewNote(desc string) string {
previewNote := ⚠️ Please note that this command is in preview and under development. While we try our best to not introduce breaking changes, they may occur when we adapt to new features and/or find better ways to facilitate what it does.
return fmt.Sprintf(“%s\n\n%s”, strings.TrimSpace(desc), previewNote)
}