diff --git a/.gitignore b/.gitignore index 5eb96f987..d4cb796db 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,6 @@ gofabric8 /release *.iml .idea +.settings +.project .DS_Store diff --git a/cmds/clean_clean_jenkins.go b/cmds/clean_clean_jenkins.go new file mode 100644 index 000000000..846e0de2d --- /dev/null +++ b/cmds/clean_clean_jenkins.go @@ -0,0 +1,108 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * 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 cmds + +import ( + "fmt" + + "github.com/fabric8io/gofabric8/client" + "github.com/fabric8io/gofabric8/util" + "github.com/spf13/cobra" + cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" +) + +type cleanUpJenkinsParams struct { + confirm bool +} + +// NewCmdCleanUpJenkins delete files in the tenants content repository +func NewCmdCleanUpJenkins(f *cmdutil.Factory) *cobra.Command { + cmd := &cobra.Command{ + Use: "jenkins", + Short: "Deletes all the jenkins jobs in your tenant Jenkins service", + Long: `Deletes all the jenkins jobs in your tenant Jenkins service`, + Aliases: []string{"content-repository"}, + + Run: func(cmd *cobra.Command, args []string) { + p := cleanUpJenkinsParams{} + if cmd.Flags().Lookup(yesFlag).Value.String() == "true" { + p.confirm = true + } + err := p.cleanUpJenkins(f) + if err != nil { + util.Fatalf("%s", err) + } + return + }, + } + return cmd +} + +func (p *cleanUpJenkinsParams) cleanUpJenkins(f *cmdutil.Factory) error { + c, cfg := client.NewClient(f) + ns, _, _ := f.DefaultNamespace() + oc, _ := client.NewOpenShiftClient(cfg) + initSchema() + + userNS, err := detectCurrentUserNamespace(ns, c, oc) + if err != nil { + return err + } + jenkinsNS := fmt.Sprintf("%s-jenkins", userNS) + + if !p.confirm { + confirm := "" + util.Warn("WARNING this is destructive and will remove ALL of the jenkins jobs\n") + util.Info("for your tenant: ") + util.Successf("%s", userNS) + util.Info(" running in namespace: ") + util.Successf("%s\n", jenkinsNS) + util.Warn("\nContinue [y/N]: ") + fmt.Scanln(&confirm) + if confirm != "y" { + util.Warn("Aborted\n") + return nil + } + } + util.Info("Cleaning jenkins for tenant: ") + util.Successf("%s", userNS) + util.Info(" running in namespace: ") + util.Successf("%s\n", jenkinsNS) + + err = ensureDeploymentOrDCHasReplicas(c, oc, jenkinsNS, "jenkins", 1) + if err != nil { + return err + } + pod, err := waitForReadyPodForDeploymentOrDC(c, oc, jenkinsNS, "jenkins") + if err != nil { + return err + } + util.Infof("Found running jenkins pod %s\n", pod) + + kubeCLI := "kubectl" + err = runCommand(kubeCLI, "exec", "-it", pod, "-n", jenkinsNS, "--", "bash", "-c", "rm -rf /var/lib/jenkins/jobs/*") + if err != nil { + return err + } + err = runCommand(kubeCLI, "delete", "pod", pod, "-n", jenkinsNS) + if err != nil { + return err + } + if err == nil { + util.Info("Completed!\n") + } + return err +} diff --git a/cmds/clean_content_repo.go b/cmds/clean_content_repo.go new file mode 100644 index 000000000..f38b2bef6 --- /dev/null +++ b/cmds/clean_content_repo.go @@ -0,0 +1,108 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * 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 cmds + +import ( + "fmt" + + "github.com/fabric8io/gofabric8/client" + "github.com/fabric8io/gofabric8/util" + "github.com/spf13/cobra" + cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" +) + +type cleanUpContentRepo struct { + confirm bool +} + +// NewCmdCleanUpContentRepository delete files in the tenants content repository +func NewCmdCleanUpContentRepository(f *cmdutil.Factory) *cobra.Command { + cmd := &cobra.Command{ + Use: "content-repo", + Short: "Hard delete all fabric8 apps, environments and configurations", + Long: `Hard delete all fabric8 apps, environments and configurations`, + Aliases: []string{"content-repository"}, + + Run: func(cmd *cobra.Command, args []string) { + p := cleanUpContentRepo{} + if cmd.Flags().Lookup(yesFlag).Value.String() == "true" { + p.confirm = true + } + err := p.cleanContentRepository(f) + if err != nil { + util.Fatalf("%s", err) + } + return + }, + } + return cmd +} + +func (p *cleanUpContentRepo) cleanContentRepository(f *cmdutil.Factory) error { + c, cfg := client.NewClient(f) + ns, _, _ := f.DefaultNamespace() + oc, _ := client.NewOpenShiftClient(cfg) + initSchema() + + userNS, err := detectCurrentUserNamespace(ns, c, oc) + if err != nil { + return err + } + jenkinsNS := fmt.Sprintf("%s-jenkins", userNS) + + if !p.confirm { + confirm := "" + util.Warn("WARNING this is destructive and will remove ALL of the releases in your content-repository\n") + util.Info("for your tenant: ") + util.Successf("%s", userNS) + util.Info(" running in namespace: ") + util.Successf("%s\n", jenkinsNS) + util.Warn("\nContinue [y/N]: ") + fmt.Scanln(&confirm) + if confirm != "y" { + util.Warn("Aborted\n") + return nil + } + } + util.Info("Cleaning content-repository for tenant: ") + util.Successf("%s", userNS) + util.Info(" running in namespace: ") + util.Successf("%s\n", jenkinsNS) + + err = ensureDeploymentOrDCHasReplicas(c, oc, jenkinsNS, "content-repository", 1) + if err != nil { + return err + } + pod, err := waitForReadyPodForDeploymentOrDC(c, oc, jenkinsNS, "content-repository") + if err != nil { + return err + } + util.Infof("Found running content-repository pod %s\n", pod) + + kubeCLI := "kubectl" + err = runCommand(kubeCLI, "exec", "-it", pod, "-n", jenkinsNS, "--", "rm", "-rf", "/var/www/html/content/repositories") + if err != nil { + return err + } + err = runCommand(kubeCLI, "exec", "-it", pod, "-n", jenkinsNS, "--", "du", "-hc", "/var/www/html/content") + if err != nil { + return err + } + if err == nil { + util.Info("Completed!\n") + } + return err +} diff --git a/cmds/cleanup.go b/cmds/cleanup.go index 5d0b610c3..ce0e2beec 100644 --- a/cmds/cleanup.go +++ b/cmds/cleanup.go @@ -99,6 +99,38 @@ func NewCmdCleanUpApp(f *cmdutil.Factory) *cobra.Command { return cmd } +func NewCmdCleanUpTenant(f *cmdutil.Factory) *cobra.Command { + cmd := &cobra.Command{ + Use: "tenant", + Short: "Hard delete your tenant removing all pipelines, apps, jobs and releases", + + Run: func(cmd *cobra.Command, args []string) { + var confirm string + if cmd.Flags().Lookup(yesFlag).Value.String() == "true" { + confirm = "y" + } else { + fmt.Fprintf(os.Stdout, `WARNING this is destructive and will remove all your pipelines, apps, jobs and releases. Continue? [y/N] `) + fmt.Scanln(&confirm) + } + + if confirm == "y" { + util.Info("Removing all tenant pipelines...\n") + err := cleanUpTenant(f) + if err != nil { + util.Fatalf("Failed to remove tenant %v", err) + } + return + } + util.Info("Cancelled") + }, + } + return cmd +} + +func cleanUpTenant(f *cmdutil.Factory) error { + return nil +} + func deleteSystem(f *cmdutil.Factory) error { var oc *oclient.Client diff --git a/cmds/common.go b/cmds/common.go index fdcdc6562..628fcabb3 100644 --- a/cmds/common.go +++ b/cmds/common.go @@ -17,18 +17,21 @@ package cmds import ( "fmt" + "io/ioutil" "os" + "os/exec" "runtime" "strings" + "time" "github.com/daviddengcn/go-colortext" "github.com/fabric8io/gofabric8/util" "github.com/spf13/cobra" - "io/ioutil" - + oclient "github.com/openshift/origin/pkg/client" osapi "github.com/openshift/origin/pkg/project/api" "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" k8api "k8s.io/kubernetes/pkg/api/unversioned" k8client "k8s.io/kubernetes/pkg/client/unversioned" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" @@ -80,13 +83,139 @@ func defaultNamespace(cmd *cobra.Command, f *cmdutil.Factory) (string, error) { return ns, err } -// currentProject ... +// ensureDeploymentOrDCHasReplicas ensures that the given Deployment or DeploymentConfig has at least the right number +// of replicas +func ensureDeploymentOrDCHasReplicas(c *k8client.Client, oc *oclient.Client, ns string, name string, minRelicas int32) error { + typeOfMaster := util.TypeOfMaster(c) + if typeOfMaster == util.OpenShift { + dc, err := oc.DeploymentConfigs(ns).Get(name) + if err == nil && dc != nil { + if dc.Spec.Replicas >= minRelicas { + return nil + } + dc.Spec.Replicas = minRelicas + util.Infof("Scaling DeploymentConfig %s in namespace %s to %d\n", name, ns, minRelicas) + _, err = oc.DeploymentConfigs(ns).Update(dc) + return err + } + } + deployment, err := c.Extensions().Deployments(ns).Get(name) + if err != nil || deployment == nil { + return fmt.Errorf("Could not find a Deployment or DeploymentConfig called %s in namespace %s due to %v", name, ns, err) + } + if deployment.Spec.Replicas >= minRelicas { + return nil + } + deployment.Spec.Replicas = minRelicas + util.Infof("Scaling Deployment %s in namespace %s to %d\n", name, ns, minRelicas) + _, err = c.Extensions().Deployments(ns).Update(deployment) + return err +} + +// waitForReadyPodForDeploymentOrDC waits for a ready pod in a Deployment or DeploymentConfig +// in the given namespace with the given name +func waitForReadyPodForDeploymentOrDC(c *k8client.Client, oc *oclient.Client, ns string, name string) (string, error) { + typeOfMaster := util.TypeOfMaster(c) + if typeOfMaster == util.OpenShift { + dc, err := oc.DeploymentConfigs(ns).Get(name) + if err == nil && dc != nil { + selector := dc.Spec.Selector + if selector == nil { + return "", fmt.Errorf("No selector defined on Deployment %s in namespace %s", name, ns) + } + return waitForReadyPodForSelector(c, oc, ns, selector) + } + } + deployment, err := c.Extensions().Deployments(ns).Get(name) + if err != nil || deployment == nil { + return "", fmt.Errorf("Could not find a Deployment or DeploymentConfig called %s in namespace %s due to %v", name, ns, err) + } + selector := deployment.Spec.Selector + if selector == nil { + return "", fmt.Errorf("No selector defined on Deployment %s in namespace %s", name, ns) + } + labels := selector.MatchLabels + if labels == nil { + return "", fmt.Errorf("No MatchLabels defined on the Selector of Deployment %s in namespace %s", name, ns) + } + return waitForReadyPodForSelector(c, oc, ns, labels) +} + +func waitForReadyPodForSelector(c *k8client.Client, oc *oclient.Client, ns string, labels map[string]string) (string, error) { + selector, err := unversioned.LabelSelectorAsSelector(&unversioned.LabelSelector{MatchLabels: labels}) + if err != nil { + return "", err + } + util.Infof("Waiting for a running pod in namespace %s with labels %v\n", ns, labels) + for { + pods, err := c.Pods(ns).List(api.ListOptions{ + LabelSelector: selector, + }) + if err != nil { + return "", err + } + name := "" + lastTime := time.Time{} + for _, pod := range pods.Items { + phase := pod.Status.Phase + if phase == api.PodRunning { + created := pod.CreationTimestamp + if name == "" || created.After(lastTime) { + lastTime = created.Time + name = pod.Name + } + } + } + if name != "" { + util.Info("Found newest pod: ") + util.Successf("%s\n", name) + return name, nil + } + + // TODO replace with a watch flavour + time.Sleep(time.Second) + } +} + +func detectCurrentUserNamespace(ns string, c *k8client.Client, oc *oclient.Client) (string, error) { + typeOfMaster := util.TypeOfMaster(c) + if typeOfMaster == util.OpenShift { + projects, err := oc.Projects().List(api.ListOptions{}) + if err != nil { + return "", err + } + return detectCurrentUserProject(ns, projects.Items, c), nil + } else { + namespaces, err := c.Namespaces().List(api.ListOptions{}) + if err != nil { + return "", err + } + return detectCurrentUserNamespaceFromNamespaces(ns, namespaces.Items, c), nil + } +} + +// detectCurrentUserProject finds the user namespace name from the given current projects +func detectCurrentUserNamespaceFromNamespaces(current string, items []api.Namespace, c *k8client.Client) (chosenone string) { + names := []string{} + for _, p := range items { + names = append(names, p.Name) + } + return detectCurrentUserNamespaceFromNames(current, names, c) +} + func detectCurrentUserProject(current string, items []osapi.Project, c *k8client.Client) (chosenone string) { + names := []string{} + for _, p := range items { + names = append(names, p.Name) + } + return detectCurrentUserNamespaceFromNames(current, names, c) +} + +func detectCurrentUserNamespaceFromNames(current string, items []string, c *k8client.Client) (chosenone string) { var detected []string var prefixes = []string{"che", "jenkins", "run", "stage"} - for _, p := range items { - name := p.Name + for _, name := range items { // NB(chmou): if we find a che suffix then store it, we are using the // project prefixes as create from init-tenant. this probably need to be // updated to be future proof. @@ -126,22 +255,35 @@ func detectCurrentUserProject(current string, items []osapi.Project, c *k8client cmdutil.CheckErr(err) // Make sure after all it exists - for _, p := range items { - if p.Name == chosenone { - cfgmap, err := c.ConfigMaps(p.Name).List(api.ListOptions{LabelSelector: selector}) + for _, name := range items { + if name == chosenone { + cfgmap, err := c.ConfigMaps(name).List(api.ListOptions{LabelSelector: selector}) cmdutil.CheckErr(err) if len(cfgmap.Items) == 0 { //TODO: add command line switch to specify the environment if we can't detect it. - util.Fatalf("Could not autodetect your environment, there is no configmaps environment in the `%s` project.\n", p.Name) + util.Fatalf("Could not autodetect your environment, there is no configmaps environment in the `%s` namespace.\n", name) } return } } - util.Errorf("Cannot find parent project for: %s\n", current) + util.Errorf("Cannot find parent namespace for: %s\n", current) return "" } +// runCommand runs the given command on the command line and returns an error if it fails +func runCommand(prog string, args ...string) error { + cmd := exec.Command(prog, args...) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + text := prog + " " + strings.Join(args, " ") + return fmt.Errorf("Failed to run command %s due to error %v", text, err) + } + return nil +} + func defaultDomain() string { defaultDomain := os.Getenv("KUBERNETES_DOMAIN") if defaultDomain == "" { diff --git a/cmds/cruds.go b/cmds/cruds.go index c5a72fd78..7fe1861a8 100644 --- a/cmds/cruds.go +++ b/cmds/cruds.go @@ -49,8 +49,21 @@ func NewCmdDelete(f *cmdutil.Factory, out io.Writer) *cobra.Command { cmd.AddCommand(NewCmdDeleteCluster(f)) cmd.AddCommand(NewCmdDeleteEnviron(f)) - cmd.AddCommand(NewCmdCleanUpSystem(f)) + return cmd +} + +func NewCmdCleanUp(f *cmdutil.Factory, out io.Writer) *cobra.Command { + cmd := &cobra.Command{ + Use: "clean", + Short: "Clean up a resource type without deleting it", + Long: longhelp, + } + cmd.AddCommand(NewCmdCleanUpApp(f)) + cmd.AddCommand(NewCmdCleanUpContentRepository(f)) + cmd.AddCommand(NewCmdCleanUpJenkins(f)) + cmd.AddCommand(NewCmdCleanUpSystem(f)) + cmd.AddCommand(NewCmdCleanUpTenant(f)) return cmd } diff --git a/cmds/deploy.go b/cmds/deploy.go index 82325f60f..ea480581e 100644 --- a/cmds/deploy.go +++ b/cmds/deploy.go @@ -225,7 +225,9 @@ func NewCmdDeploy(f *cmdutil.Factory) *cobra.Command { cmd.PersistentFlags().String(exposerFlag, "", "The exposecontroller strategy such as Ingress, Router, NodePort, LoadBalancer") cmd.PersistentFlags().String(githubClientIDFlag, "", "The github OAuth Application Client ID. Defaults to $GITHUB_OAUTH_CLIENT_ID if not specified") cmd.PersistentFlags().String(githubClientSecretFlag, "", "The github OAuth Application Client Secret. Defaults to $GITHUB_OAUTH_CLIENT_SECRET if not specified") - cmd.PersistentFlags().String(packageFlag, systemPackage, "The name of the package to startup such as 'service', 'platform', 'console', 'ipaas'. Otherwise specify a URL or local file of the YAML to install") + // TODO re-enable when we're ready for 4.x + //cmd.PersistentFlags().String(packageFlag, systemPackage, "The name of the package to startup such as 'system' for the 4.x version of fabric8 or 'platform' for 3.x. Otherwise specify a URL or local file of the YAML to install") + cmd.PersistentFlags().String(packageFlag, platformPackage, "The name of the package to startup such as 'system' for the 4.x version of fabric8 or 'platform' for 3.x. Otherwise specify a URL or local file of the YAML to install") cmd.PersistentFlags().Bool(pvFlag, true, "if false will convert deployments to use Kubernetes emptyDir and disable persistence for core apps") cmd.PersistentFlags().Bool(templatesFlag, true, "Should the standard Fabric8 templates be installed?") diff --git a/cmds/environ.go b/cmds/environ.go index 5e8d4ded6..269a9022e 100644 --- a/cmds/environ.go +++ b/cmds/environ.go @@ -155,9 +155,9 @@ func createEnviron(cmd *cobra.Command, args []string, detectedNS string, c *k8cl // NewCmdDeleteEnviron is a command to delete an environ using: gofabric8 delete environ abcd func NewCmdDeleteEnviron(f *cmdutil.Factory) *cobra.Command { cmd := &cobra.Command{ - Use: "environ", + Use: "env", Short: "Delete environment from fabric8-environments configmap", - Aliases: []string{"env"}, + Aliases: []string{"environ", "enviroment"}, Run: func(cmd *cobra.Command, args []string) { wp := cmd.Flags().Lookup("work-project").Value.String() detectedNS, c, _ := getOpenShiftClient(f, wp) diff --git a/cmds/log.go b/cmds/log.go new file mode 100644 index 000000000..d1093da02 --- /dev/null +++ b/cmds/log.go @@ -0,0 +1,76 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * 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 cmds + +import ( + "fmt" + + "github.com/fabric8io/gofabric8/client" + "github.com/fabric8io/gofabric8/util" + "github.com/spf13/cobra" + cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" +) + +type logFlags struct { + name string +} + +// NewCmdLog tails the log of the newest pod for a Deployment or DeploymentConfig +func NewCmdLog(f *cmdutil.Factory) *cobra.Command { + p := &logFlags{} + cmd := &cobra.Command{ + Use: "log", + Short: "Tails the log of the newest pod for the given named Deployment or DeploymentConfig", + Long: `Tails the log of the newest pod for the given named Deployment or DeploymentConfig`, + + Run: func(cmd *cobra.Command, args []string) { + err := p.tailLog(f, args) + if err != nil { + util.Fatalf("%s", err) + } + return + }, + } + flags := cmd.Flags() + flags.StringVarP(&p.name, "name", "n", "", "the name of the Deployment or DeploymentConfig to log") + return cmd +} + +func (p *logFlags) tailLog(f *cmdutil.Factory, args []string) error { + if len(args) == 0 { + return fmt.Errorf("Must specify a Deployment/DeploymentConfig name argument!") + } + p.name = args[0] + c, cfg := client.NewClient(f) + ns, _, _ := f.DefaultNamespace() + oc, _ := client.NewOpenShiftClient(cfg) + initSchema() + + for { + pod, err := waitForReadyPodForDeploymentOrDC(c, oc, ns, p.name) + if err != nil { + return err + } + if pod == "" { + return fmt.Errorf("No pod found for ") + } + err = runCommand("kubectl", "logs", "-f", pod) + if err != nil { + return nil + } + } + return nil +} diff --git a/gofabric8.go b/gofabric8.go index b24a8d1d9..91a575b75 100644 --- a/gofabric8.go +++ b/gofabric8.go @@ -105,11 +105,13 @@ func NewGoFabric8Command(f *cmdutil.Factory, in io.Reader, out, err io.Writer) * cmds.AddCommand(commands.NewCmdCopyEndpoints(f)) cmds.AddCommand(commands.NewCmdCheShell(f)) + cmds.AddCommand(commands.NewCmdCleanUp(f, out)) cmds.AddCommand(commands.NewCmdConsole(f)) cmds.AddCommand(commands.NewCmdDeploy(f)) cmds.AddCommand(commands.NewCmdDockerEnv(f)) cmds.AddCommand(commands.NewCmdIngress(f)) cmds.AddCommand(commands.NewCmdInstall(f)) + cmds.AddCommand(commands.NewCmdLog(f)) cmds.AddCommand(commands.NewCmdPackages(f)) cmds.AddCommand(commands.NewCmdPackageVersions(f)) cmds.AddCommand(commands.NewCmdPull(f))