diff --git a/cmd/root.go b/cmd/root.go index 1ad342d19..c3f2e6e4e 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,7 +1,6 @@ package cmd import ( - "fmt" "io" "github.com/spf13/cobra" @@ -9,6 +8,7 @@ import ( "github.com/ian-howell/airshipctl/cmd/bootstrap" "github.com/ian-howell/airshipctl/cmd/workflow" "github.com/ian-howell/airshipctl/pkg/environment" + "github.com/ian-howell/airshipctl/pkg/log" ) // NewRootCmd creates the root `airshipctl` command. All other commands are @@ -20,11 +20,8 @@ func NewRootCmd(out io.Writer) (*cobra.Command, *environment.AirshipCTLSettings, Short: "airshipctl is a unified entrypoint to various airship components", SilenceErrors: true, SilenceUsage: true, - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - if err := settings.Init(); err != nil { - return fmt.Errorf("error while initializing settings: %s", err) - } - return nil + PersistentPreRun: func(cmd *cobra.Command, args []string) { + log.Init(settings.Debug, cmd.OutOrStderr()) }, } rootCmd.SetOutput(out) diff --git a/cmd/workflow/workflow.go b/cmd/workflow/workflow.go index fd2079836..c3b751549 100644 --- a/cmd/workflow/workflow.go +++ b/cmd/workflow/workflow.go @@ -18,12 +18,13 @@ func NewWorkflowCommand(rootSettings *environment.AirshipCTLSettings) *cobra.Com Aliases: []string{"workflows", "wf"}, } - wfSettings := &wfenv.Settings{} + wfSettings := &wfenv.Settings{ + AirshipCTLSettings: rootSettings, + } wfSettings.InitFlags(workflowRootCmd) - rootSettings.Register(PluginSettingsID, wfSettings) - workflowRootCmd.AddCommand(NewWorkflowInitCommand(rootSettings)) - workflowRootCmd.AddCommand(NewWorkflowListCommand(rootSettings)) + workflowRootCmd.AddCommand(NewWorkflowInitCommand(wfSettings)) + workflowRootCmd.AddCommand(NewWorkflowListCommand(wfSettings)) return workflowRootCmd } diff --git a/cmd/workflow/workflow_init.go b/cmd/workflow/workflow_init.go index a3d989089..1af10b47c 100644 --- a/cmd/workflow/workflow_init.go +++ b/cmd/workflow/workflow_init.go @@ -5,9 +5,8 @@ import ( "github.com/spf13/cobra" - "github.com/ian-howell/airshipctl/pkg/environment" - "github.com/ian-howell/airshipctl/pkg/workflow" - wfenv "github.com/ian-howell/airshipctl/pkg/workflow/environment" + wf "github.com/ian-howell/airshipctl/pkg/workflow" + "github.com/ian-howell/airshipctl/pkg/workflow/environment" ) var ( @@ -15,20 +14,21 @@ var ( ) // NewWorkflowInitCommand is a command for bootstrapping a kubernetes cluster with the necessary components for Argo workflows -func NewWorkflowInitCommand(rootSettings *environment.AirshipCTLSettings) *cobra.Command { +func NewWorkflowInitCommand(settings *environment.Settings) *cobra.Command { workflowInitCommand := &cobra.Command{ Use: "init [flags]", Short: "bootstraps the kubernetes cluster with the Workflow CRDs and controller", Run: func(cmd *cobra.Command, args []string) { out := cmd.OutOrStdout() - wfSettings, ok := rootSettings.PluginSettings[PluginSettingsID].(*wfenv.Settings) - if !ok { - fmt.Fprintf(out, "settings for %s were not registered\n", PluginSettingsID) + + clientset, err := wf.GetClientset(settings) + if err != nil { + fmt.Fprintf(out, "Could not get Workflow Clientset: %s\n", err.Error()) return } - if err := workflow.Initialize(out, wfSettings, manifestPath); err != nil { + if err := wf.Initialize(clientset, settings, manifestPath); err != nil { fmt.Fprintf(out, "error while initializing argo: %s\n", err.Error()) return } diff --git a/cmd/workflow/workflow_list.go b/cmd/workflow/workflow_list.go index eaa60cddc..9752a81f9 100644 --- a/cmd/workflow/workflow_list.go +++ b/cmd/workflow/workflow_list.go @@ -8,32 +8,32 @@ import ( "github.com/spf13/cobra" "github.com/ian-howell/airshipctl/pkg/apis/workflow/v1alpha1" - "github.com/ian-howell/airshipctl/pkg/environment" "github.com/ian-howell/airshipctl/pkg/util" - "github.com/ian-howell/airshipctl/pkg/workflow" - wfenv "github.com/ian-howell/airshipctl/pkg/workflow/environment" + wf "github.com/ian-howell/airshipctl/pkg/workflow" + "github.com/ian-howell/airshipctl/pkg/workflow/environment" wfutil "github.com/ian-howell/airshipctl/pkg/workflow/util" ) // NewWorkflowListCommand is a command for listing argo workflows -func NewWorkflowListCommand(rootSettings *environment.AirshipCTLSettings) *cobra.Command { +func NewWorkflowListCommand(settings *environment.Settings) *cobra.Command { workflowListCmd := &cobra.Command{ Use: "list", Short: "list workflows", Aliases: []string{"ls"}, Run: func(cmd *cobra.Command, args []string) { out := cmd.OutOrStdout() - wfSettings, ok := rootSettings.PluginSettings[PluginSettingsID].(*wfenv.Settings) - if !ok { - fmt.Fprintf(out, "settings for %s were not registered\n", PluginSettingsID) + fmt.Fprintf(out, "debug state: %v\n", settings.Debug) + clientset, err := wf.GetClientset(settings) + if err != nil { + fmt.Fprintf(out, "Could not get Workflow Clientset: %s\n", err.Error()) return } - wflist, err := workflow.ListWorkflows(wfSettings) + wflist, err := wf.ListWorkflows(clientset, settings) if err != nil { fmt.Fprintf(out, "Could not list workflows: %s\n", err.Error()) return } - printTable(out, wflist, wfSettings) + printTable(out, wflist, settings) }, } @@ -41,10 +41,10 @@ func NewWorkflowListCommand(rootSettings *environment.AirshipCTLSettings) *cobra } // printTable pretty prints the list of workflows to out -func printTable(out io.Writer, wfList []v1alpha1.Workflow, wfSettings *wfenv.Settings) { +func printTable(out io.Writer, wfList []v1alpha1.Workflow, settings *environment.Settings) { w := util.NewTabWriter(out) defer w.Flush() - if wfSettings.AllNamespaces { + if settings.AllNamespaces { fmt.Fprint(w, "NAMESPACE\t") } fmt.Fprint(w, "NAME\tSTATUS\tAGE\tDURATION\tPRIORITY") @@ -52,7 +52,7 @@ func printTable(out io.Writer, wfList []v1alpha1.Workflow, wfSettings *wfenv.Set for _, wf := range wfList { ageStr := humanize.RelativeDurationShort(wf.ObjectMeta.CreationTimestamp.Time, util.Now()) durationStr := humanize.RelativeDurationShort(wf.Status.StartedAt.Time, wf.Status.FinishedAt.Time) - if wfSettings.AllNamespaces { + if settings.AllNamespaces { fmt.Fprintf(w, "%s\t", wf.ObjectMeta.Namespace) } var priority int diff --git a/main.go b/main.go index e4b426ce9..1fd987646 100644 --- a/main.go +++ b/main.go @@ -5,7 +5,6 @@ import ( "os" "github.com/ian-howell/airshipctl/cmd" - "github.com/ian-howell/airshipctl/pkg/log" ) func main() { @@ -17,8 +16,6 @@ func main() { cmd.AddDefaultAirshipCTLCommands(rootCmd, settings) - log.Init(settings, os.Stdout) - if err := rootCmd.Execute(); err != nil { fmt.Fprintln(os.Stdout, err) os.Exit(1) diff --git a/pkg/environment/settings.go b/pkg/environment/settings.go index 66d18d6ef..97cee9456 100644 --- a/pkg/environment/settings.go +++ b/pkg/environment/settings.go @@ -4,16 +4,10 @@ import ( "github.com/spf13/cobra" ) -type PluginSettings interface { - Init() error -} - // AirshipCTLSettings is a container for all of the settings needed by airshipctl type AirshipCTLSettings struct { // Debug is used for verbose output Debug bool - - PluginSettings map[string]PluginSettings } // InitFlags adds the default settings flags to cmd @@ -21,19 +15,3 @@ func (a *AirshipCTLSettings) InitFlags(cmd *cobra.Command) { flags := cmd.PersistentFlags() flags.BoolVar(&a.Debug, "debug", false, "enable verbose output") } - -func (a *AirshipCTLSettings) Register(pluginName string, pluginSettings PluginSettings) { - if a.PluginSettings == nil { - a.PluginSettings = map[string]PluginSettings{} - } - a.PluginSettings[pluginName] = pluginSettings -} - -func (a *AirshipCTLSettings) Init() error { - for _, pluginSettings := range a.PluginSettings { - if err := pluginSettings.Init(); err != nil { - return err - } - } - return nil -} diff --git a/pkg/log/log.go b/pkg/log/log.go index 889fffbf6..4e4b46ac6 100644 --- a/pkg/log/log.go +++ b/pkg/log/log.go @@ -3,15 +3,13 @@ package log import ( "io" "log" - - "github.com/ian-howell/airshipctl/pkg/environment" ) var debug = false // Init initializes settings related to logging -func Init(settings *environment.AirshipCTLSettings, out io.Writer) { - debug = settings.Debug +func Init(debugFlag bool, out io.Writer) { + debug = debugFlag log.SetOutput(out) } diff --git a/pkg/workflow/clientset.go b/pkg/workflow/clientset.go new file mode 100644 index 000000000..52cc1c9b8 --- /dev/null +++ b/pkg/workflow/clientset.go @@ -0,0 +1,60 @@ +package workflow + +import ( + apixv1beta1 "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" + + argo "github.com/ian-howell/airshipctl/pkg/client/clientset/versioned" + "github.com/ian-howell/airshipctl/pkg/workflow/environment" +) + +// Clientset is a container for the various clients that are useful to the workflow command +type Clientset struct { + // Kube is an instrument for interacting with a kubernetes cluster + Kube kubernetes.Interface + + // Argo is an instrument for interacting with Argo workflows + Argo argo.Interface + + // CRD is an instrument for interacting with CRDs + CRD apixv1beta1.Interface +} + +var ( + clientset *Clientset +) + +// GetClientset provides access to the clientset singleton +func GetClientset(settings *environment.Settings) (*Clientset, error) { + if clientset != nil { + return clientset, nil + } + + if settings.KubeConfigFilePath == "" { + settings.KubeConfigFilePath = clientcmd.RecommendedHomeFile + } + + kubeConfig, err := clientcmd.BuildConfigFromFlags("", settings.KubeConfigFilePath) + if err != nil { + return nil, err + } + + clientset.Kube, err = kubernetes.NewForConfig(kubeConfig) + if err != nil { + return nil, err + } + + clientset.Argo, err = argo.NewForConfig(kubeConfig) + if err != nil { + return nil, err + } + + clientset.CRD, err = apixv1beta1.NewForConfig(kubeConfig) + if err != nil { + return nil, err + } + + clientset = &Clientset{} + return clientset, nil +} diff --git a/pkg/workflow/environment/settings.go b/pkg/workflow/environment/settings.go index 93f69beeb..6c6f84814 100644 --- a/pkg/workflow/environment/settings.go +++ b/pkg/workflow/environment/settings.go @@ -2,15 +2,14 @@ package environment import ( "github.com/spf13/cobra" - apixv1beta1 "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/tools/clientcmd" - argo "github.com/ian-howell/airshipctl/pkg/client/clientset/versioned" + "github.com/ian-howell/airshipctl/pkg/environment" ) // Settings is a container for all of the settings needed by workflows type Settings struct { + *environment.AirshipCTLSettings + // Namespace is the kubernetes namespace to be used during the context of this action Namespace string @@ -21,18 +20,6 @@ type Settings struct { // This flag is only needed when airshipctl is being used // out-of-cluster KubeConfigFilePath string - - // KubeClient is an instrument for interacting with a kubernetes cluster - KubeClient kubernetes.Interface - - // ArgoClient is an instrument for interacting with Argo workflows - ArgoClient argo.Interface - - // CRDClient is an instrument for interacting with CRDs - CRDClient apixv1beta1.Interface - - // Initialized denotes whether the settings have been initialized or not. It is useful for unit-testing - Initialized bool } // InitFlags adds the default settings flags to cmd @@ -42,38 +29,3 @@ func (s *Settings) InitFlags(cmd *cobra.Command) { flags.StringVar(&s.Namespace, "namespace", "default", "kubernetes namespace to use for the context of this command") flags.BoolVar(&s.AllNamespaces, "all-namespaces", false, "use all kubernetes namespaces for the context of this command") } - -// Init assigns default values -func (s *Settings) Init() error { - if s.Initialized { - return nil - } - - if s.KubeConfigFilePath == "" { - s.KubeConfigFilePath = clientcmd.RecommendedHomeFile - } - - var err error - kubeConfig, err := clientcmd.BuildConfigFromFlags("", s.KubeConfigFilePath) - if err != nil { - return err - } - - s.KubeClient, err = kubernetes.NewForConfig(kubeConfig) - if err != nil { - return err - } - - s.ArgoClient, err = argo.NewForConfig(kubeConfig) - if err != nil { - return err - } - - s.CRDClient, err = apixv1beta1.NewForConfig(kubeConfig) - if err != nil { - return err - } - - s.Initialized = true - return nil -} diff --git a/pkg/workflow/initialize.go b/pkg/workflow/initialize.go index fdc7207e1..665af4c56 100644 --- a/pkg/workflow/initialize.go +++ b/pkg/workflow/initialize.go @@ -2,7 +2,6 @@ package workflow import ( "fmt" - "io" v1beta2 "k8s.io/api/apps/v1beta2" "k8s.io/api/core/v1" @@ -13,6 +12,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" + "github.com/ian-howell/airshipctl/pkg/log" "github.com/ian-howell/airshipctl/pkg/workflow/environment" ) @@ -22,16 +22,13 @@ const ( // Initialize creates the argo namespace and all of the required kubernetes // objects for argo to function -func Initialize(out io.Writer, settings *environment.Settings, manifestPath string) error { - kubeClient := settings.KubeClient - crdClient := settings.CRDClient - - if err := createNamespace(out, kubeClient); err != nil { +func Initialize(clientset *Clientset, settings *environment.Settings, manifestPath string) error { + if err := createNamespace(clientset.Kube); err != nil { return err } if manifestPath == "" { - if err := createDefaultObjects(out, kubeClient, crdClient); err != nil { + if err := createDefaultObjects(clientset.Kube, clientset.CRD); err != nil { return fmt.Errorf("could not create default objects: %s", err.Error()) } } else { @@ -43,55 +40,55 @@ func Initialize(out io.Writer, settings *environment.Settings, manifestPath stri return nil } -func createNamespace(out io.Writer, kubeClient kubernetes.Interface) error { - fmt.Fprintf(out, "Creating namespace %s\n", argoNamespace) +func createNamespace(kubeClient kubernetes.Interface) error { + log.Debugf("Creating namespace %s", argoNamespace) _, err := kubeClient.CoreV1().Namespaces().Create(&v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "argo"}}) - return handleCreateError(out, fmt.Sprintf("namespace %s", argoNamespace), err) + return handleCreateError(fmt.Sprintf("namespace %s", argoNamespace), err) } -func createDefaultObjects(out io.Writer, kubeClient kubernetes.Interface, crdClient apixv1beta1client.Interface) error { - fmt.Fprintf(out, "Registering Workflow CRD\n") - if err := handleCreateError(out, "Workflow CRD", registerDefaultWorkflow(crdClient)); err != nil { +func createDefaultObjects(kubeClient kubernetes.Interface, crdClient apixv1beta1client.Interface) error { + log.Debugf("Registering Workflow CRD") + if err := handleCreateError("Workflow CRD", registerDefaultWorkflow(crdClient)); err != nil { return err } - fmt.Fprintf(out, "Creating argo ServiceAccount\n") - if err := handleCreateError(out, "argo ServiceAccount", createArgoServiceAccount(kubeClient)); err != nil { + log.Debugf("Creating argo ServiceAccount") + if err := handleCreateError("argo ServiceAccount", createArgoServiceAccount(kubeClient)); err != nil { return err } - fmt.Fprintf(out, "Creating argo admin ClusterRole\n") - if err := handleCreateError(out, "argo admin ClusterRole", createArgoAdminClusterRole(kubeClient)); err != nil { + log.Debugf("Creating argo admin ClusterRole") + if err := handleCreateError("argo admin ClusterRole", createArgoAdminClusterRole(kubeClient)); err != nil { return err } - fmt.Fprintf(out, "Creating argo edit ClusterRole\n") - if err := handleCreateError(out, "argo edit ClusterRole", createArgoEditClusterRole(kubeClient)); err != nil { + log.Debugf("Creating argo edit ClusterRole") + if err := handleCreateError("argo edit ClusterRole", createArgoEditClusterRole(kubeClient)); err != nil { return err } - fmt.Fprintf(out, "Creating argo view ClusterRole\n") - if err := handleCreateError(out, "argo view ClusterRole", createArgoViewClusterRole(kubeClient)); err != nil { + log.Debugf("Creating argo view ClusterRole") + if err := handleCreateError("argo view ClusterRole", createArgoViewClusterRole(kubeClient)); err != nil { return err } - fmt.Fprintf(out, "Creating argo ClusterRole\n") - if err := handleCreateError(out, "argo ClusterRole", createArgoClusterRole(kubeClient)); err != nil { + log.Debugf("Creating argo ClusterRole") + if err := handleCreateError("argo ClusterRole", createArgoClusterRole(kubeClient)); err != nil { return err } - fmt.Fprintf(out, "Creating argo ClusterRoleBinding\n") - if err := handleCreateError(out, "argo ClusterRoleBinding", createArgoClusterRoleBinding(kubeClient)); err != nil { + log.Debugf("Creating argo ClusterRoleBinding") + if err := handleCreateError("argo ClusterRoleBinding", createArgoClusterRoleBinding(kubeClient)); err != nil { return err } - fmt.Fprintf(out, "Creating argo ConfigMap\n") - if err := handleCreateError(out, "argo ConfigMap", createArgoConfigMap(kubeClient)); err != nil { + log.Debugf("Creating argo ConfigMap") + if err := handleCreateError("argo ConfigMap", createArgoConfigMap(kubeClient)); err != nil { return err } - fmt.Fprintf(out, "Creating argo Deployment\n") - if err := handleCreateError(out, "argo Deployment", createArgoDeployment(kubeClient)); err != nil { + log.Debugf("Creating argo Deployment") + if err := handleCreateError("argo Deployment", createArgoDeployment(kubeClient)); err != nil { return err } @@ -380,12 +377,12 @@ func createArgoDeployment(kubeClient kubernetes.Interface) error { return err } -func handleCreateError(out io.Writer, resourceName string, err error) error { +func handleCreateError(resourceName string, err error) error { if err == nil { return nil } if errors.IsAlreadyExists(err) { - fmt.Fprintf(out, "%s already exists\n", resourceName) + log.Debugf("%s already exists", resourceName) return nil } return fmt.Errorf("Could not create %s: %s", resourceName, err.Error()) diff --git a/pkg/workflow/list.go b/pkg/workflow/list.go index 38af8d1fb..f0893b04c 100644 --- a/pkg/workflow/list.go +++ b/pkg/workflow/list.go @@ -12,14 +12,14 @@ import ( ) // ListWorkflows returns a list of Workflows -func ListWorkflows(settings *environment.Settings) ([]v1alpha1.Workflow, error) { - var clientSet v1alpha1client.WorkflowInterface +func ListWorkflows(clientset *Clientset, settings *environment.Settings) ([]v1alpha1.Workflow, error) { + var wfClient v1alpha1client.WorkflowInterface if settings.AllNamespaces { - clientSet = settings.ArgoClient.ArgoprojV1alpha1().Workflows(apiv1.NamespaceAll) + wfClient = clientset.Argo.ArgoprojV1alpha1().Workflows(apiv1.NamespaceAll) } else { - clientSet = settings.ArgoClient.ArgoprojV1alpha1().Workflows(settings.Namespace) + wfClient = clientset.Argo.ArgoprojV1alpha1().Workflows(settings.Namespace) } - wflist, err := clientSet.List(v1.ListOptions{}) + wflist, err := wfClient.List(v1.ListOptions{}) if err != nil { return []v1alpha1.Workflow{}, err }