Skip to content

Commit

Permalink
odo create namespace (redhat-developer#5724)
Browse files Browse the repository at this point in the history
* odo create namespace

* Add documentation

Signed-off-by: Parthvi Vala <[email protected]>

* Philippe's review

Signed-off-by: Parthvi Vala <[email protected]>

* Rename doc file

* Attempt at fixing integration tests

Signed-off-by: Parthvi Vala <[email protected]>

* Second Attempt at fixing integration tests

Signed-off-by: Parthvi Vala <[email protected]>

* Arm3l review

* Arm3l review part 2
  • Loading branch information
valaparthvi authored and cdrage committed Aug 31, 2022
1 parent 6058f5c commit b805e9d
Show file tree
Hide file tree
Showing 9 changed files with 297 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
title: odo create namespace
sidebar_position: 3
---

`odo create namespace` lets you create a namespace/project on your cluster. If you are on a Kubernetes cluster, running the command will create a Namespace resource for you, and for an OpenShift cluster, it will create a Project resource.

Any new namespace created with this command will also be set as the current active namespace, this applies to project as well.

To create a namespace you can run `odo create namespace <name>`:
```shell
odo create namespace mynamespace
```

Example -
```shell
odo create namespace mynamespace
✓ Namespace "mynamespace" is ready for use
✓ New namespace created and now using namespace: mynamespace
```

Optionally, you can also use `project` as an alias to `namespace`.

To create a project you can run `odo create project <name>`:
```shell
odo create project myproject
```

Example -
```shell
odo create project myproject
✓ Project "myproject" is ready for use
✓ New project created and now using project: myproject
```

:::note
Using either of the aliases will not make any change to the resource created on the cluster. This command is smart enough to detect the resources supported by your cluster and make an informed decision on the type of resource that should be created.
So you can run `odo create project` on a Kubernetes cluster, and it will create a Namespace resource, and you can run `odo create namespace` on an OpenShift cluster, it will create a Project resource.
:::

## Available Flags
* `--wait` - Use this flag to wait until the new namespace is ready
2 changes: 2 additions & 0 deletions pkg/odo/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/redhat-developer/odo/pkg/odo/cli/add"
"github.com/redhat-developer/odo/pkg/odo/cli/alizer"
"github.com/redhat-developer/odo/pkg/odo/cli/build_images"
"github.com/redhat-developer/odo/pkg/odo/cli/create"
_delete "github.com/redhat-developer/odo/pkg/odo/cli/delete"
"github.com/redhat-developer/odo/pkg/odo/cli/deploy"
"github.com/redhat-developer/odo/pkg/odo/cli/describe"
Expand Down Expand Up @@ -179,6 +180,7 @@ func odoRootCmd(name, fullName string) *cobra.Command {
alizer.NewCmdAlizer(alizer.RecommendedCommandName, util.GetFullName(fullName, alizer.RecommendedCommandName)),
describe.NewCmdDescribe(describe.RecommendedCommandName, util.GetFullName(fullName, describe.RecommendedCommandName)),
registry.NewCmdRegistry(registry.RecommendedCommandName, util.GetFullName(fullName, registry.RecommendedCommandName)),
create.NewCmdCreate(create.RecommendedCommandName, util.GetFullName(fullName, create.RecommendedCommandName)),
)

// Add all subcommands to base commands
Expand Down
35 changes: 35 additions & 0 deletions pkg/odo/cli/create/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package create

import (
"fmt"

"github.com/spf13/cobra"

"github.com/redhat-developer/odo/pkg/odo/cli/create/namespace"
odoutil "github.com/redhat-developer/odo/pkg/odo/util"
)

// RecommendedCommandName is the recommended namespace command name
const RecommendedCommandName = "create"

// NewCmdCreate implements the namespace odo command
func NewCmdCreate(name, fullName string) *cobra.Command {

namespaceCreateCmd := namespace.NewCmdNamespaceCreate(namespace.RecommendedCommandName, odoutil.GetFullName(fullName, namespace.RecommendedCommandName))
createCmd := &cobra.Command{
Use: name + " [options]",
Short: "Perform create operation",
Long: "Perform create operation",
Example: fmt.Sprintf("%s\n",
namespaceCreateCmd.Example,
),
Annotations: map[string]string{"command": "main"},
}

createCmd.AddCommand(namespaceCreateCmd)

// Add a defined annotation in order to appear in the help menu
createCmd.SetUsageTemplate(odoutil.CmdUsageTemplate)

return createCmd
}
142 changes: 142 additions & 0 deletions pkg/odo/cli/create/namespace/namespace.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package namespace

import (
"fmt"
"os"
"strings"

"context"

dfutil "github.com/devfile/library/pkg/util"
"github.com/spf13/cobra"
ktemplates "k8s.io/kubectl/pkg/util/templates"

"github.com/redhat-developer/odo/pkg/log"
"github.com/redhat-developer/odo/pkg/odo/cmdline"
"github.com/redhat-developer/odo/pkg/odo/genericclioptions"
"github.com/redhat-developer/odo/pkg/odo/genericclioptions/clientset"
scontext "github.com/redhat-developer/odo/pkg/segment/context"
)

const RecommendedCommandName = "namespace"

var (
createExample = ktemplates.Examples(`
# Create a new namespace and set it as the current active namespace
%[1]s my-namespace
`)

createLongDesc = ktemplates.LongDesc(`Create a new namespace.
This command directly performs actions on the cluster and doesn't require a push.
Any new namespace created with this command will also be set as the current active namespace.
`)

createShortDesc = `Create a new namespace`
)

// NamespaceCreateOptions encapsulates the options for the odo namespace create command
type NamespaceCreateOptions struct {
// Context
*genericclioptions.Context

// Clients
clientset *clientset.Clientset

// Parameters
namespaceName string

// Flags
waitFlag bool

// value can be either 'project' or 'namespace', depending on what command is called
commandName string
}

// NewNamespaceCreateOptions creates a NamespaceCreateOptions instance
func NewNamespaceCreateOptions() *NamespaceCreateOptions {
return &NamespaceCreateOptions{}
}

func (o *NamespaceCreateOptions) SetClientset(clientset *clientset.Clientset) {
o.clientset = clientset
}

// Complete completes NamespaceCreateOptions after they've been created
func (nco *NamespaceCreateOptions) Complete(cmdline cmdline.Cmdline, args []string) (err error) {
nco.namespaceName = args[0]
nco.Context, err = genericclioptions.New(genericclioptions.NewCreateParameters(cmdline))
if err != nil {
return err
}
if scontext.GetTelemetryStatus(cmdline.Context()) {
scontext.SetClusterType(cmdline.Context(), nco.KClient)
}
return nil
}

// Validate validates the parameters of the NamespaceCreateOptions
func (nco *NamespaceCreateOptions) Validate() error {
return dfutil.ValidateK8sResourceName("namespace name", nco.namespaceName)
}

// Run runs the namespace create command
func (nco *NamespaceCreateOptions) Run(ctx context.Context) (err error) {
// Create the "spinner"
s := &log.Status{}

// If the --wait parameter has been passed, we add a spinner..
if nco.waitFlag {
s = log.Spinnerf("Waiting for %s to come up", nco.commandName)
defer s.End(false)
}

// Create the namespace & end the spinner (if there is any..)
err = nco.clientset.ProjectClient.Create(nco.namespaceName, nco.waitFlag)
if err != nil {
return err
}
s.End(true)

successMessage := fmt.Sprintf(`%s %q is ready for use`, strings.Title(nco.commandName), nco.namespaceName)
log.Successf(successMessage)

// Set the current namespace when created
err = nco.clientset.ProjectClient.SetCurrent(nco.namespaceName)
if err != nil {
return err
}

log.Successf("New %[1]s created and now using %[1]s: %v", nco.commandName, nco.namespaceName)

return nil
}

// NewCmdNamespaceCreate creates the namespace create command
func NewCmdNamespaceCreate(name, fullName string) *cobra.Command {
o := NewNamespaceCreateOptions()
// To help the UI messages deal better with namespace vs project
o.commandName = name
if len(os.Args) > 2 {
o.commandName = os.Args[2]
}

namespaceCreateCmd := &cobra.Command{
Use: name,
Short: createShortDesc,
Long: createLongDesc,
Example: fmt.Sprintf(createExample, fullName),
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
genericclioptions.GenericRun(o, cmd, args)
},
Annotations: map[string]string{"command": "main"},
Aliases: []string{"project"},
}

namespaceCreateCmd.Flags().BoolVarP(&o.waitFlag, "wait", "w", false, "Wait until the namespace is ready")

clientset.Add(namespaceCreateCmd, clientset.PROJECT)

return namespaceCreateCmd
}
3 changes: 3 additions & 0 deletions tests/helper/helper_cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ type CliRunner interface {
SetProject(namespace string) string
DeleteNamespaceProject(projectName string)
DeletePod(podName string, projectName string)
GetNamespaceProject() string
CheckNamespaceProjectExists(name string) bool
GetActiveNamespace() string
GetEnvsDevFileDeployment(componentName, appName, projectName string) map[string]string
GetEnvRefNames(componentName, appName, projectName string) []string
GetPVCSize(compName, storageName, namespace string) string
Expand Down
2 changes: 1 addition & 1 deletion tests/helper/helper_generic.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ func JsonPathContentIsValidUserPort(json string, path string) {
))
}

// SetProjectName sets projectNames based on the neame of the test file name (withouth path and replacing _ with -), line number of current ginkgo execution, and a random string of 3 letters
// SetProjectName sets projectNames based on the name of the test file name (without path and replacing _ with -), line number of current ginkgo execution, and a random string of 3 letters
func SetProjectName() string {
// Get current test filename and remove file path, file extension and replace undescores with hyphens
currGinkgoTestFileName := strings.Replace(CurrentGinkgoTestDescription().FileName[strings.LastIndex(CurrentGinkgoTestDescription().FileName, "/")+1:strings.LastIndex(CurrentGinkgoTestDescription().FileName, ".")], "_", "-", -1)
Expand Down
12 changes: 12 additions & 0 deletions tests/helper/helper_kubectl.go
Original file line number Diff line number Diff line change
Expand Up @@ -378,3 +378,15 @@ func (kubectl KubectlRunner) EnsureOperatorIsInstalled(partialOperatorName strin
return strings.Contains(output, partialOperatorName)
})
}

func (kubectl KubectlRunner) GetNamespaceProject() string {
return Cmd(kubectl.path, "get", "namespace").ShouldPass().Out()
}

func (kubectl KubectlRunner) CheckNamespaceProjectExists(name string) bool {
return Cmd(kubectl.path, "get", "namespace", name).ShouldPass().pass
}

func (kubectl KubectlRunner) GetActiveNamespace() string {
return Cmd(kubectl.path, "config", "view", "--minify", "-ojsonpath={..namespace}").ShouldPass().Out()
}
12 changes: 12 additions & 0 deletions tests/helper/helper_oc.go
Original file line number Diff line number Diff line change
Expand Up @@ -570,3 +570,15 @@ func (oc OcRunner) EnsureOperatorIsInstalled(partialOperatorName string) {
return strings.Contains(output, partialOperatorName)
})
}

func (oc OcRunner) GetNamespaceProject() string {
return Cmd(oc.path, "get", "project").ShouldPass().Out()
}

func (oc OcRunner) CheckNamespaceProjectExists(name string) bool {
return Cmd(oc.path, "get", "project", name).ShouldPass().pass
}

func (oc OcRunner) GetActiveNamespace() string {
return Cmd(oc.path, "config", "view", "--minify", "-ojsonpath={..namespace}").ShouldPass().Out()
}
48 changes: 48 additions & 0 deletions tests/integration/cmd_namespace_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package integration

import (
"fmt"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"

"github.com/redhat-developer/odo/tests/helper"
)

var _ = Describe("create/delete/list/get/set namespace tests", func() {
var commonVar helper.CommonVar

BeforeEach(func() {
commonVar = helper.CommonBeforeEach()
})
AfterEach(func() {
helper.CommonAfterEach(commonVar)
})
for _, command := range []string{"namespace", "project"} {
When(fmt.Sprintf("using the alias %[1]s to create a %[1]s", command), func() {
var namespace string
BeforeEach(func() {
namespace = fmt.Sprintf("%s-%s", helper.RandString(4), command)
helper.Cmd("odo", "create", command, namespace, "--wait").ShouldPass()
})
AfterEach(func() {
commonVar.CliRunner.DeleteNamespaceProject(namespace)
})
It(fmt.Sprintf("should successfully create the %s", command), func() {
Expect(commonVar.CliRunner.CheckNamespaceProjectExists(namespace)).To(BeTrue())
Expect(commonVar.CliRunner.GetActiveNamespace()).To(Equal(namespace))
})
})

}

It("should fail to create namespace", func() {
By("using an existent namespace name", func() {
helper.Cmd("odo", "create", "namespace", commonVar.Project).ShouldFail()
})
By("using an invalid namespace name", func() {
helper.Cmd("odo", "create", "namespace", "12345").ShouldFail()
Expect(commonVar.CliRunner.GetActiveNamespace()).To(Equal(commonVar.Project))
})
})
})

0 comments on commit b805e9d

Please sign in to comment.