diff --git a/create/project.go b/create/project.go index 0b2ab6c..5d9ea6f 100644 --- a/create/project.go +++ b/create/project.go @@ -14,6 +14,7 @@ import ( type projectCmd struct { Name string `arg:"" default:"" help:"Name of the project. A random name is generated if omitted."` + DisplayName string `default:"" help:"Display Name of the project."` Wait bool `default:"true" help:"Wait until the project was fully created."` WaitTimeout time.Duration `default:"10m" help:"Duration to wait for project getting ready. Only relevant if wait is set."` } @@ -27,7 +28,7 @@ func (proj *projectCmd) Run(ctx context.Context, client *api.Client) error { return err } - p := newProject(proj.Name, cfg.Organization) + p := newProject(proj.Name, cfg.Organization, proj.DisplayName) fmt.Printf("Creating new project %s for organization %s\n", p.Name, cfg.Organization) c := newCreator(client, p, strings.ToLower(management.ProjectKind)) ctx, cancel := context.WithTimeout(ctx, proj.WaitTimeout) @@ -47,7 +48,7 @@ func (proj *projectCmd) Run(ctx context.Context, client *api.Client) error { }) } -func newProject(name, project string) *management.Project { +func newProject(name, project, displayName string) *management.Project { return &management.Project{ ObjectMeta: metav1.ObjectMeta{ Name: getName(name), @@ -57,6 +58,8 @@ func newProject(name, project string) *management.Project { Kind: management.ProjectKind, APIVersion: management.SchemeGroupVersion.String(), }, - Spec: management.ProjectSpec{}, + Spec: management.ProjectSpec{ + DisplayName: displayName, + }, } } diff --git a/create/project_test.go b/create/project_test.go index 821dbe6..d3ff6b6 100644 --- a/create/project_test.go +++ b/create/project_test.go @@ -26,6 +26,7 @@ func TestProjects(t *testing.T) { cmd := projectCmd{ Name: projectName, + DisplayName: "Some Display Name", Wait: false, WaitTimeout: time.Second, } diff --git a/get/project.go b/get/project.go index 1ab6dcc..9cd4f9b 100644 --- a/get/project.go +++ b/get/project.go @@ -8,6 +8,7 @@ import ( management "github.com/ninech/apis/management/v1alpha1" "github.com/ninech/nctl/api" + "github.com/ninech/nctl/api/util" "github.com/ninech/nctl/internal/format" ) @@ -60,11 +61,15 @@ func printProject(projects []management.Project, get Cmd, out io.Writer, header // for projects if header { get.AllProjects = false - get.writeHeader(w, "NAME") + get.writeHeader(w, "NAME", "DISPLAY NAME") } for _, proj := range projects { - get.writeTabRow(w, "", proj.Name) + displayName := proj.Spec.DisplayName + if len(displayName) == 0 { + displayName = util.NoneText + } + get.writeTabRow(w, "", proj.Name, displayName) } return w.Flush() diff --git a/get/project_test.go b/get/project_test.go index f602e85..6a1a171 100644 --- a/get/project_test.go +++ b/get/project_test.go @@ -23,6 +23,7 @@ func TestProject(t *testing.T) { for name, testCase := range map[string]struct { projects []client.Object + displayNames []string name string outputFormat output allProjects bool @@ -30,29 +31,30 @@ func TestProject(t *testing.T) { }{ "projects exist, full format": { projects: test.Projects(organization, "dev", "staging", "prod"), + displayNames: []string{"Development", "", "Production"}, outputFormat: full, - output: `NAME -dev -prod -staging + output: `NAME DISPLAY NAME +dev Development +prod Production +staging `, }, "projects exist, no header format": { projects: test.Projects(organization, "dev", "staging", "prod"), outputFormat: noHeader, - output: `dev -prod -staging + output: `dev +prod +staging `, }, "projects exist and allProjects is set": { projects: test.Projects(organization, "dev", "staging", "prod"), outputFormat: full, allProjects: true, - output: `NAME -dev -prod -staging + output: `NAME DISPLAY NAME +dev +prod +staging `, }, "no projects exist": { @@ -69,8 +71,8 @@ staging projects: test.Projects(organization, "dev", "staging"), name: "dev", outputFormat: full, - output: `NAME -dev + output: `NAME DISPLAY NAME +dev `, }, "specific project requested, but does not exist": { @@ -99,6 +101,14 @@ dev t.Fatal(err) } + projects := testCase.projects + for i, proj := range projects { + if len(projects) != len(testCase.displayNames) { + break + } + proj.(*management.Project).Spec.DisplayName = testCase.displayNames[i] + } + client := fake.NewClientBuilder(). WithScheme(scheme). WithIndex(&management.Project{}, "metadata.name", func(o client.Object) []string { diff --git a/update/project.go b/update/project.go new file mode 100644 index 0000000..110b7fd --- /dev/null +++ b/update/project.go @@ -0,0 +1,53 @@ +package update + +import ( + "context" + "fmt" + + "github.com/crossplane/crossplane-runtime/pkg/resource" + management "github.com/ninech/apis/management/v1alpha1" + "github.com/ninech/nctl/api" + "github.com/ninech/nctl/auth" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type projectCmd struct { + Name string `arg:"" default:"" help:"Name of the project to update."` + DisplayName *string `default:"" help:"Display Name of the project."` +} + +func (cmd *projectCmd) Run(ctx context.Context, client *api.Client) error { + cfg, err := auth.ReadConfig(client.KubeconfigPath, client.KubeconfigContext) + if err != nil { + if auth.IsConfigNotFoundError(err) { + return auth.ReloginNeeded(err) + } + return err + } + + project := &management.Project{ + ObjectMeta: metav1.ObjectMeta{ + Name: cmd.Name, + Namespace: cfg.Organization, + }, + } + + upd := newUpdater(client, project, management.ProjectKind, func(current resource.Managed) error { + project, ok := current.(*management.Project) + if !ok { + return fmt.Errorf("resource is of type %T, expected %T", current, management.Project{}) + } + + cmd.applyUpdates(project) + + return nil + }) + + return upd.Update(ctx) +} + +func (cmd *projectCmd) applyUpdates(project *management.Project) { + if cmd.DisplayName != nil { + project.Spec.DisplayName = *cmd.DisplayName + } +} diff --git a/update/project_test.go b/update/project_test.go new file mode 100644 index 0000000..5c20931 --- /dev/null +++ b/update/project_test.go @@ -0,0 +1,81 @@ +package update + +import ( + "context" + "os" + "testing" + + management "github.com/ninech/apis/management/v1alpha1" + "github.com/ninech/nctl/api" + "github.com/ninech/nctl/internal/test" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" +) + +func TestProject(t *testing.T) { + const ( + projectName = "some-project" + organization = "org" + ) + existingProject := &management.Project{ + ObjectMeta: metav1.ObjectMeta{ + Name: projectName, + Namespace: organization, + }, + Spec: management.ProjectSpec{}, + } + + cases := map[string]struct { + orig *management.Project + project string + cmd projectCmd + checkProject func(t *testing.T, cmd projectCmd, orig, updated *management.Project) + }{ + "all fields update": { + orig: existingProject, + project: projectName, + cmd: projectCmd{ + Name: projectName, + DisplayName: ptr.To("some display name"), + }, + checkProject: func(t *testing.T, cmd projectCmd, orig, updated *management.Project) { + assert.Equal(t, *cmd.DisplayName, updated.Spec.DisplayName) + }, + }, + } + + for name, tc := range cases { + tc := tc + + t.Run(name, func(t *testing.T) { + apiClient, err := test.SetupClient(tc.orig) + if err != nil { + t.Fatal(err) + } + apiClient.Project = tc.project + + ctx := context.Background() + + // we create a kubeconfig which does not contain a nctl config + // extension + kubeconfig, err := test.CreateTestKubeconfig(apiClient, organization) + require.NoError(t, err) + defer os.Remove(kubeconfig) + + if err := tc.cmd.Run(ctx, apiClient); err != nil { + t.Fatal(err) + } + + updated := &management.Project{} + if err := apiClient.Get(ctx, api.ObjectName(tc.orig), updated); err != nil { + t.Fatal(err) + } + + if tc.checkProject != nil { + tc.checkProject(t, tc.cmd, tc.orig, updated) + } + }) + } +} diff --git a/update/update.go b/update/update.go index e982187..8cddfc0 100644 --- a/update/update.go +++ b/update/update.go @@ -11,6 +11,7 @@ import ( type Cmd struct { Application applicationCmd `cmd:"" group:"deplo.io" name:"application" aliases:"app" help:"Update an existing deplo.io Application. (Beta - requires access)"` Config configCmd `cmd:"" group:"deplo.io" name:"config" help:"Update an existing deplo.io Project Configuration. (Beta - requires access)"` + Project projectCmd `cmd:"" group:"management.nine.ch" name:"project" help:"Update an existing Project"` } type updater struct { @@ -27,7 +28,7 @@ func newUpdater(client *api.Client, mg resource.Managed, kind string, f updateFu } func (u *updater) Update(ctx context.Context) error { - if err := u.client.Get(ctx, u.client.Name(u.mg.GetName()), u.mg); err != nil { + if err := u.client.Get(ctx, api.ObjectName(u.mg), u.mg); err != nil { return err }