diff --git a/cmd/admin/v2/network.go b/cmd/admin/v2/network.go index e924374..b1805c4 100644 --- a/cmd/admin/v2/network.go +++ b/cmd/admin/v2/network.go @@ -205,6 +205,7 @@ func (c *networkCmd) Create(rq *adminv2.NetworkServiceCreateRequest) (*apiv2.Net if errorutil.IsConflict(err) { return nil, genericcli.AlreadyExistsError() } + return nil, err } diff --git a/cmd/admin/v2/partition.go b/cmd/admin/v2/partition.go index 1fa3a1a..df401cf 100644 --- a/cmd/admin/v2/partition.go +++ b/cmd/admin/v2/partition.go @@ -5,6 +5,7 @@ import ( "sort" "strings" + "github.com/metal-stack/api/go/errorutil" adminv2 "github.com/metal-stack/api/go/metalstack/admin/v2" apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" "github.com/metal-stack/cli/cmd/config" @@ -48,14 +49,6 @@ func newPartitionCmd(c *config.Config) *cobra.Command { ListPrinter: func() printers.Printer { return c.ListPrinter }, ValidArgsFn: c.Completion.Partition, Sorter: sorters.PartitionSorter(), - OnlyCmds: genericcli.OnlyCmds( - genericcli.DescribeCmd, - genericcli.ListCmd, - genericcli.CreateCmd, - genericcli.UpdateCmd, - genericcli.DeleteCmd, - genericcli.EditCmd, - ), CreateCmdMutateFn: func(cmd *cobra.Command) { cmd.Flags().String("id", "", "the id of the partition to create") addMutableFlags(cmd) @@ -173,7 +166,11 @@ func (c *partition) Create(rq *adminv2.PartitionServiceCreateRequest) (*apiv2.Pa resp, err := c.c.Client.Adminv2().Partition().Create(ctx, rq) if err != nil { - return nil, fmt.Errorf("failed to create partition: %w", err) + if errorutil.IsConflict(err) { + return nil, genericcli.AlreadyExistsError() + } + + return nil, err } return resp.Partition, nil diff --git a/cmd/admin/v2/project.go b/cmd/admin/v2/project.go index 5ca54f0..d47234e 100644 --- a/cmd/admin/v2/project.go +++ b/cmd/admin/v2/project.go @@ -3,6 +3,7 @@ package v2 import ( "fmt" + "github.com/metal-stack/api/go/errorutil" adminv2 "github.com/metal-stack/api/go/metalstack/admin/v2" apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" "github.com/metal-stack/cli/cmd/config" @@ -24,7 +25,7 @@ func newProjectCmd(c *config.Config) *cobra.Command { c: c, } - cmdsConfig := &genericcli.CmdsConfig[*apiv2.ProjectServiceCreateRequest, *apiv2.ProjectServiceUpdateRequest, *apiv2.Project]{ + cmdsConfig := &genericcli.CmdsConfig[*adminv2.ProjectServiceCreateRequest, *apiv2.ProjectServiceUpdateRequest, *apiv2.Project]{ BinaryName: config.BinaryName, GenericCLI: genericcli.NewGenericCLI(w).WithFS(c.Fs), Singular: "project", @@ -43,7 +44,19 @@ func newProjectCmd(c *config.Config) *cobra.Command { } func (c *project) Get(id string) (*apiv2.Project, error) { - panic("unimplemented") + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + req := &apiv2.ProjectServiceGetRequest{ + Project: id, + } + + resp, err := c.c.Client.Apiv2().Project().Get(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to get project: %w", err) + } + + return resp.GetProject(), nil } func (c *project) List() ([]*apiv2.Project, error) { @@ -73,18 +86,62 @@ func (c *project) List() ([]*apiv2.Project, error) { return resp.GetProjects(), nil } -func (c *project) Create(rq *apiv2.ProjectServiceCreateRequest) (*apiv2.Project, error) { - panic("unimplemented") +func (c *project) Create(rq *adminv2.ProjectServiceCreateRequest) (*apiv2.Project, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Adminv2().Project().Create(ctx, rq) + if err != nil { + if errorutil.IsConflict(err) { + return nil, genericcli.AlreadyExistsError() + } + + return nil, err + } + + return resp.Project, nil } func (c *project) Delete(id string) (*apiv2.Project, error) { - panic("unimplemented") + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Apiv2().Project().Delete(ctx, &apiv2.ProjectServiceDeleteRequest{ + Project: id, + }) + if err != nil { + return nil, fmt.Errorf("failed to delete project: %w", err) + } + + return resp.Project, nil } -func (c *project) Convert(r *apiv2.Project) (string, *apiv2.ProjectServiceCreateRequest, *apiv2.ProjectServiceUpdateRequest, error) { - panic("unimplemented") +func (c *project) Convert(r *apiv2.Project) (string, *adminv2.ProjectServiceCreateRequest, *apiv2.ProjectServiceUpdateRequest, error) { + return r.Uuid, &adminv2.ProjectServiceCreateRequest{ + Login: r.Tenant, + Name: r.Name, + Description: r.Description, + AvatarUrl: r.AvatarUrl, + Labels: pointer.SafeDeref(r.Meta).Labels, + Project: pointer.PointerOrNil(r.Uuid), + }, &apiv2.ProjectServiceUpdateRequest{ + Project: r.Uuid, + Name: pointer.PointerOrNil(r.Name), + Description: pointer.PointerOrNil(r.Description), + AvatarUrl: r.AvatarUrl, + UpdateMeta: helpers.UpdateMetaFromMeta(r.Meta), + Labels: helpers.UpdateLabelsFromMeta(r.Meta), + }, nil } func (c *project) Update(rq *apiv2.ProjectServiceUpdateRequest) (*apiv2.Project, error) { - panic("unimplemented") + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Apiv2().Project().Update(ctx, rq) + if err != nil { + return nil, fmt.Errorf("failed to update project: %w", err) + } + + return resp.Project, nil } diff --git a/cmd/admin/v2/tenant.go b/cmd/admin/v2/tenant.go index e8948c3..b64daee 100644 --- a/cmd/admin/v2/tenant.go +++ b/cmd/admin/v2/tenant.go @@ -25,7 +25,7 @@ func newTenantCmd(c *config.Config) *cobra.Command { c: c, } - cmdsConfig := &genericcli.CmdsConfig[*adminv2.TenantServiceCreateRequest, any, *apiv2.Tenant]{ + cmdsConfig := &genericcli.CmdsConfig[*adminv2.TenantServiceCreateRequest, *apiv2.TenantServiceUpdateRequest, *apiv2.Tenant]{ BinaryName: config.BinaryName, GenericCLI: genericcli.NewGenericCLI(w).WithFS(c.Fs), Singular: "tenant", @@ -54,7 +54,6 @@ func newTenantCmd(c *config.Config) *cobra.Command { AvatarUrl: pointer.PointerOrNil(viper.GetString("avatar-url")), }, nil }, - OnlyCmds: genericcli.OnlyCmds(genericcli.ListCmd, genericcli.CreateCmd), ValidArgsFn: w.c.Completion.AdminTenant, } @@ -62,7 +61,19 @@ func newTenantCmd(c *config.Config) *cobra.Command { } func (c *tenant) Get(id string) (*apiv2.Tenant, error) { - panic("unimplemented") + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + req := &apiv2.TenantServiceGetRequest{ + Login: id, + } + + resp, err := c.c.Client.Apiv2().Tenant().Get(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to get tenant: %w", err) + } + + return resp.GetTenant(), nil } func (c *tenant) List() ([]*apiv2.Tenant, error) { @@ -110,15 +121,50 @@ func (c *tenant) Create(rq *adminv2.TenantServiceCreateRequest) (*apiv2.Tenant, } func (c *tenant) Delete(id string) (*apiv2.Tenant, error) { - panic("unimplemented") + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Apiv2().Tenant().Delete(ctx, &apiv2.TenantServiceDeleteRequest{ + Login: id, + }) + if err != nil { + return nil, fmt.Errorf("failed to delete tenant: %w", err) + } + + return resp.Tenant, nil } -func (c *tenant) Convert(r *apiv2.Tenant) (string, *adminv2.TenantServiceCreateRequest, any, error) { - panic("unimplemented") +func (c *tenant) Convert(r *apiv2.Tenant) (string, *adminv2.TenantServiceCreateRequest, *apiv2.TenantServiceUpdateRequest, error) { + return r.Login, &adminv2.TenantServiceCreateRequest{ + Name: r.Name, + Description: pointer.PointerOrNil(r.Description), + Email: pointer.PointerOrNil(r.Email), + AvatarUrl: pointer.PointerOrNil(r.AvatarUrl), + Labels: pointer.SafeDeref(r.Meta).Labels, + Login: pointer.PointerOrNil(r.Login), + }, + &apiv2.TenantServiceUpdateRequest{ + Login: r.Login, + Name: pointer.PointerOrNil(r.Name), + Email: pointer.PointerOrNil(r.Email), + Description: pointer.PointerOrNil(r.Description), + AvatarUrl: pointer.PointerOrNil(r.AvatarUrl), + UpdateMeta: helpers.UpdateMetaFromMeta(r.Meta), + Labels: helpers.UpdateLabelsFromMeta(r.Meta), + }, + nil } -func (c *tenant) Update(rq any) (*apiv2.Tenant, error) { - panic("unimplemented") +func (c *tenant) Update(rq *apiv2.TenantServiceUpdateRequest) (*apiv2.Tenant, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Apiv2().Tenant().Update(ctx, rq) + if err != nil { + return nil, fmt.Errorf("failed to update tenant: %w", err) + } + + return resp.Tenant, nil } func newAddMemberCmd(c *config.Config) *cobra.Command { diff --git a/cmd/api/v2/project.go b/cmd/api/v2/project.go index 2f4f398..f9b8dd8 100644 --- a/cmd/api/v2/project.go +++ b/cmd/api/v2/project.go @@ -114,57 +114,7 @@ func newProjectCmd(c *config.Config) *cobra.Command { inviteCmd.AddCommand(generateInviteCmd, deleteInviteCmd, listInvitesCmd, joinProjectCmd) - memberCmd := &cobra.Command{ - Use: "member", - Aliases: []string{"members"}, - Short: "manage project members", - } - - listMembersCmd := &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: "lists members of a project", - RunE: func(cmd *cobra.Command, args []string) error { - return w.listMembers() - }, - } - - listMembersCmd.Flags().StringP("project", "p", "", "the project of which to list the members") - - genericcli.AddSortFlag(listMembersCmd, sorters.ProjectMemberSorter()) - - removeMemberCmd := &cobra.Command{ - Use: "delete ", - Aliases: []string{"destroy", "rm", "remove"}, - Short: "remove member from a project", - RunE: func(cmd *cobra.Command, args []string) error { - return w.removeMember(args) - }, - ValidArgsFunction: c.Completion.ProjectMember, - } - - removeMemberCmd.Flags().StringP("project", "p", "", "the project in which to remove the member") - - genericcli.Must(removeMemberCmd.RegisterFlagCompletionFunc("project", c.Completion.Project)) - - updateMemberCmd := &cobra.Command{ - Use: "update ", - Short: "update member from a project", - RunE: func(cmd *cobra.Command, args []string) error { - return w.updateMember(args) - }, - ValidArgsFunction: c.Completion.ProjectMember, - } - - updateMemberCmd.Flags().StringP("project", "p", "", "the project in which to remove the member") - updateMemberCmd.Flags().String("role", "", "the role of the member") - - genericcli.Must(updateMemberCmd.RegisterFlagCompletionFunc("project", c.Completion.Project)) - genericcli.Must(updateMemberCmd.RegisterFlagCompletionFunc("role", c.Completion.ProjectRole)) - - memberCmd.AddCommand(removeMemberCmd, updateMemberCmd, listMembersCmd) - - return genericcli.NewCmds(cmdsConfig, joinProjectCmd, inviteCmd, memberCmd) + return genericcli.NewCmds(cmdsConfig, joinProjectCmd, inviteCmd, newProjectMemberCmd(c)) } func (c *project) Get(id string) (*apiv2.Project, error) { @@ -223,6 +173,7 @@ func (c *project) Create(rq *apiv2.ProjectServiceCreateRequest) (*apiv2.Project, return nil, err } + return resp.Project, nil } @@ -245,10 +196,15 @@ func (c *project) Convert(r *apiv2.Project) (string, *apiv2.ProjectServiceCreate Login: r.Tenant, Name: r.Name, Description: r.Description, + AvatarUrl: r.AvatarUrl, + Labels: pointer.SafeDeref(r.Meta).Labels, }, &apiv2.ProjectServiceUpdateRequest{ Project: r.Uuid, - Name: new(r.Name), - Description: new(r.Description), + Name: pointer.PointerOrNil(r.Name), + Description: pointer.PointerOrNil(r.Description), + AvatarUrl: r.AvatarUrl, + UpdateMeta: helpers.UpdateMetaFromMeta(r.Meta), + Labels: helpers.UpdateLabelsFromMeta(r.Meta), }, nil } @@ -409,66 +365,3 @@ func (c *project) deleteInvite(args []string) error { return nil } - -func (c *project) removeMember(args []string) error { - member, err := genericcli.GetExactlyOneArg(args) - if err != nil { - return err - } - - ctx, cancel := c.c.NewRequestContext() - defer cancel() - - _, err = c.c.Client.Apiv2().Project().RemoveMember(ctx, &apiv2.ProjectServiceRemoveMemberRequest{ - Project: c.c.GetProject(), - Member: member, - }) - if err != nil { - return fmt.Errorf("failed to remove member from project: %w", err) - } - - _, _ = fmt.Fprintf(c.c.Out, "%s successfully removed member %q\n", color.GreenString("✔"), member) - - return nil -} - -func (c *project) updateMember(args []string) error { - member, err := genericcli.GetExactlyOneArg(args) - if err != nil { - return err - } - - ctx, cancel := c.c.NewRequestContext() - defer cancel() - - resp, err := c.c.Client.Apiv2().Project().UpdateMember(ctx, &apiv2.ProjectServiceUpdateMemberRequest{ - Project: c.c.GetProject(), - Member: member, - Role: apiv2.ProjectRole(apiv2.ProjectRole_value[viper.GetString("role")]), - }) - if err != nil { - return fmt.Errorf("failed to update member: %w", err) - } - - return c.c.DescribePrinter.Print(resp.GetProjectMember()) -} - -func (c *project) listMembers() error { - ctx, cancel := c.c.NewRequestContext() - defer cancel() - - resp, err := c.c.Client.Apiv2().Project().Get(ctx, &apiv2.ProjectServiceGetRequest{ - Project: c.c.GetProject(), - }) - if err != nil { - return fmt.Errorf("failed to get project: %w", err) - } - - members := resp.GetProjectMembers() - - if err := sorters.ProjectMemberSorter().SortBy(members); err != nil { - return err - } - - return c.c.ListPrinter.Print(members) -} diff --git a/cmd/api/v2/project_member.go b/cmd/api/v2/project_member.go new file mode 100644 index 0000000..55ac197 --- /dev/null +++ b/cmd/api/v2/project_member.go @@ -0,0 +1,219 @@ +package v2 + +import ( + "fmt" + + "github.com/metal-stack/api/go/errorutil" + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + cliv2 "github.com/metal-stack/api/go/metalstack/cli/v2" + "github.com/metal-stack/cli/cmd/config" + "github.com/metal-stack/cli/cmd/sorters" + "github.com/metal-stack/metal-lib/pkg/genericcli" + "github.com/metal-stack/metal-lib/pkg/genericcli/printers" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +type projectMember struct { + c *config.Config +} + +func newProjectMemberCmd(c *config.Config) *cobra.Command { + w := &projectMember{ + c: c, + } + + cmdsConfig := &genericcli.CmdsConfig[*apiv2.ProjectServiceAddMemberRequest, *apiv2.ProjectServiceUpdateMemberRequest, *cliv2.ProjectMember]{ + BinaryName: config.BinaryName, + GenericCLI: genericcli.NewGenericCLI(w).WithFS(c.Fs), + Singular: "member", + Plural: "members", + Description: "manage api project members", + Sorter: sorters.ProjectMemberSorter(), + DescribePrinter: func() printers.Printer { return c.DescribePrinter }, + ListPrinter: func() printers.Printer { return c.ListPrinter }, + DescribeCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().String("project", "", "the project to describe the project members of, defaults to project of the default project") + + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.Project)) + }, + ListCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().String("project", "", "the project to list the project members of, defaults to project of the default project") + cmd.Flags().String("role", "", "the role of the member") + + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.Project)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("role", c.Completion.ProjectRole)) + }, + CreateCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().String("project", "", "the project for which to create a project member, defaults to project of the default project") + + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.Project)) + }, + UpdateCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().String("project", "", "the project for which to update a project member, defaults to project of the default project") + cmd.Flags().String("role", "", "the role of the member") + + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.Project)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("role", c.Completion.ProjectRole)) + + }, + DeleteCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().String("project", "", "the project from which to delete a project member, defaults to project of the default project") + + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.Project)) + }, + EditCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().String("project", "", "the project from which to edit a project member, defaults to project of the default project") + + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.Project)) + }, + CreateRequestFromCLI: w.createRequestFromCLI, + UpdateRequestFromCLI: w.updateRequestFromCLI, + ValidArgsFn: w.c.Completion.ProjectMember, + } + + return genericcli.NewCmds(cmdsConfig) +} + +func (c *projectMember) Get(id string) (*cliv2.ProjectMember, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + project := c.c.GetProject() + + resp, err := c.c.Client.Apiv2().Project().Get(ctx, &apiv2.ProjectServiceGetRequest{ + Project: project, + }) + if err != nil { + return nil, fmt.Errorf("failed to get project: %w", err) + } + + for _, member := range resp.GetProjectMembers() { + if member.Id == id { + return &cliv2.ProjectMember{ + Project: project, + ProjectMember: member, + }, nil + } + } + + return nil, fmt.Errorf("unable to find project members %q in project %q", id, project) +} + +func (c *projectMember) List() ([]*cliv2.ProjectMember, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + project := c.c.GetProject() + + resp, err := c.c.Client.Apiv2().Project().Get(ctx, &apiv2.ProjectServiceGetRequest{ + Project: project, + }) + if err != nil { + return nil, fmt.Errorf("failed to get project: %w", err) + } + + var res []*cliv2.ProjectMember + + for _, member := range resp.GetProjectMembers() { + res = append(res, &cliv2.ProjectMember{ + Project: project, + ProjectMember: member, + }) + } + + return res, nil +} + +func (c *projectMember) Create(rq *apiv2.ProjectServiceAddMemberRequest) (*cliv2.ProjectMember, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Apiv2().Project().AddMember(ctx, rq) + if err != nil { + if errorutil.IsConflict(err) { + return nil, genericcli.AlreadyExistsError() + } + + return nil, err + } + + return &cliv2.ProjectMember{ + Project: rq.Project, + ProjectMember: resp.ProjectMember, + }, nil +} + +func (c *projectMember) Delete(id string) (*cliv2.ProjectMember, error) { + project := c.c.GetProject() + + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Apiv2().Project().RemoveMember(ctx, &apiv2.ProjectServiceRemoveMemberRequest{ + Project: project, + Member: id, + }) + if err != nil { + return nil, fmt.Errorf("failed to remove member from project: %w", err) + } + + return &cliv2.ProjectMember{ + Project: project, + ProjectMember: resp.ProjectMember, + }, nil +} + +func (c *projectMember) Convert(r *cliv2.ProjectMember) (string, *apiv2.ProjectServiceAddMemberRequest, *apiv2.ProjectServiceUpdateMemberRequest, error) { + return r.ProjectMember.Id, &apiv2.ProjectServiceAddMemberRequest{ + Project: r.Project, + Member: r.ProjectMember.Id, + Role: r.ProjectMember.Role, + }, + &apiv2.ProjectServiceUpdateMemberRequest{ + Project: r.Project, + Member: r.ProjectMember.Id, + Role: r.ProjectMember.Role, + }, + nil +} + +func (c *projectMember) Update(rq *apiv2.ProjectServiceUpdateMemberRequest) (*cliv2.ProjectMember, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Apiv2().Project().UpdateMember(ctx, rq) + if err != nil { + return nil, fmt.Errorf("failed to update member: %w", err) + } + + return &cliv2.ProjectMember{ + Project: rq.Project, + ProjectMember: resp.ProjectMember, + }, nil +} + +func (c *projectMember) updateRequestFromCLI(args []string) (*apiv2.ProjectServiceUpdateMemberRequest, error) { + member, err := genericcli.GetExactlyOneArg(args) + if err != nil { + return nil, err + } + + project := c.c.GetProject() + + return &apiv2.ProjectServiceUpdateMemberRequest{ + Project: project, + Member: member, + Role: apiv2.ProjectRole(apiv2.ProjectRole_value[viper.GetString("role")]), + }, nil +} + +func (c *projectMember) createRequestFromCLI() (*apiv2.ProjectServiceAddMemberRequest, error) { + project := c.c.GetProject() + + return &apiv2.ProjectServiceAddMemberRequest{ + Project: project, + Member: viper.GetString("id"), + Role: apiv2.ProjectRole(apiv2.ProjectRole_value[viper.GetString("role")]), + }, nil +} diff --git a/cmd/api/v2/tenant.go b/cmd/api/v2/tenant.go index 2ffbef4..345b03a 100644 --- a/cmd/api/v2/tenant.go +++ b/cmd/api/v2/tenant.go @@ -111,59 +111,9 @@ func newTenantCmd(c *config.Config) *cobra.Command { }, } - memberCmd := &cobra.Command{ - Use: "member", - Aliases: []string{"members"}, - Short: "manage tenant members", - } - - listMembersCmd := &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: "lists members of a tenant", - RunE: func(cmd *cobra.Command, args []string) error { - return w.listMembers() - }, - } - - listMembersCmd.Flags().String("tenant", "", "the tenant in which to remove the member") - - genericcli.AddSortFlag(listMembersCmd, sorters.TenantMemberSorter()) - - removeMemberCmd := &cobra.Command{ - Use: "remove ", - Short: "remove member from a tenant", - Aliases: []string{"destroy", "rm", "remove"}, - RunE: func(cmd *cobra.Command, args []string) error { - return w.removeMember(args) - }, - ValidArgsFunction: c.Completion.TenantMember, - } - - removeMemberCmd.Flags().String("tenant", "", "the tenant in which to remove the member") - - genericcli.Must(removeMemberCmd.RegisterFlagCompletionFunc("tenant", c.Completion.Tenant)) - - updateMemberCmd := &cobra.Command{ - Use: "update ", - Short: "update member from a tenant", - RunE: func(cmd *cobra.Command, args []string) error { - return w.updateMember(args) - }, - ValidArgsFunction: c.Completion.TenantMember, - } - - updateMemberCmd.Flags().String("tenant", "", "the tenant in which to remove the member") - updateMemberCmd.Flags().String("role", "", "the role of the member") - - genericcli.Must(updateMemberCmd.RegisterFlagCompletionFunc("tenant", c.Completion.Tenant)) - genericcli.Must(updateMemberCmd.RegisterFlagCompletionFunc("role", c.Completion.TenantRole)) - - memberCmd.AddCommand(removeMemberCmd, updateMemberCmd, listMembersCmd) - inviteCmd.AddCommand(generateInviteCmd, deleteInviteCmd, listInvitesCmd, joinTenantCmd) - return genericcli.NewCmds(cmdsConfig, joinTenantCmd, inviteCmd, memberCmd) + return genericcli.NewCmds(cmdsConfig, joinTenantCmd, inviteCmd, newTenantMemberCmd(c)) } func (c *tenant) Get(id string) (*apiv2.Tenant, error) { @@ -241,12 +191,12 @@ func (c *tenant) Delete(id string) (*apiv2.Tenant, error) { } func (c *tenant) Convert(r *apiv2.Tenant) (string, *apiv2.TenantServiceCreateRequest, *apiv2.TenantServiceUpdateRequest, error) { - return r.Login, &apiv2.TenantServiceCreateRequest{ Name: r.Name, - Description: &r.Description, - Email: &r.Email, - AvatarUrl: &r.AvatarUrl, + Description: pointer.PointerOrNil(r.Description), + Email: pointer.PointerOrNil(r.Email), + AvatarUrl: pointer.PointerOrNil(r.AvatarUrl), + Labels: pointer.SafeDeref(r.Meta).Labels, }, &apiv2.TenantServiceUpdateRequest{ Login: r.Login, @@ -254,6 +204,8 @@ func (c *tenant) Convert(r *apiv2.Tenant) (string, *apiv2.TenantServiceCreateReq Email: pointer.PointerOrNil(r.Email), Description: pointer.PointerOrNil(r.Description), AvatarUrl: pointer.PointerOrNil(r.AvatarUrl), + UpdateMeta: helpers.UpdateMetaFromMeta(r.Meta), + Labels: helpers.UpdateLabelsFromMeta(r.Meta), }, nil } @@ -399,81 +351,3 @@ func (c *tenant) deleteInvite(args []string) error { return nil } - -func (c *tenant) removeMember(args []string) error { - member, err := genericcli.GetExactlyOneArg(args) - if err != nil { - return err - } - - tenant, err := c.c.GetTenant() - if err != nil { - return err - } - - ctx, cancel := c.c.NewRequestContext() - defer cancel() - - _, err = c.c.Client.Apiv2().Tenant().RemoveMember(ctx, &apiv2.TenantServiceRemoveMemberRequest{ - Login: tenant, - Member: member, - }) - if err != nil { - return fmt.Errorf("failed to remove member from tenant: %w", err) - } - - _, _ = fmt.Fprintf(c.c.Out, "%s successfully removed member %q\n", color.GreenString("✔"), member) - - return nil -} - -func (c *tenant) updateMember(args []string) error { - member, err := genericcli.GetExactlyOneArg(args) - if err != nil { - return err - } - - tenant, err := c.c.GetTenant() - if err != nil { - return err - } - - ctx, cancel := c.c.NewRequestContext() - defer cancel() - - resp, err := c.c.Client.Apiv2().Tenant().UpdateMember(ctx, &apiv2.TenantServiceUpdateMemberRequest{ - Login: tenant, - Member: member, - Role: apiv2.TenantRole(apiv2.TenantRole_value[viper.GetString("role")]), - }) - if err != nil { - return fmt.Errorf("failed to update member: %w", err) - } - - return c.c.DescribePrinter.Print(resp.GetTenantMember()) -} - -func (c *tenant) listMembers() error { - ctx, cancel := c.c.NewRequestContext() - defer cancel() - - tenant, err := c.c.GetTenant() - if err != nil { - return err - } - - resp, err := c.c.Client.Apiv2().Tenant().Get(ctx, &apiv2.TenantServiceGetRequest{ - Login: tenant, - }) - if err != nil { - return fmt.Errorf("failed to get tenant: %w", err) - } - - members := resp.GetTenantMembers() - - if err := sorters.TenantMemberSorter().SortBy(members); err != nil { - return err - } - - return c.c.ListPrinter.Print(members) -} diff --git a/cmd/api/v2/tenant_member.go b/cmd/api/v2/tenant_member.go new file mode 100644 index 0000000..da6246e --- /dev/null +++ b/cmd/api/v2/tenant_member.go @@ -0,0 +1,234 @@ +package v2 + +import ( + "fmt" + + "github.com/metal-stack/api/go/errorutil" + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + cliv2 "github.com/metal-stack/api/go/metalstack/cli/v2" + "github.com/metal-stack/cli/cmd/config" + "github.com/metal-stack/cli/cmd/sorters" + "github.com/metal-stack/metal-lib/pkg/genericcli" + "github.com/metal-stack/metal-lib/pkg/genericcli/printers" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +type tenantMember struct { + c *config.Config +} + +func newTenantMemberCmd(c *config.Config) *cobra.Command { + w := &tenantMember{ + c: c, + } + + cmdsConfig := &genericcli.CmdsConfig[*apiv2.TenantServiceAddMemberRequest, *apiv2.TenantServiceUpdateMemberRequest, *cliv2.TenantMember]{ + BinaryName: config.BinaryName, + GenericCLI: genericcli.NewGenericCLI(w).WithFS(c.Fs), + Singular: "member", + Plural: "members", + Description: "manage api tenant members", + Sorter: sorters.TenantMemberSorter(), + DescribePrinter: func() printers.Printer { return c.DescribePrinter }, + ListPrinter: func() printers.Printer { return c.ListPrinter }, + DescribeCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().String("tenant", "", "the tenant to describe the tenant members of, defaults to tenant of the default project") + + genericcli.Must(cmd.RegisterFlagCompletionFunc("tenant", c.Completion.Tenant)) + }, + ListCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().String("tenant", "", "the tenant to list the tenant members of, defaults to tenant of the default project") + cmd.Flags().String("role", "", "the role of the member") + + genericcli.Must(cmd.RegisterFlagCompletionFunc("tenant", c.Completion.Tenant)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("role", c.Completion.TenantRole)) + }, + CreateCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().String("tenant", "", "the tenant for which to create a tenant member, defaults to tenant of the default project") + + genericcli.Must(cmd.RegisterFlagCompletionFunc("tenant", c.Completion.Tenant)) + }, + UpdateCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().String("tenant", "", "the tenant for which to update a tenant member, defaults to tenant of the default project") + cmd.Flags().String("role", "", "the role of the member") + + genericcli.Must(cmd.RegisterFlagCompletionFunc("tenant", c.Completion.Tenant)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("role", c.Completion.TenantRole)) + + }, + DeleteCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().String("tenant", "", "the tenant from which to delete a tenant member, defaults to tenant of the default project") + + genericcli.Must(cmd.RegisterFlagCompletionFunc("tenant", c.Completion.Tenant)) + }, + EditCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().String("tenant", "", "the tenant from which to edit a tenant member, defaults to tenant of the default project") + + genericcli.Must(cmd.RegisterFlagCompletionFunc("tenant", c.Completion.Tenant)) + }, + CreateRequestFromCLI: w.createRequestFromCLI, + UpdateRequestFromCLI: w.updateRequestFromCLI, + ValidArgsFn: w.c.Completion.TenantMember, + } + + return genericcli.NewCmds(cmdsConfig) +} + +func (c *tenantMember) Get(id string) (*cliv2.TenantMember, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + tenant, err := c.c.GetTenant() + if err != nil { + return nil, err + } + + resp, err := c.c.Client.Apiv2().Tenant().Get(ctx, &apiv2.TenantServiceGetRequest{ + Login: tenant, + }) + if err != nil { + return nil, fmt.Errorf("failed to get tenant: %w", err) + } + + for _, member := range resp.GetTenantMembers() { + if member.Id == id { + return &cliv2.TenantMember{ + Tenant: tenant, + TenantMember: member, + }, nil + } + } + + return nil, fmt.Errorf("unable to find tenant members %q in tenant %q", id, tenant) +} + +func (c *tenantMember) List() ([]*cliv2.TenantMember, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + tenant, err := c.c.GetTenant() + if err != nil { + return nil, err + } + + resp, err := c.c.Client.Apiv2().Tenant().Get(ctx, &apiv2.TenantServiceGetRequest{ + Login: tenant, + }) + if err != nil { + return nil, fmt.Errorf("failed to get tenant: %w", err) + } + + var res []*cliv2.TenantMember + + for _, member := range resp.GetTenantMembers() { + res = append(res, &cliv2.TenantMember{ + Tenant: tenant, + TenantMember: member, + }) + } + + return res, nil +} + +func (c *tenantMember) Create(rq *apiv2.TenantServiceAddMemberRequest) (*cliv2.TenantMember, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Apiv2().Tenant().AddMember(ctx, rq) + if err != nil { + if errorutil.IsConflict(err) { + return nil, genericcli.AlreadyExistsError() + } + + return nil, err + } + + return &cliv2.TenantMember{ + Tenant: rq.Login, + TenantMember: resp.TenantMember, + }, nil +} + +func (c *tenantMember) Delete(id string) (*cliv2.TenantMember, error) { + tenant, err := c.c.GetTenant() + if err != nil { + return nil, err + } + + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Apiv2().Tenant().RemoveMember(ctx, &apiv2.TenantServiceRemoveMemberRequest{ + Login: tenant, + Member: id, + }) + if err != nil { + return nil, fmt.Errorf("failed to remove member from tenant: %w", err) + } + + return &cliv2.TenantMember{ + Tenant: tenant, + TenantMember: resp.TenantMember, + }, nil +} + +func (c *tenantMember) Convert(r *cliv2.TenantMember) (string, *apiv2.TenantServiceAddMemberRequest, *apiv2.TenantServiceUpdateMemberRequest, error) { + return r.TenantMember.Id, &apiv2.TenantServiceAddMemberRequest{ + Login: r.Tenant, + Member: r.TenantMember.Id, + Role: r.TenantMember.Role, + }, + &apiv2.TenantServiceUpdateMemberRequest{ + Login: r.Tenant, + Member: r.TenantMember.Id, + Role: r.TenantMember.Role, + }, + nil +} + +func (c *tenantMember) Update(rq *apiv2.TenantServiceUpdateMemberRequest) (*cliv2.TenantMember, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Apiv2().Tenant().UpdateMember(ctx, rq) + if err != nil { + return nil, fmt.Errorf("failed to update member: %w", err) + } + + return &cliv2.TenantMember{ + Tenant: rq.Login, + TenantMember: resp.TenantMember, + }, nil +} + +func (c *tenantMember) updateRequestFromCLI(args []string) (*apiv2.TenantServiceUpdateMemberRequest, error) { + member, err := genericcli.GetExactlyOneArg(args) + if err != nil { + return nil, err + } + + tenant, err := c.c.GetTenant() + if err != nil { + return nil, err + } + + return &apiv2.TenantServiceUpdateMemberRequest{ + Login: tenant, + Member: member, + Role: apiv2.TenantRole(apiv2.TenantRole_value[viper.GetString("role")]), + }, nil +} + +func (c *tenantMember) createRequestFromCLI() (*apiv2.TenantServiceAddMemberRequest, error) { + tenant, err := c.c.GetTenant() + if err != nil { + return nil, err + } + + return &apiv2.TenantServiceAddMemberRequest{ + Login: tenant, + Member: viper.GetString("id"), + Role: apiv2.TenantRole(apiv2.TenantRole_value[viper.GetString("role")]), + }, nil +} diff --git a/cmd/sorters/project.go b/cmd/sorters/project.go index f80487c..70fcaee 100644 --- a/cmd/sorters/project.go +++ b/cmd/sorters/project.go @@ -2,6 +2,7 @@ package sorters import ( apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + cliv2 "github.com/metal-stack/api/go/metalstack/cli/v2" "github.com/metal-stack/metal-lib/pkg/multisort" ) @@ -36,25 +37,25 @@ func ProjectInviteSorter() *multisort.Sorter[*apiv2.ProjectInvite] { }, multisort.Keys{{ID: "project"}, {ID: "role"}, {ID: "expiration"}}) } -func ProjectMemberSorter() *multisort.Sorter[*apiv2.ProjectMember] { - return multisort.New(multisort.FieldMap[*apiv2.ProjectMember]{ - "id": func(a, b *apiv2.ProjectMember, descending bool) multisort.CompareResult { - return multisort.Compare(a.Id, b.Id, descending) +func ProjectMemberSorter() *multisort.Sorter[*cliv2.ProjectMember] { + return multisort.New(multisort.FieldMap[*cliv2.ProjectMember]{ + "id": func(a, b *cliv2.ProjectMember, descending bool) multisort.CompareResult { + return multisort.Compare(a.ProjectMember.Id, b.ProjectMember.Id, descending) }, - "role": func(a, b *apiv2.ProjectMember, descending bool) multisort.CompareResult { - return multisort.Compare(a.Role, b.Role, descending) + "role": func(a, b *cliv2.ProjectMember, descending bool) multisort.CompareResult { + return multisort.Compare(a.ProjectMember.Role, b.ProjectMember.Role, descending) }, - "created": func(a, b *apiv2.ProjectMember, descending bool) multisort.CompareResult { - return multisort.Compare(a.CreatedAt.AsTime().UnixMilli(), b.CreatedAt.AsTime().UnixMilli(), descending) + "created": func(a, b *cliv2.ProjectMember, descending bool) multisort.CompareResult { + return multisort.Compare(a.ProjectMember.CreatedAt.AsTime().UnixMilli(), b.ProjectMember.CreatedAt.AsTime().UnixMilli(), descending) }, - "inherited": func(a, b *apiv2.ProjectMember, descending bool) multisort.CompareResult { + "inherited": func(a, b *cliv2.ProjectMember, descending bool) multisort.CompareResult { boolToInt := func(in bool) int { if in { return 1 } return 0 } - return multisort.Compare(boolToInt(a.InheritedMembership), boolToInt(b.InheritedMembership), descending) + return multisort.Compare(boolToInt(a.ProjectMember.InheritedMembership), boolToInt(b.ProjectMember.InheritedMembership), descending) }, }, multisort.Keys{{ID: "inherited", Descending: false}, {ID: "role"}, {ID: "id"}}) } diff --git a/cmd/sorters/tenant.go b/cmd/sorters/tenant.go index 159bb63..6e8554f 100644 --- a/cmd/sorters/tenant.go +++ b/cmd/sorters/tenant.go @@ -2,6 +2,7 @@ package sorters import ( apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + cliv2 "github.com/metal-stack/api/go/metalstack/cli/v2" "github.com/metal-stack/metal-lib/pkg/multisort" ) @@ -36,16 +37,16 @@ func TenantInviteSorter() *multisort.Sorter[*apiv2.TenantInvite] { }, multisort.Keys{{ID: "tenant"}, {ID: "role"}, {ID: "expiration"}}) } -func TenantMemberSorter() *multisort.Sorter[*apiv2.TenantMember] { - return multisort.New(multisort.FieldMap[*apiv2.TenantMember]{ - "id": func(a, b *apiv2.TenantMember, descending bool) multisort.CompareResult { - return multisort.Compare(a.Id, b.Id, descending) +func TenantMemberSorter() *multisort.Sorter[*cliv2.TenantMember] { + return multisort.New(multisort.FieldMap[*cliv2.TenantMember]{ + "id": func(a, b *cliv2.TenantMember, descending bool) multisort.CompareResult { + return multisort.Compare(a.TenantMember.Id, b.TenantMember.Id, descending) }, - "role": func(a, b *apiv2.TenantMember, descending bool) multisort.CompareResult { - return multisort.Compare(a.Role, b.Role, descending) + "role": func(a, b *cliv2.TenantMember, descending bool) multisort.CompareResult { + return multisort.Compare(a.TenantMember.Role, b.TenantMember.Role, descending) }, - "created": func(a, b *apiv2.TenantMember, descending bool) multisort.CompareResult { - return multisort.Compare(a.CreatedAt.AsTime().UnixMilli(), b.CreatedAt.AsTime().UnixMilli(), descending) + "created": func(a, b *cliv2.TenantMember, descending bool) multisort.CompareResult { + return multisort.Compare(a.TenantMember.CreatedAt.AsTime().UnixMilli(), b.TenantMember.CreatedAt.AsTime().UnixMilli(), descending) }, }, multisort.Keys{{ID: "role"}, {ID: "id"}}) } diff --git a/cmd/tableprinters/common.go b/cmd/tableprinters/common.go index f7c820f..fdbfac2 100644 --- a/cmd/tableprinters/common.go +++ b/cmd/tableprinters/common.go @@ -8,6 +8,7 @@ import ( adminv2 "github.com/metal-stack/api/go/metalstack/admin/v2" apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + cliv2 "github.com/metal-stack/api/go/metalstack/cli/v2" "github.com/metal-stack/cli/cmd/config" "github.com/metal-stack/metal-lib/pkg/genericcli/printers" "github.com/metal-stack/metal-lib/pkg/pointer" @@ -93,9 +94,9 @@ func (t *TablePrinter) ToHeaderAndRows(data any, wide bool) ([]string, [][]strin return t.ProjectInviteTable(pointer.WrapInSlice(d), wide) case []*apiv2.ProjectInvite: return t.ProjectInviteTable(d, wide) - case *apiv2.ProjectMember: + case *cliv2.ProjectMember: return t.ProjectMemberTable(pointer.WrapInSlice(d), wide) - case []*apiv2.ProjectMember: + case []*cliv2.ProjectMember: return t.ProjectMemberTable(d, wide) case *adminv2.TaskInfo: @@ -127,9 +128,9 @@ func (t *TablePrinter) ToHeaderAndRows(data any, wide bool) ([]string, [][]strin return t.TenantInviteTable(pointer.WrapInSlice(d), wide) case []*apiv2.TenantInvite: return t.TenantInviteTable(d, wide) - case *apiv2.TenantMember: + case *cliv2.TenantMember: return t.TenantMemberTable(pointer.WrapInSlice(d), wide) - case []*apiv2.TenantMember: + case []*cliv2.TenantMember: return t.TenantMemberTable(d, wide) case *apiv2.Health: diff --git a/cmd/tableprinters/project.go b/cmd/tableprinters/project.go index 27e893a..c34fbb3 100644 --- a/cmd/tableprinters/project.go +++ b/cmd/tableprinters/project.go @@ -6,6 +6,7 @@ import ( "github.com/dustin/go-humanize" apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + cliv2 "github.com/metal-stack/api/go/metalstack/cli/v2" "github.com/metal-stack/metal-lib/pkg/genericcli" ) @@ -54,7 +55,7 @@ func (t *TablePrinter) ProjectInviteTable(data []*apiv2.ProjectInvite, _ bool) ( return header, rows, nil } -func (t *TablePrinter) ProjectMemberTable(data []*apiv2.ProjectMember, _ bool) ([]string, [][]string, error) { +func (t *TablePrinter) ProjectMemberTable(data []*cliv2.ProjectMember, _ bool) ([]string, [][]string, error) { var ( rows [][]string ) @@ -62,10 +63,10 @@ func (t *TablePrinter) ProjectMemberTable(data []*apiv2.ProjectMember, _ bool) ( for _, member := range data { row := []string{ - member.Id, - member.Role.String(), - strconv.FormatBool(member.InheritedMembership), - humanize.Time(member.CreatedAt.AsTime()), + member.ProjectMember.Id, + member.ProjectMember.Role.String(), + strconv.FormatBool(member.ProjectMember.InheritedMembership), + humanize.Time(member.ProjectMember.CreatedAt.AsTime()), } rows = append(rows, row) diff --git a/cmd/tableprinters/tenant.go b/cmd/tableprinters/tenant.go index 5e26223..6778559 100644 --- a/cmd/tableprinters/tenant.go +++ b/cmd/tableprinters/tenant.go @@ -4,6 +4,7 @@ import ( "github.com/dustin/go-humanize" apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + cliv2 "github.com/metal-stack/api/go/metalstack/cli/v2" ) func (t *TablePrinter) TenantTable(data []*apiv2.Tenant, wide bool) ([]string, [][]string, error) { @@ -24,7 +25,7 @@ func (t *TablePrinter) TenantTable(data []*apiv2.Tenant, wide bool) ([]string, [ return header, rows, nil } -func (t *TablePrinter) TenantMemberTable(data []*apiv2.TenantMember, _ bool) ([]string, [][]string, error) { +func (t *TablePrinter) TenantMemberTable(data []*cliv2.TenantMember, _ bool) ([]string, [][]string, error) { var ( rows [][]string ) @@ -32,9 +33,9 @@ func (t *TablePrinter) TenantMemberTable(data []*apiv2.TenantMember, _ bool) ([] for _, member := range data { row := []string{ - member.Id, - member.Role.String(), - humanize.Time(member.CreatedAt.AsTime()), + member.TenantMember.Id, + member.TenantMember.Role.String(), + humanize.Time(member.TenantMember.CreatedAt.AsTime()), } rows = append(rows, row) diff --git a/docs/admin/metalctlv2_admin_partition.md b/docs/admin/metalctlv2_admin_partition.md index c76bfe4..5792597 100644 --- a/docs/admin/metalctlv2_admin_partition.md +++ b/docs/admin/metalctlv2_admin_partition.md @@ -28,6 +28,7 @@ manage partitions ### SEE ALSO * [metalctlv2 admin](metalctlv2_admin.md) - admin commands +* [metalctlv2 admin partition apply](metalctlv2_admin_partition_apply.md) - applies one or more partitions from a given file * [metalctlv2 admin partition capacity](metalctlv2_admin_partition_capacity.md) - show partition capacity * [metalctlv2 admin partition create](metalctlv2_admin_partition_create.md) - creates the partition * [metalctlv2 admin partition delete](metalctlv2_admin_partition_delete.md) - deletes the partition diff --git a/docs/admin/metalctlv2_admin_partition_apply.md b/docs/admin/metalctlv2_admin_partition_apply.md new file mode 100644 index 0000000..e96d77b --- /dev/null +++ b/docs/admin/metalctlv2_admin_partition_apply.md @@ -0,0 +1,46 @@ +## metalctlv2 admin partition apply + +applies one or more partitions from a given file + +``` +metalctlv2 admin partition apply [flags] +``` + +### Options + +``` + --bulk-output when used with --file (bulk operation): prints results at the end as a list. default is printing results intermediately during the operation, which causes single entities to be printed in a row. + -f, --file string filename of the create or update request in yaml format, or - for stdin. + + Example: + $ metalctlv2 partition describe partition-1 -o yaml > partition.yaml + $ vi partition.yaml + $ # either via stdin + $ cat partition.yaml | metalctlv2 partition apply -f - + $ # or via file + $ metalctlv2 partition apply -f partition.yaml + + the file can also contain multiple documents and perform a bulk operation. + + -h, --help help for apply + --skip-security-prompts skips security prompt for bulk operations + --timestamps when used with --file (bulk operation): prints timestamps in-between the operations +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template), wide is a table with more columns. (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2 admin partition](metalctlv2_admin_partition.md) - manage partition entities + diff --git a/docs/admin/metalctlv2_admin_tenant.md b/docs/admin/metalctlv2_admin_tenant.md index 66f3544..b4bf20d 100644 --- a/docs/admin/metalctlv2_admin_tenant.md +++ b/docs/admin/metalctlv2_admin_tenant.md @@ -29,6 +29,11 @@ manage api tenants * [metalctlv2 admin](metalctlv2_admin.md) - admin commands * [metalctlv2 admin tenant add-member](metalctlv2_admin_tenant_add-member.md) - Add a new member to a tenant +* [metalctlv2 admin tenant apply](metalctlv2_admin_tenant_apply.md) - applies one or more tenants from a given file * [metalctlv2 admin tenant create](metalctlv2_admin_tenant_create.md) - creates the tenant +* [metalctlv2 admin tenant delete](metalctlv2_admin_tenant_delete.md) - deletes the tenant +* [metalctlv2 admin tenant describe](metalctlv2_admin_tenant_describe.md) - describes the tenant +* [metalctlv2 admin tenant edit](metalctlv2_admin_tenant_edit.md) - edit the tenant through an editor and update * [metalctlv2 admin tenant list](metalctlv2_admin_tenant_list.md) - list all tenants +* [metalctlv2 admin tenant update](metalctlv2_admin_tenant_update.md) - updates the tenant diff --git a/docs/admin/metalctlv2_admin_tenant_apply.md b/docs/admin/metalctlv2_admin_tenant_apply.md new file mode 100644 index 0000000..4352b63 --- /dev/null +++ b/docs/admin/metalctlv2_admin_tenant_apply.md @@ -0,0 +1,46 @@ +## metalctlv2 admin tenant apply + +applies one or more tenants from a given file + +``` +metalctlv2 admin tenant apply [flags] +``` + +### Options + +``` + --bulk-output when used with --file (bulk operation): prints results at the end as a list. default is printing results intermediately during the operation, which causes single entities to be printed in a row. + -f, --file string filename of the create or update request in yaml format, or - for stdin. + + Example: + $ metalctlv2 tenant describe tenant-1 -o yaml > tenant.yaml + $ vi tenant.yaml + $ # either via stdin + $ cat tenant.yaml | metalctlv2 tenant apply -f - + $ # or via file + $ metalctlv2 tenant apply -f tenant.yaml + + the file can also contain multiple documents and perform a bulk operation. + + -h, --help help for apply + --skip-security-prompts skips security prompt for bulk operations + --timestamps when used with --file (bulk operation): prints timestamps in-between the operations +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template), wide is a table with more columns. (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2 admin tenant](metalctlv2_admin_tenant.md) - manage tenant entities + diff --git a/docs/admin/metalctlv2_admin_tenant_delete.md b/docs/admin/metalctlv2_admin_tenant_delete.md new file mode 100644 index 0000000..788535a --- /dev/null +++ b/docs/admin/metalctlv2_admin_tenant_delete.md @@ -0,0 +1,46 @@ +## metalctlv2 admin tenant delete + +deletes the tenant + +``` +metalctlv2 admin tenant delete [flags] +``` + +### Options + +``` + --bulk-output when used with --file (bulk operation): prints results at the end as a list. default is printing results intermediately during the operation, which causes single entities to be printed in a row. + -f, --file string filename of the create or update request in yaml format, or - for stdin. + + Example: + $ metalctlv2 tenant describe tenant-1 -o yaml > tenant.yaml + $ vi tenant.yaml + $ # either via stdin + $ cat tenant.yaml | metalctlv2 tenant delete -f - + $ # or via file + $ metalctlv2 tenant delete -f tenant.yaml + + the file can also contain multiple documents and perform a bulk operation. + + -h, --help help for delete + --skip-security-prompts skips security prompt for bulk operations + --timestamps when used with --file (bulk operation): prints timestamps in-between the operations +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template), wide is a table with more columns. (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2 admin tenant](metalctlv2_admin_tenant.md) - manage tenant entities + diff --git a/docs/admin/metalctlv2_admin_tenant_describe.md b/docs/admin/metalctlv2_admin_tenant_describe.md new file mode 100644 index 0000000..3821f57 --- /dev/null +++ b/docs/admin/metalctlv2_admin_tenant_describe.md @@ -0,0 +1,31 @@ +## metalctlv2 admin tenant describe + +describes the tenant + +``` +metalctlv2 admin tenant describe [flags] +``` + +### Options + +``` + -h, --help help for describe +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template), wide is a table with more columns. (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2 admin tenant](metalctlv2_admin_tenant.md) - manage tenant entities + diff --git a/docs/admin/metalctlv2_admin_tenant_edit.md b/docs/admin/metalctlv2_admin_tenant_edit.md new file mode 100644 index 0000000..e33d329 --- /dev/null +++ b/docs/admin/metalctlv2_admin_tenant_edit.md @@ -0,0 +1,31 @@ +## metalctlv2 admin tenant edit + +edit the tenant through an editor and update + +``` +metalctlv2 admin tenant edit [flags] +``` + +### Options + +``` + -h, --help help for edit +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template), wide is a table with more columns. (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2 admin tenant](metalctlv2_admin_tenant.md) - manage tenant entities + diff --git a/docs/admin/metalctlv2_admin_tenant_update.md b/docs/admin/metalctlv2_admin_tenant_update.md new file mode 100644 index 0000000..3920b4d --- /dev/null +++ b/docs/admin/metalctlv2_admin_tenant_update.md @@ -0,0 +1,46 @@ +## metalctlv2 admin tenant update + +updates the tenant + +``` +metalctlv2 admin tenant update [flags] +``` + +### Options + +``` + --bulk-output when used with --file (bulk operation): prints results at the end as a list. default is printing results intermediately during the operation, which causes single entities to be printed in a row. + -f, --file string filename of the create or update request in yaml format, or - for stdin. + + Example: + $ metalctlv2 tenant describe tenant-1 -o yaml > tenant.yaml + $ vi tenant.yaml + $ # either via stdin + $ cat tenant.yaml | metalctlv2 tenant update -f - + $ # or via file + $ metalctlv2 tenant update -f tenant.yaml + + the file can also contain multiple documents and perform a bulk operation. + + -h, --help help for update + --skip-security-prompts skips security prompt for bulk operations + --timestamps when used with --file (bulk operation): prints timestamps in-between the operations +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template), wide is a table with more columns. (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2 admin tenant](metalctlv2_admin_tenant.md) - manage tenant entities + diff --git a/docs/metalctlv2_project.md b/docs/metalctlv2_project.md index d0c1aa6..a3b7b4f 100644 --- a/docs/metalctlv2_project.md +++ b/docs/metalctlv2_project.md @@ -36,6 +36,6 @@ manage api projects * [metalctlv2 project invite](metalctlv2_project_invite.md) - manage project invites * [metalctlv2 project join](metalctlv2_project_join.md) - join a project of someone who shared an invite secret with you * [metalctlv2 project list](metalctlv2_project_list.md) - list all projects -* [metalctlv2 project member](metalctlv2_project_member.md) - manage project members +* [metalctlv2 project member](metalctlv2_project_member.md) - manage member entities * [metalctlv2 project update](metalctlv2_project_update.md) - updates the project diff --git a/docs/metalctlv2_project_member.md b/docs/metalctlv2_project_member.md index 88bc350..8a9553f 100644 --- a/docs/metalctlv2_project_member.md +++ b/docs/metalctlv2_project_member.md @@ -1,6 +1,10 @@ ## metalctlv2 project member -manage project members +manage member entities + +### Synopsis + +manage api project members ### Options @@ -24,7 +28,11 @@ manage project members ### SEE ALSO * [metalctlv2 project](metalctlv2_project.md) - manage project entities -* [metalctlv2 project member delete](metalctlv2_project_member_delete.md) - remove member from a project -* [metalctlv2 project member list](metalctlv2_project_member_list.md) - lists members of a project -* [metalctlv2 project member update](metalctlv2_project_member_update.md) - update member from a project +* [metalctlv2 project member apply](metalctlv2_project_member_apply.md) - applies one or more members from a given file +* [metalctlv2 project member create](metalctlv2_project_member_create.md) - creates the member +* [metalctlv2 project member delete](metalctlv2_project_member_delete.md) - deletes the member +* [metalctlv2 project member describe](metalctlv2_project_member_describe.md) - describes the member +* [metalctlv2 project member edit](metalctlv2_project_member_edit.md) - edit the member through an editor and update +* [metalctlv2 project member list](metalctlv2_project_member_list.md) - list all members +* [metalctlv2 project member update](metalctlv2_project_member_update.md) - updates the member diff --git a/docs/metalctlv2_project_member_apply.md b/docs/metalctlv2_project_member_apply.md new file mode 100644 index 0000000..39d11ee --- /dev/null +++ b/docs/metalctlv2_project_member_apply.md @@ -0,0 +1,46 @@ +## metalctlv2 project member apply + +applies one or more members from a given file + +``` +metalctlv2 project member apply [flags] +``` + +### Options + +``` + --bulk-output when used with --file (bulk operation): prints results at the end as a list. default is printing results intermediately during the operation, which causes single entities to be printed in a row. + -f, --file string filename of the create or update request in yaml format, or - for stdin. + + Example: + $ metalctlv2 member describe member-1 -o yaml > member.yaml + $ vi member.yaml + $ # either via stdin + $ cat member.yaml | metalctlv2 member apply -f - + $ # or via file + $ metalctlv2 member apply -f member.yaml + + the file can also contain multiple documents and perform a bulk operation. + + -h, --help help for apply + --skip-security-prompts skips security prompt for bulk operations + --timestamps when used with --file (bulk operation): prints timestamps in-between the operations +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template), wide is a table with more columns. (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2 project member](metalctlv2_project_member.md) - manage member entities + diff --git a/docs/metalctlv2_project_member_create.md b/docs/metalctlv2_project_member_create.md new file mode 100644 index 0000000..819c7d9 --- /dev/null +++ b/docs/metalctlv2_project_member_create.md @@ -0,0 +1,47 @@ +## metalctlv2 project member create + +creates the member + +``` +metalctlv2 project member create [flags] +``` + +### Options + +``` + --bulk-output when used with --file (bulk operation): prints results at the end as a list. default is printing results intermediately during the operation, which causes single entities to be printed in a row. + -f, --file string filename of the create or update request in yaml format, or - for stdin. + + Example: + $ metalctlv2 member describe member-1 -o yaml > member.yaml + $ vi member.yaml + $ # either via stdin + $ cat member.yaml | metalctlv2 member create -f - + $ # or via file + $ metalctlv2 member create -f member.yaml + + the file can also contain multiple documents and perform a bulk operation. + + -h, --help help for create + --project string the project for which to create a project member, defaults to project of the default project + --skip-security-prompts skips security prompt for bulk operations + --timestamps when used with --file (bulk operation): prints timestamps in-between the operations +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template), wide is a table with more columns. (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2 project member](metalctlv2_project_member.md) - manage member entities + diff --git a/docs/metalctlv2_project_member_delete.md b/docs/metalctlv2_project_member_delete.md index c7c2aae..049d2a9 100644 --- a/docs/metalctlv2_project_member_delete.md +++ b/docs/metalctlv2_project_member_delete.md @@ -1,16 +1,31 @@ ## metalctlv2 project member delete -remove member from a project +deletes the member ``` -metalctlv2 project member delete [flags] +metalctlv2 project member delete [flags] ``` ### Options ``` - -h, --help help for delete - -p, --project string the project in which to remove the member + --bulk-output when used with --file (bulk operation): prints results at the end as a list. default is printing results intermediately during the operation, which causes single entities to be printed in a row. + -f, --file string filename of the create or update request in yaml format, or - for stdin. + + Example: + $ metalctlv2 member describe member-1 -o yaml > member.yaml + $ vi member.yaml + $ # either via stdin + $ cat member.yaml | metalctlv2 member delete -f - + $ # or via file + $ metalctlv2 member delete -f member.yaml + + the file can also contain multiple documents and perform a bulk operation. + + -h, --help help for delete + --project string the project from which to delete a project member, defaults to project of the default project + --skip-security-prompts skips security prompt for bulk operations + --timestamps when used with --file (bulk operation): prints timestamps in-between the operations ``` ### Options inherited from parent commands @@ -28,5 +43,5 @@ metalctlv2 project member delete [flags] ### SEE ALSO -* [metalctlv2 project member](metalctlv2_project_member.md) - manage project members +* [metalctlv2 project member](metalctlv2_project_member.md) - manage member entities diff --git a/docs/metalctlv2_project_member_describe.md b/docs/metalctlv2_project_member_describe.md new file mode 100644 index 0000000..099b4f3 --- /dev/null +++ b/docs/metalctlv2_project_member_describe.md @@ -0,0 +1,32 @@ +## metalctlv2 project member describe + +describes the member + +``` +metalctlv2 project member describe [flags] +``` + +### Options + +``` + -h, --help help for describe + --project string the project to describe the project members of, defaults to project of the default project +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template), wide is a table with more columns. (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2 project member](metalctlv2_project_member.md) - manage member entities + diff --git a/docs/metalctlv2_project_member_edit.md b/docs/metalctlv2_project_member_edit.md new file mode 100644 index 0000000..cd2e095 --- /dev/null +++ b/docs/metalctlv2_project_member_edit.md @@ -0,0 +1,32 @@ +## metalctlv2 project member edit + +edit the member through an editor and update + +``` +metalctlv2 project member edit [flags] +``` + +### Options + +``` + -h, --help help for edit + --project string the project from which to edit a project member, defaults to project of the default project +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template), wide is a table with more columns. (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2 project member](metalctlv2_project_member.md) - manage member entities + diff --git a/docs/metalctlv2_project_member_list.md b/docs/metalctlv2_project_member_list.md index e1ce860..c54b928 100644 --- a/docs/metalctlv2_project_member_list.md +++ b/docs/metalctlv2_project_member_list.md @@ -1,6 +1,6 @@ ## metalctlv2 project member list -lists members of a project +list all members ``` metalctlv2 project member list [flags] @@ -10,7 +10,8 @@ metalctlv2 project member list [flags] ``` -h, --help help for list - -p, --project string the project of which to list the members + --project string the project to list the project members of, defaults to project of the default project + --role string the role of the member --sort-by strings sort by (comma separated) column(s), sort direction can be changed by appending :asc or :desc behind the column identifier. possible values: created|id|inherited|role ``` @@ -29,5 +30,5 @@ metalctlv2 project member list [flags] ### SEE ALSO -* [metalctlv2 project member](metalctlv2_project_member.md) - manage project members +* [metalctlv2 project member](metalctlv2_project_member.md) - manage member entities diff --git a/docs/metalctlv2_project_member_update.md b/docs/metalctlv2_project_member_update.md index 4f29aaf..033cb9a 100644 --- a/docs/metalctlv2_project_member_update.md +++ b/docs/metalctlv2_project_member_update.md @@ -1,17 +1,32 @@ ## metalctlv2 project member update -update member from a project +updates the member ``` -metalctlv2 project member update [flags] +metalctlv2 project member update [flags] ``` ### Options ``` - -h, --help help for update - -p, --project string the project in which to remove the member - --role string the role of the member + --bulk-output when used with --file (bulk operation): prints results at the end as a list. default is printing results intermediately during the operation, which causes single entities to be printed in a row. + -f, --file string filename of the create or update request in yaml format, or - for stdin. + + Example: + $ metalctlv2 member describe member-1 -o yaml > member.yaml + $ vi member.yaml + $ # either via stdin + $ cat member.yaml | metalctlv2 member update -f - + $ # or via file + $ metalctlv2 member update -f member.yaml + + the file can also contain multiple documents and perform a bulk operation. + + -h, --help help for update + --project string the project for which to update a project member, defaults to project of the default project + --role string the role of the member + --skip-security-prompts skips security prompt for bulk operations + --timestamps when used with --file (bulk operation): prints timestamps in-between the operations ``` ### Options inherited from parent commands @@ -29,5 +44,5 @@ metalctlv2 project member update [flags] ### SEE ALSO -* [metalctlv2 project member](metalctlv2_project_member.md) - manage project members +* [metalctlv2 project member](metalctlv2_project_member.md) - manage member entities diff --git a/docs/metalctlv2_tenant.md b/docs/metalctlv2_tenant.md index 9b29e1b..37fcd73 100644 --- a/docs/metalctlv2_tenant.md +++ b/docs/metalctlv2_tenant.md @@ -36,6 +36,6 @@ manage api tenants * [metalctlv2 tenant invite](metalctlv2_tenant_invite.md) - manage tenant invites * [metalctlv2 tenant join](metalctlv2_tenant_join.md) - join a tenant of someone who shared an invite secret with you * [metalctlv2 tenant list](metalctlv2_tenant_list.md) - list all tenants -* [metalctlv2 tenant member](metalctlv2_tenant_member.md) - manage tenant members +* [metalctlv2 tenant member](metalctlv2_tenant_member.md) - manage member entities * [metalctlv2 tenant update](metalctlv2_tenant_update.md) - updates the tenant diff --git a/docs/metalctlv2_tenant_member.md b/docs/metalctlv2_tenant_member.md index 7db51c2..51ab871 100644 --- a/docs/metalctlv2_tenant_member.md +++ b/docs/metalctlv2_tenant_member.md @@ -1,6 +1,10 @@ ## metalctlv2 tenant member -manage tenant members +manage member entities + +### Synopsis + +manage api tenant members ### Options @@ -24,7 +28,11 @@ manage tenant members ### SEE ALSO * [metalctlv2 tenant](metalctlv2_tenant.md) - manage tenant entities -* [metalctlv2 tenant member list](metalctlv2_tenant_member_list.md) - lists members of a tenant -* [metalctlv2 tenant member remove](metalctlv2_tenant_member_remove.md) - remove member from a tenant -* [metalctlv2 tenant member update](metalctlv2_tenant_member_update.md) - update member from a tenant +* [metalctlv2 tenant member apply](metalctlv2_tenant_member_apply.md) - applies one or more members from a given file +* [metalctlv2 tenant member create](metalctlv2_tenant_member_create.md) - creates the member +* [metalctlv2 tenant member delete](metalctlv2_tenant_member_delete.md) - deletes the member +* [metalctlv2 tenant member describe](metalctlv2_tenant_member_describe.md) - describes the member +* [metalctlv2 tenant member edit](metalctlv2_tenant_member_edit.md) - edit the member through an editor and update +* [metalctlv2 tenant member list](metalctlv2_tenant_member_list.md) - list all members +* [metalctlv2 tenant member update](metalctlv2_tenant_member_update.md) - updates the member diff --git a/docs/metalctlv2_tenant_member_apply.md b/docs/metalctlv2_tenant_member_apply.md new file mode 100644 index 0000000..2c442dd --- /dev/null +++ b/docs/metalctlv2_tenant_member_apply.md @@ -0,0 +1,46 @@ +## metalctlv2 tenant member apply + +applies one or more members from a given file + +``` +metalctlv2 tenant member apply [flags] +``` + +### Options + +``` + --bulk-output when used with --file (bulk operation): prints results at the end as a list. default is printing results intermediately during the operation, which causes single entities to be printed in a row. + -f, --file string filename of the create or update request in yaml format, or - for stdin. + + Example: + $ metalctlv2 member describe member-1 -o yaml > member.yaml + $ vi member.yaml + $ # either via stdin + $ cat member.yaml | metalctlv2 member apply -f - + $ # or via file + $ metalctlv2 member apply -f member.yaml + + the file can also contain multiple documents and perform a bulk operation. + + -h, --help help for apply + --skip-security-prompts skips security prompt for bulk operations + --timestamps when used with --file (bulk operation): prints timestamps in-between the operations +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template), wide is a table with more columns. (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2 tenant member](metalctlv2_tenant_member.md) - manage member entities + diff --git a/docs/metalctlv2_tenant_member_create.md b/docs/metalctlv2_tenant_member_create.md new file mode 100644 index 0000000..5403bbe --- /dev/null +++ b/docs/metalctlv2_tenant_member_create.md @@ -0,0 +1,47 @@ +## metalctlv2 tenant member create + +creates the member + +``` +metalctlv2 tenant member create [flags] +``` + +### Options + +``` + --bulk-output when used with --file (bulk operation): prints results at the end as a list. default is printing results intermediately during the operation, which causes single entities to be printed in a row. + -f, --file string filename of the create or update request in yaml format, or - for stdin. + + Example: + $ metalctlv2 member describe member-1 -o yaml > member.yaml + $ vi member.yaml + $ # either via stdin + $ cat member.yaml | metalctlv2 member create -f - + $ # or via file + $ metalctlv2 member create -f member.yaml + + the file can also contain multiple documents and perform a bulk operation. + + -h, --help help for create + --skip-security-prompts skips security prompt for bulk operations + --tenant string the tenant for which to create a tenant member, defaults to tenant of the default project + --timestamps when used with --file (bulk operation): prints timestamps in-between the operations +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template), wide is a table with more columns. (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2 tenant member](metalctlv2_tenant_member.md) - manage member entities + diff --git a/docs/metalctlv2_tenant_member_delete.md b/docs/metalctlv2_tenant_member_delete.md new file mode 100644 index 0000000..72222dd --- /dev/null +++ b/docs/metalctlv2_tenant_member_delete.md @@ -0,0 +1,47 @@ +## metalctlv2 tenant member delete + +deletes the member + +``` +metalctlv2 tenant member delete [flags] +``` + +### Options + +``` + --bulk-output when used with --file (bulk operation): prints results at the end as a list. default is printing results intermediately during the operation, which causes single entities to be printed in a row. + -f, --file string filename of the create or update request in yaml format, or - for stdin. + + Example: + $ metalctlv2 member describe member-1 -o yaml > member.yaml + $ vi member.yaml + $ # either via stdin + $ cat member.yaml | metalctlv2 member delete -f - + $ # or via file + $ metalctlv2 member delete -f member.yaml + + the file can also contain multiple documents and perform a bulk operation. + + -h, --help help for delete + --skip-security-prompts skips security prompt for bulk operations + --tenant string the tenant from which to delete a tenant member, defaults to tenant of the default project + --timestamps when used with --file (bulk operation): prints timestamps in-between the operations +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template), wide is a table with more columns. (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2 tenant member](metalctlv2_tenant_member.md) - manage member entities + diff --git a/docs/metalctlv2_tenant_member_remove.md b/docs/metalctlv2_tenant_member_describe.md similarity index 76% rename from docs/metalctlv2_tenant_member_remove.md rename to docs/metalctlv2_tenant_member_describe.md index 7c37c64..9e553c4 100644 --- a/docs/metalctlv2_tenant_member_remove.md +++ b/docs/metalctlv2_tenant_member_describe.md @@ -1,16 +1,16 @@ -## metalctlv2 tenant member remove +## metalctlv2 tenant member describe -remove member from a tenant +describes the member ``` -metalctlv2 tenant member remove [flags] +metalctlv2 tenant member describe [flags] ``` ### Options ``` - -h, --help help for remove - --tenant string the tenant in which to remove the member + -h, --help help for describe + --tenant string the tenant to describe the tenant members of, defaults to tenant of the default project ``` ### Options inherited from parent commands @@ -28,5 +28,5 @@ metalctlv2 tenant member remove [flags] ### SEE ALSO -* [metalctlv2 tenant member](metalctlv2_tenant_member.md) - manage tenant members +* [metalctlv2 tenant member](metalctlv2_tenant_member.md) - manage member entities diff --git a/docs/metalctlv2_tenant_member_edit.md b/docs/metalctlv2_tenant_member_edit.md new file mode 100644 index 0000000..a1cc671 --- /dev/null +++ b/docs/metalctlv2_tenant_member_edit.md @@ -0,0 +1,32 @@ +## metalctlv2 tenant member edit + +edit the member through an editor and update + +``` +metalctlv2 tenant member edit [flags] +``` + +### Options + +``` + -h, --help help for edit + --tenant string the tenant from which to edit a tenant member, defaults to tenant of the default project +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template), wide is a table with more columns. (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2 tenant member](metalctlv2_tenant_member.md) - manage member entities + diff --git a/docs/metalctlv2_tenant_member_list.md b/docs/metalctlv2_tenant_member_list.md index 6037691..8bb93b9 100644 --- a/docs/metalctlv2_tenant_member_list.md +++ b/docs/metalctlv2_tenant_member_list.md @@ -1,6 +1,6 @@ ## metalctlv2 tenant member list -lists members of a tenant +list all members ``` metalctlv2 tenant member list [flags] @@ -10,8 +10,9 @@ metalctlv2 tenant member list [flags] ``` -h, --help help for list + --role string the role of the member --sort-by strings sort by (comma separated) column(s), sort direction can be changed by appending :asc or :desc behind the column identifier. possible values: created|id|role - --tenant string the tenant in which to remove the member + --tenant string the tenant to list the tenant members of, defaults to tenant of the default project ``` ### Options inherited from parent commands @@ -29,5 +30,5 @@ metalctlv2 tenant member list [flags] ### SEE ALSO -* [metalctlv2 tenant member](metalctlv2_tenant_member.md) - manage tenant members +* [metalctlv2 tenant member](metalctlv2_tenant_member.md) - manage member entities diff --git a/docs/metalctlv2_tenant_member_update.md b/docs/metalctlv2_tenant_member_update.md index 3eeae30..cdc8083 100644 --- a/docs/metalctlv2_tenant_member_update.md +++ b/docs/metalctlv2_tenant_member_update.md @@ -1,17 +1,32 @@ ## metalctlv2 tenant member update -update member from a tenant +updates the member ``` -metalctlv2 tenant member update [flags] +metalctlv2 tenant member update [flags] ``` ### Options ``` - -h, --help help for update - --role string the role of the member - --tenant string the tenant in which to remove the member + --bulk-output when used with --file (bulk operation): prints results at the end as a list. default is printing results intermediately during the operation, which causes single entities to be printed in a row. + -f, --file string filename of the create or update request in yaml format, or - for stdin. + + Example: + $ metalctlv2 member describe member-1 -o yaml > member.yaml + $ vi member.yaml + $ # either via stdin + $ cat member.yaml | metalctlv2 member update -f - + $ # or via file + $ metalctlv2 member update -f member.yaml + + the file can also contain multiple documents and perform a bulk operation. + + -h, --help help for update + --role string the role of the member + --skip-security-prompts skips security prompt for bulk operations + --tenant string the tenant for which to update a tenant member, defaults to tenant of the default project + --timestamps when used with --file (bulk operation): prints timestamps in-between the operations ``` ### Options inherited from parent commands @@ -29,5 +44,5 @@ metalctlv2 tenant member update [flags] ### SEE ALSO -* [metalctlv2 tenant member](metalctlv2_tenant_member.md) - manage tenant members +* [metalctlv2 tenant member](metalctlv2_tenant_member.md) - manage member entities diff --git a/go.mod b/go.mod index 12fadf8..24dc1a1 100644 --- a/go.mod +++ b/go.mod @@ -9,8 +9,8 @@ require ( github.com/fatih/color v1.19.0 github.com/google/go-cmp v0.7.0 github.com/google/uuid v1.6.0 - github.com/metal-stack/api v0.5.5 - github.com/metal-stack/metal-console v0.7.7-0.20260909095012-75ec3d1e3e1a + github.com/metal-stack/api v0.7.1-0.20260929135827-11f6a1ed8099 + github.com/metal-stack/metal-console v0.8.2 github.com/metal-stack/metal-lib v0.26.3 github.com/metal-stack/v v1.0.3 github.com/spf13/afero v1.15.0 @@ -24,8 +24,9 @@ require ( require ( buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect - buf.build/go/protovalidate v1.3.0 // indirect + buf.build/go/protovalidate v1.4.0 // indirect buf.build/go/protoyaml v0.7.0 // indirect + cel.dev/cel-go v0.32.0 // indirect cel.dev/expr v0.25.3 // indirect filippo.io/edwards25519 v1.2.0 // indirect github.com/akutz/memconn v0.1.0 // indirect @@ -53,7 +54,6 @@ require ( github.com/golang-jwt/jwt/v5 v5.3.1 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/google/btree v1.1.3 // indirect - github.com/google/cel-go v0.31.0 // indirect github.com/hdevalence/ed25519consensus v0.2.0 // indirect github.com/huin/goupnp v1.3.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -103,8 +103,8 @@ require ( golang.org/x/time v0.15.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wireguard/windows v1.0.1 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260825221802-da73d73af1c5 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260825221802-da73d73af1c5 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260831171406-18b4a7587f8a // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260831171406-18b4a7587f8a // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gvisor.dev/gvisor v0.0.0-20260224225140-573d5e7127a8 // indirect diff --git a/go.sum b/go.sum index 09c16e9..5c74b21 100644 --- a/go.sum +++ b/go.sum @@ -2,10 +2,12 @@ 9fans.net/go v0.0.8-0.20250307142834-96bdba94b63f/go.mod h1:hHyrZRryGqVdqrknjq5OWDLGCTJ2NeEvtrpR96mjraM= buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= -buf.build/go/protovalidate v1.3.0 h1:8ITcnZGkAHx6TyhZvro+iET/AyqU8gEWQJK2WsT62ms= -buf.build/go/protovalidate v1.3.0/go.mod h1:82s5g+rFRj1CZPiLv6OTA31jBu2fpq7mLXHwa9mZfEs= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= buf.build/go/protoyaml v0.7.0 h1:z4oVoFicbpPefhT7WAykxUdfp0yEQlhMQ2mCZOY5V38= buf.build/go/protoyaml v0.7.0/go.mod h1:+a0cavd0uMvirb87xdu2ZMMmjlIQoiH/N2Ich5MGSQ0= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= connectrpc.com/connect v1.20.0 h1:6TNDAB+WeNd2uolWNlYczB5E0KNNaVMNUEx8JEUsPmQ= @@ -139,8 +141,6 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/cel-go v0.31.0 h1:H0bhpFTqOvmHrBGrWKp7ZlhBm5Hh8PYUEXnwxT1LL7A= -github.com/google/cel-go v0.31.0/go.mod h1:X0bD6iVNR8pkROSOoHVdgTkzmRcosof7WQqCD6wcMc8= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-tpm v0.9.4 h1:awZRf9FwOeTunQmHoDYSHJps3ie6f1UlhS1fOdPEt1I= @@ -191,10 +191,10 @@ github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE= github.com/mdlayher/socket v0.6.1 h1:M7uj2NtuujUY4mYr1C57NmfNiRHbkKpnBxO856lsc3A= github.com/mdlayher/socket v0.6.1/go.mod h1:+/SGtqc9V+5dAuRgQsU0fGBI+oRDiW7O2Obx10OIWfg= -github.com/metal-stack/api v0.5.5 h1:AzPjTKi8Y4XNiJaEcRDCAjuOmIaLuuOEhKXVWliTtWc= -github.com/metal-stack/api v0.5.5/go.mod h1:U8c+awSMxXaRJjzWpo1IMcvlNxOi1ewu/fk1BzDM7hI= -github.com/metal-stack/metal-console v0.7.7-0.20260909095012-75ec3d1e3e1a h1:zFB1a0PMyzgBGtJU3HID75pEsD6l694BViuxJxKDdLg= -github.com/metal-stack/metal-console v0.7.7-0.20260909095012-75ec3d1e3e1a/go.mod h1:ZkselOaxF/iELFL9Tku3maXgpX4DM0CHjoG7CGuky6E= +github.com/metal-stack/api v0.7.1-0.20260929135827-11f6a1ed8099 h1:suq0OfxZkzAHArsXhPfgyXnYd4w/Sb3GRI8n3OKKmTU= +github.com/metal-stack/api v0.7.1-0.20260929135827-11f6a1ed8099/go.mod h1:rWdj0dTAg2w/WuQhIZ+JVp9b68v6VVCm1k7sLx4U+PQ= +github.com/metal-stack/metal-console v0.8.2 h1:Y86penzEROJKcLica1PeE2ddtr9G0/u16HiCAYXaxxU= +github.com/metal-stack/metal-console v0.8.2/go.mod h1:ZkselOaxF/iELFL9Tku3maXgpX4DM0CHjoG7CGuky6E= github.com/metal-stack/metal-lib v0.26.3 h1:K5gLoD65m6p3l6qCPrfavIdvNdWfmF2QdXrvU2URaZs= github.com/metal-stack/metal-lib v0.26.3/go.mod h1:cNXjPBs8SFnjqfBobuSbm5mDk6E/jS8PVeIOrV/7POE= github.com/metal-stack/v v1.0.3 h1:Sh2oBlnxrCUD+mVpzfC8HiqL045YWkxs0gpTvkjppqs= @@ -335,17 +335,16 @@ golang.zx2c4.com/wireguard v0.0.0-20260522210424-ecfc5a8d5446 h1:cqHQ3AycTHvM2R7 golang.zx2c4.com/wireguard v0.0.0-20260522210424-ecfc5a8d5446/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw= golang.zx2c4.com/wireguard/windows v1.0.1 h1:eOxiDVbywPC+ZQqvdCK7x+ZwWXKbYv50TtH8ysFIbw8= golang.zx2c4.com/wireguard/windows v1.0.1/go.mod h1:+fbT3FFdX4zzYDLwJh5+HPEcNN/3HyNdzhNSVsQM+zs= -google.golang.org/genproto/googleapis/api v0.0.0-20260825221802-da73d73af1c5 h1:izFU9hz7aeLI/Mi1J0991ae+xcwRLr7hTqWnB/9aIIU= -google.golang.org/genproto/googleapis/api v0.0.0-20260825221802-da73d73af1c5/go.mod h1:3LhxRw4YYkf+ylAfgaY9JlVLFKhokkCV8duhLLe7+t0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260825221802-da73d73af1c5 h1:1VUiZAXyC+zmiFYi+WLtBzr68Cj8wOofHjjrA/kkizc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260825221802-da73d73af1c5/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/genproto/googleapis/api v0.0.0-20260831171406-18b4a7587f8a h1:i3TAXhpKc7TUP1VAPiBBrv45kamjoizCC3rOC0cAbOs= +google.golang.org/genproto/googleapis/api v0.0.0-20260831171406-18b4a7587f8a/go.mod h1:CvYJHpbzPlT0fb/PsgtAamdwru/GVxUsomFdXTpOTI8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260831171406-18b4a7587f8a h1:3Dnd1cDaZlB68lziofO+bJXpjOy8UfRv8Unt+yH8tQ4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260831171406-18b4a7587f8a/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU= google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/tests/e2e/admin/image_test.go b/tests/e2e/admin/image_test.go index f8e7a0f..fc84757 100644 --- a/tests/e2e/admin/image_test.go +++ b/tests/e2e/admin/image_test.go @@ -1,6 +1,7 @@ package admin_e2e import ( + "fmt" "testing" "connectrpc.com/connect" @@ -246,9 +247,83 @@ func Test_AdminImageCmd_Usage(t *testing.T) { ubuntu-24.04 Ubuntu 24.04 Ubuntu 24.04 LTS machine supported 2 `), WantWideTable: new(` - ID NAME DESCRIPTION FEATURES EXPIRATION STATUS USAGE - ubuntu-24.04 Ubuntu 24.04 Ubuntu 24.04 LTS machine supported machine-a - machine-b + ID NAME DESCRIPTION FEATURES EXPIRATION STATUS USAGE + ubuntu-24.04 Ubuntu 24.04 Ubuntu 24.04 LTS machine supported machine-a + machine-b + `), + }, + } + for _, tt := range tests { + tt.TestCmd(t) + } +} + +func Test_AdminImageCmd_Apply(t *testing.T) { + tests := []*e2e.Test[adminv2.ImageServiceUpdateResponse, *apiv2.Image]{ + { + Name: "apply", + CmdArgs: append([]string{"admin", "image", "apply"}, e2e.AppendFromFileCommonArgs()...), + NewRootCmd: e2erootcmd.NewRootCmd(t, + &e2erootcmd.TestConfig{ + FsMocks: func(fs *afero.Afero) { + require.NoError(t, fs.WriteFile(e2e.InputFilePath, e2e.MustMarshal(t, testresources.Image1()), 0755)) + }, + ClientCalls: []client.ClientCall{ + { + WantRequest: &adminv2.ImageServiceCreateRequest{ + Image: testresources.Image1(), + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&adminv2.ImageServiceCreateResponse{ + Image: testresources.Image1(), + }) + }, + }, + }, + }), + WantTable: new(` + ID NAME DESCRIPTION FEATURES EXPIRATION STATUS + ubuntu-24.04 Ubuntu 24.04 Ubuntu 24.04 LTS machine supported + `), + }, + { + Name: "apply already exists", + CmdArgs: append([]string{"admin", "image", "apply"}, e2e.AppendFromFileCommonArgs()...), + NewRootCmd: e2erootcmd.NewRootCmd(t, + &e2erootcmd.TestConfig{ + FsMocks: func(fs *afero.Afero) { + require.NoError(t, fs.WriteFile(e2e.InputFilePath, e2e.MustMarshal(t, testresources.Image1()), 0755)) + }, + ClientCalls: []client.ClientCall{ + { + WantRequest: &adminv2.ImageServiceCreateRequest{ + Image: testresources.Image1(), + }, + WantError: connect.NewError(connect.CodeAlreadyExists, fmt.Errorf("already exists")), + }, + { + WantRequest: &adminv2.ImageServiceUpdateRequest{ + Id: testresources.Image1().Id, + Url: new(testresources.Image1().Url), + Name: testresources.Image1().Name, + Description: testresources.Image1().Description, + Features: testresources.Image1().Features, + Classification: testresources.Image1().Classification, + UpdateMeta: &apiv2.UpdateMeta{ + LockingStrategy: apiv2.OptimisticLockingStrategy_OPTIMISTIC_LOCKING_STRATEGY_SERVER, + }, + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&adminv2.ImageServiceUpdateResponse{ + Image: testresources.Image1(), + }) + }, + }, + }, + }), + WantTable: new(` + ID NAME DESCRIPTION FEATURES EXPIRATION STATUS + ubuntu-24.04 Ubuntu 24.04 Ubuntu 24.04 LTS machine supported `), }, } diff --git a/tests/e2e/admin/partition_test.go b/tests/e2e/admin/partition_test.go index 2daac45..b34dfca 100644 --- a/tests/e2e/admin/partition_test.go +++ b/tests/e2e/admin/partition_test.go @@ -1,6 +1,7 @@ package admin_e2e import ( + "fmt" "testing" "connectrpc.com/connect" @@ -11,6 +12,8 @@ import ( e2erootcmd "github.com/metal-stack/cli/testing/e2e" "github.com/metal-stack/cli/tests/e2e/testresources" e2e "github.com/metal-stack/metal-lib/pkg/genericcli/e2e" + "github.com/spf13/afero" + "github.com/stretchr/testify/require" ) func Test_AdminPartitionCmd_List(t *testing.T) { @@ -188,3 +191,80 @@ partition-1 size-1 tt.TestCmd(t) } } + +func Test_AdminPartitionCmd_Apply(t *testing.T) { + tests := []*e2e.Test[adminv2.PartitionServiceUpdateResponse, *apiv2.Partition]{ + { + Name: "apply", + CmdArgs: append([]string{"admin", "partition", "apply"}, e2e.AppendFromFileCommonArgs()...), + NewRootCmd: e2erootcmd.NewRootCmd(t, + &e2erootcmd.TestConfig{ + FsMocks: func(fs *afero.Afero) { + require.NoError(t, fs.WriteFile(e2e.InputFilePath, e2e.MustMarshal(t, testresources.Partition1()), 0755)) + }, + ClientCalls: []client.ClientCall{ + { + WantRequest: &adminv2.PartitionServiceCreateRequest{ + Partition: testresources.Partition1(), + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&adminv2.PartitionServiceCreateResponse{ + Partition: testresources.Partition1(), + }) + }, + }, + }, + }), + WantTable: new(` + ID DESCRIPTION + partition-1 partition 1 + `), + }, + { + Name: "apply already exists", + CmdArgs: append([]string{"admin", "partition", "apply"}, e2e.AppendFromFileCommonArgs()...), + NewRootCmd: e2erootcmd.NewRootCmd(t, + &e2erootcmd.TestConfig{ + FsMocks: func(fs *afero.Afero) { + require.NoError(t, fs.WriteFile(e2e.InputFilePath, e2e.MustMarshal(t, testresources.Partition1()), 0755)) + }, + ClientCalls: []client.ClientCall{ + { + WantRequest: &adminv2.PartitionServiceCreateRequest{ + Partition: testresources.Partition1(), + }, + WantError: connect.NewError(connect.CodeAlreadyExists, fmt.Errorf("already exists")), + }, + { + WantRequest: &adminv2.PartitionServiceUpdateRequest{ + Id: testresources.Partition1().Id, + Description: new(testresources.Partition1().Description), + BootConfiguration: testresources.Partition1().BootConfiguration, + MgmtServiceAddresses: testresources.Partition1().MgmtServiceAddresses, + UpdateMeta: &apiv2.UpdateMeta{ + LockingStrategy: apiv2.OptimisticLockingStrategy_OPTIMISTIC_LOCKING_STRATEGY_SERVER, + }, + Labels: &apiv2.UpdateLabels{ + Strategy: &apiv2.UpdateLabels_Replace{ + Replace: testresources.Partition1().Meta.Labels, + }, + }, + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&adminv2.PartitionServiceUpdateResponse{ + Partition: testresources.Partition1(), + }) + }, + }, + }, + }), + WantTable: new(` + ID DESCRIPTION + partition-1 partition 1 + `), + }, + } + for _, tt := range tests { + tt.TestCmd(t) + } +} diff --git a/tests/e2e/admin/size_test.go b/tests/e2e/admin/size_test.go index 1caaefe..b9ae447 100644 --- a/tests/e2e/admin/size_test.go +++ b/tests/e2e/admin/size_test.go @@ -1,6 +1,7 @@ package admin_e2e import ( + "fmt" "testing" "connectrpc.com/connect" @@ -155,6 +156,83 @@ func Test_AdminSizeCmd_Create(t *testing.T) { } } +func Test_AdminSizeCmd_Apply(t *testing.T) { + tests := []*e2e.Test[adminv2.SizeServiceUpdateResponse, *apiv2.Size]{ + { + Name: "apply", + CmdArgs: append([]string{"admin", "size", "apply"}, e2e.AppendFromFileCommonArgs()...), + NewRootCmd: e2erootcmd.NewRootCmd(t, + &e2erootcmd.TestConfig{ + FsMocks: func(fs *afero.Afero) { + require.NoError(t, fs.WriteFile(e2e.InputFilePath, e2e.MustMarshal(t, testresources.Size1()), 0755)) + }, + ClientCalls: []client.ClientCall{ + { + WantRequest: &adminv2.SizeServiceCreateRequest{ + Size: testresources.Size1(), + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&adminv2.SizeServiceCreateResponse{ + Size: testresources.Size1(), + }) + }, + }, + }, + }), + WantTable: new(` + ID NAME DESCRIPTION CPU RANGE MEMORY RANGE STORAGE RANGE GPU RANGE + v1-medium-x86 v1-medium-x86 Virtual size for mini-lab 4 - 4 500 MB - 4.0 GB 1.0 GB - 100 GB + `), + }, + { + Name: "apply already exists", + CmdArgs: append([]string{"admin", "size", "apply"}, e2e.AppendFromFileCommonArgs()...), + NewRootCmd: e2erootcmd.NewRootCmd(t, + &e2erootcmd.TestConfig{ + FsMocks: func(fs *afero.Afero) { + require.NoError(t, fs.WriteFile(e2e.InputFilePath, e2e.MustMarshal(t, testresources.Size1()), 0755)) + }, + ClientCalls: []client.ClientCall{ + { + WantRequest: &adminv2.SizeServiceCreateRequest{ + Size: testresources.Size1(), + }, + WantError: connect.NewError(connect.CodeAlreadyExists, fmt.Errorf("already exists")), + }, + { + WantRequest: &adminv2.SizeServiceUpdateRequest{ + Id: testresources.Size1().Id, + Name: testresources.Size1().Name, + Description: testresources.Size1().Description, + UpdateMeta: &apiv2.UpdateMeta{ + LockingStrategy: apiv2.OptimisticLockingStrategy_OPTIMISTIC_LOCKING_STRATEGY_SERVER, + }, + Labels: &apiv2.UpdateLabels{ + Strategy: &apiv2.UpdateLabels_Replace{ + Replace: testresources.Size1().Meta.Labels, + }, + }, + Constraints: testresources.Size1().Constraints, + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&adminv2.SizeServiceUpdateResponse{ + Size: testresources.Size1(), + }) + }, + }, + }, + }), + WantTable: new(` + ID NAME DESCRIPTION CPU RANGE MEMORY RANGE STORAGE RANGE GPU RANGE + v1-medium-x86 v1-medium-x86 Virtual size for mini-lab 4 - 4 500 MB - 4.0 GB 1.0 GB - 100 GB + `), + }, + } + for _, tt := range tests { + tt.TestCmd(t) + } +} + func Test_AdminSizeCmd_Update(t *testing.T) { tests := []*e2e.Test[adminv2.SizeServiceUpdateResponse, *apiv2.Size]{ { diff --git a/tests/e2e/api/project_test.go b/tests/e2e/api/project_test.go index 1b2864b..3376cba 100644 --- a/tests/e2e/api/project_test.go +++ b/tests/e2e/api/project_test.go @@ -243,6 +243,9 @@ func Test_ProjectCmd_Update(t *testing.T) { Project: testresources.Project1().Uuid, Name: new(testresources.Project1().Name), Description: new(testresources.Project1().Description), + UpdateMeta: &apiv2.UpdateMeta{ + LockingStrategy: apiv2.OptimisticLockingStrategy_OPTIMISTIC_LOCKING_STRATEGY_SERVER, + }, }, WantResponse: func() connect.AnyResponse { return connect.NewResponse(&apiv2.ProjectServiceUpdateResponse{ @@ -365,6 +368,9 @@ func Test_ProjectCmd_Apply(t *testing.T) { Project: testresources.Project1().Uuid, Description: &testresources.Project1().Description, Name: &testresources.Project1().Name, + UpdateMeta: &apiv2.UpdateMeta{ + LockingStrategy: apiv2.OptimisticLockingStrategy_OPTIMISTIC_LOCKING_STRATEGY_SERVER, + }, }, WantResponse: func() connect.AnyResponse { return connect.NewResponse(&apiv2.ProjectServiceUpdateResponse{ @@ -548,7 +554,7 @@ func Test_ProjectCmd_ListMembers(t *testing.T) { return connect.NewResponse(&apiv2.ProjectServiceGetResponse{ Project: testresources.Project1(), ProjectMembers: []*apiv2.ProjectMember{ - testresources.Project1Members(), testresources.Project2Members(), + testresources.Project1Member(), testresources.Project2Member(), }, }) }, @@ -565,7 +571,7 @@ func Test_ProjectCmd_ListMembers(t *testing.T) { 16d6e8ba-f574-494f-8d5e-74f6cb2d8db0 PROJECT_ROLE_OWNER false now 40c0da4b-9eb9-4371-91aa-1ae62193fa54 PROJECT_ROLE_EDITOR true now `), - Template: new("{{ .id }} {{ .role }}"), + Template: new("{{ .project_member.id }} {{ .project_member.role }}"), WantTemplate: new(` 16d6e8ba-f574-494f-8d5e-74f6cb2d8db0 1 40c0da4b-9eb9-4371-91aa-1ae62193fa54 2 @@ -587,21 +593,26 @@ func Test_ProjectCmd_DeleteMember(t *testing.T) { tests := []*e2e.Test[apiv2.ProjectServiceRemoveMemberResponse, string]{ { Name: "delete project member", - CmdArgs: []string{"project", "member", "delete", testresources.Project1Members().Id, "--project", testresources.Project1().Uuid}, + CmdArgs: []string{"project", "member", "delete", testresources.Project1Member().Id, "--project", testresources.Project1().Uuid}, NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ ClientCalls: []client.ClientCall{ { WantRequest: &apiv2.ProjectServiceRemoveMemberRequest{ Project: testresources.Project1().Uuid, - Member: testresources.Project1Members().Id, + Member: testresources.Project1Member().Id, }, WantResponse: func() connect.AnyResponse { - return connect.NewResponse(&apiv2.ProjectServiceRemoveMemberResponse{}) + return connect.NewResponse(&apiv2.ProjectServiceRemoveMemberResponse{ + ProjectMember: testresources.Project1Member(), + }) }, }, }, }), - WantDefault: new(fmt.Sprintf("✔ successfully removed member \"%s\"", testresources.Project1Members().Id)), + WantTable: new(` + ID ROLE INHERITED SINCE + 16d6e8ba-f574-494f-8d5e-74f6cb2d8db0 PROJECT_ROLE_OWNER false now + `), }, } for _, tt := range tests { @@ -613,24 +624,23 @@ func Test_ProjectCmd_UpdateMember(t *testing.T) { tests := []*e2e.Test[apiv2.ProjectServiceUpdateMemberResponse, *apiv2.ProjectMember]{ { Name: "update project member", - CmdArgs: []string{"project", "member", "update", testresources.Project1Members().Id, "--project", testresources.Project1().Uuid, "--role", testresources.Project1Members().Role.String()}, + CmdArgs: []string{"project", "member", "update", testresources.Project1Member().Id, "--project", testresources.Project1().Uuid, "--role", testresources.Project1Member().Role.String()}, NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ ClientCalls: []client.ClientCall{ { WantRequest: &apiv2.ProjectServiceUpdateMemberRequest{ Project: testresources.Project1().Uuid, - Member: testresources.Project1Members().Id, - Role: testresources.Project1Members().Role, + Member: testresources.Project1Member().Id, + Role: testresources.Project1Member().Role, }, WantResponse: func() connect.AnyResponse { return connect.NewResponse(&apiv2.ProjectServiceUpdateMemberResponse{ - ProjectMember: testresources.Project1Members(), + ProjectMember: testresources.Project1Member(), }) }, }, }, }), - WantProtoObject: testresources.Project1Members(), WantTable: new(` ID ROLE INHERITED SINCE 16d6e8ba-f574-494f-8d5e-74f6cb2d8db0 PROJECT_ROLE_OWNER false now diff --git a/tests/e2e/api/tenant_test.go b/tests/e2e/api/tenant_test.go index d806fe0..81acee3 100644 --- a/tests/e2e/api/tenant_test.go +++ b/tests/e2e/api/tenant_test.go @@ -8,6 +8,7 @@ import ( "github.com/dustin/go-humanize" "github.com/metal-stack/api/go/client" apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + cliv2 "github.com/metal-stack/api/go/metalstack/cli/v2" e2erootcmd "github.com/metal-stack/cli/testing/e2e" "github.com/metal-stack/cli/tests/e2e/testresources" "github.com/metal-stack/metal-lib/pkg/genericcli/e2e" @@ -143,7 +144,7 @@ func Test_TenantCmd_Create(t *testing.T) { Name: testresources.Tenant1().Name, Description: &testresources.Tenant1().Description, Email: &testresources.Tenant1().Email, - AvatarUrl: new(""), + AvatarUrl: nil, }, WantResponse: func() connect.AnyResponse { return connect.NewResponse(&apiv2.TenantServiceCreateResponse{ @@ -170,7 +171,7 @@ func Test_TenantCmd_Create(t *testing.T) { WantRequest: &apiv2.TenantServiceCreateRequest{ Name: testresources.Tenant1().Name, Description: &testresources.Tenant1().Description, - AvatarUrl: new(""), + AvatarUrl: nil, Email: &testresources.Tenant1().Email, }, WantResponse: func() connect.AnyResponse { @@ -184,7 +185,7 @@ func Test_TenantCmd_Create(t *testing.T) { Name: testresources.Tenant2().Name, Description: &testresources.Tenant2().Description, Email: &testresources.Tenant2().Email, - AvatarUrl: new(""), + AvatarUrl: nil, }, WantResponse: func() connect.AnyResponse { return connect.NewResponse(&apiv2.TenantServiceCreateResponse{ @@ -295,6 +296,9 @@ func Test_TenantCmd_Update(t *testing.T) { Name: new(testresources.Tenant1().Name), Email: new(testresources.Tenant1().Email), Description: new(testresources.Tenant1().Description), + UpdateMeta: &apiv2.UpdateMeta{ + LockingStrategy: apiv2.OptimisticLockingStrategy_OPTIMISTIC_LOCKING_STRATEGY_SERVER, + }, }, WantResponse: func() connect.AnyResponse { return connect.NewResponse(&apiv2.TenantServiceUpdateResponse{ @@ -331,7 +335,7 @@ func Test_TenantCmd_Apply(t *testing.T) { Email: new(testresources.Tenant1().Email), Description: new(testresources.Tenant1().Description), Name: testresources.Tenant1().Name, - AvatarUrl: new(""), + AvatarUrl: nil, }, WantResponse: func() connect.AnyResponse { return connect.NewResponse(&apiv2.TenantServiceCreateResponse{ @@ -361,7 +365,7 @@ func Test_TenantCmd_Apply(t *testing.T) { Email: new(testresources.Tenant1().Email), Description: new(testresources.Tenant1().Description), Name: testresources.Tenant1().Name, - AvatarUrl: new(""), + AvatarUrl: nil, }, WantError: connect.NewError(connect.CodeAlreadyExists, fmt.Errorf("already exists")), }, @@ -371,6 +375,9 @@ func Test_TenantCmd_Apply(t *testing.T) { Email: new(testresources.Tenant1().Email), Description: new(testresources.Tenant1().Description), Name: new(testresources.Tenant1().Name), + UpdateMeta: &apiv2.UpdateMeta{ + LockingStrategy: apiv2.OptimisticLockingStrategy_OPTIMISTIC_LOCKING_STRATEGY_SERVER, + }, }, WantResponse: func() connect.AnyResponse { return connect.NewResponse(&apiv2.TenantServiceUpdateResponse{ @@ -407,7 +414,7 @@ func Test_TenantCmd_ListMembers(t *testing.T) { return connect.NewResponse(&apiv2.TenantServiceGetResponse{ Tenant: testresources.Tenant1(), TenantMembers: []*apiv2.TenantMember{ - testresources.Tenant1Members(), testresources.Tenant2Members(), + testresources.Tenant1Member(), testresources.Tenant2Member(), }, }) }, @@ -424,7 +431,7 @@ func Test_TenantCmd_ListMembers(t *testing.T) { 16d6e8ba-f574-494f-8d5e-74f6cb2d8db0 TENANT_ROLE_OWNER now 40c0da4b-9eb9-4371-91aa-1ae62193fa54 TENANT_ROLE_EDITOR now `), - Template: new("{{ .id }} {{ .role }} {{ .projects }}"), + Template: new("{{ .tenant_member.id }} {{ .tenant_member.role }} {{ .tenant_member.projects }}"), WantTemplate: new(` 16d6e8ba-f574-494f-8d5e-74f6cb2d8db0 1 [0d81bca7-73f6-4da3-8397-4a8c52a0c583 f3b4e6a1-2c8d-4e5f-a7b9-1d3e5f7a9b0c] 40c0da4b-9eb9-4371-91aa-1ae62193fa54 2 [0d81bca7-73f6-4da3-8397-4a8c52a0c583] @@ -446,21 +453,26 @@ func Test_TenantCmd_DeleteMember(t *testing.T) { tests := []*e2e.Test[apiv2.TenantServiceRemoveMemberResponse, string]{ { Name: "delete tenant member", - CmdArgs: []string{"tenant", "member", "remove", testresources.Tenant1Members().Id, "--tenant", testresources.Tenant1().Login}, + CmdArgs: []string{"tenant", "member", "remove", testresources.Tenant1Member().Id, "--tenant", testresources.Tenant1().Login}, NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ ClientCalls: []client.ClientCall{ { WantRequest: &apiv2.TenantServiceRemoveMemberRequest{ Login: testresources.Tenant1().Login, - Member: testresources.Tenant1Members().Id, + Member: testresources.Tenant1Member().Id, }, WantResponse: func() connect.AnyResponse { - return connect.NewResponse(&apiv2.TenantServiceRemoveMemberResponse{}) + return connect.NewResponse(&apiv2.TenantServiceRemoveMemberResponse{ + TenantMember: testresources.Tenant1Member(), + }) }, }, }, }), - WantDefault: new(fmt.Sprintf("✔ successfully removed member \"%s\"", testresources.Tenant1Members().Id)), + WantTable: new(` + ID ROLE SINCE + 16d6e8ba-f574-494f-8d5e-74f6cb2d8db0 TENANT_ROLE_OWNER now + `), }, } for _, tt := range tests { @@ -469,27 +481,106 @@ func Test_TenantCmd_DeleteMember(t *testing.T) { } func Test_TenantCmd_UpdateMember(t *testing.T) { - tests := []*e2e.Test[apiv2.TenantServiceUpdateMemberResponse, *apiv2.TenantMember]{ + tests := []*e2e.Test[apiv2.TenantServiceUpdateMemberResponse, *cliv2.TenantMember]{ { Name: "update tenant member", - CmdArgs: []string{"tenant", "member", "update", testresources.Tenant1Members().Id, "--tenant", testresources.Tenant1().Login, "--role", testresources.Tenant1Members().Role.String()}, + CmdArgs: []string{"tenant", "member", "update", testresources.Tenant1Member().Id, "--tenant", testresources.Tenant1().Login, "--role", testresources.Tenant1Member().Role.String()}, NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ ClientCalls: []client.ClientCall{ { WantRequest: &apiv2.TenantServiceUpdateMemberRequest{ Login: testresources.Tenant1().Login, - Member: testresources.Tenant1Members().Id, - Role: testresources.Tenant1Members().Role, + Member: testresources.Tenant1Member().Id, + Role: testresources.Tenant1Member().Role, }, WantResponse: func() connect.AnyResponse { return connect.NewResponse(&apiv2.TenantServiceUpdateMemberResponse{ - TenantMember: testresources.Tenant1Members(), + TenantMember: testresources.Tenant1Member(), }) }, }, }, }), - WantProtoObject: testresources.Tenant1Members(), + WantTable: new(` + ID ROLE SINCE + 16d6e8ba-f574-494f-8d5e-74f6cb2d8db0 TENANT_ROLE_OWNER now + `), + }, + } + for _, tt := range tests { + tt.TestCmd(t) + } +} + +func Test_TenantCmd_ApplyMember(t *testing.T) { + tests := []*e2e.Test[apiv2.TenantServiceUpdateMemberResponse, *cliv2.TenantMember]{ + { + Name: "apply", + CmdArgs: append([]string{"tenant", "member", "apply"}, e2e.AppendFromFileCommonArgs()...), + NewRootCmd: e2erootcmd.NewRootCmd(t, + &e2erootcmd.TestConfig{ + FsMocks: func(fs *afero.Afero) { + require.NoError(t, fs.WriteFile(e2e.InputFilePath, e2e.MustMarshal(t, &cliv2.TenantMember{ + Tenant: testresources.Tenant1().Login, + TenantMember: testresources.Tenant1Member(), + }), 0755)) + }, + ClientCalls: []client.ClientCall{ + { + WantRequest: &apiv2.TenantServiceAddMemberRequest{ + Login: testresources.Tenant1().Login, + Member: testresources.Tenant1Member().Id, + Role: testresources.Tenant1Member().Role, + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&apiv2.TenantServiceAddMemberResponse{ + TenantMember: testresources.Tenant1Member(), + }) + }, + }, + }, + }, + ), + WantTable: new(` + ID ROLE SINCE + 16d6e8ba-f574-494f-8d5e-74f6cb2d8db0 TENANT_ROLE_OWNER now + `), + }, + { + Name: "apply already exists", + CmdArgs: append([]string{"tenant", "member", "apply"}, e2e.AppendFromFileCommonArgs()...), + NewRootCmd: e2erootcmd.NewRootCmd(t, + &e2erootcmd.TestConfig{ + FsMocks: func(fs *afero.Afero) { + require.NoError(t, fs.WriteFile(e2e.InputFilePath, e2e.MustMarshal(t, &cliv2.TenantMember{ + Tenant: testresources.Tenant1().Login, + TenantMember: testresources.Tenant1Member(), + }), 0755)) + }, + ClientCalls: []client.ClientCall{ + { + WantRequest: &apiv2.TenantServiceAddMemberRequest{ + Login: testresources.Tenant1().Login, + Member: testresources.Tenant1Member().Id, + Role: testresources.Tenant1Member().Role, + }, + WantError: connect.NewError(connect.CodeAlreadyExists, fmt.Errorf("already exists")), + }, + { + WantRequest: &apiv2.TenantServiceUpdateMemberRequest{ + Login: testresources.Tenant1().Login, + Member: testresources.Tenant1Member().Id, + Role: testresources.Tenant1Member().Role, + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&apiv2.TenantServiceUpdateMemberResponse{ + TenantMember: testresources.Tenant1Member(), + }) + }, + }, + }, + }, + ), WantTable: new(` ID ROLE SINCE 16d6e8ba-f574-494f-8d5e-74f6cb2d8db0 TENANT_ROLE_OWNER now diff --git a/tests/e2e/testresources/projects.go b/tests/e2e/testresources/projects.go index 4859fc1..50d88e4 100644 --- a/tests/e2e/testresources/projects.go +++ b/tests/e2e/testresources/projects.go @@ -55,7 +55,7 @@ var ( } } - Project1Members = func() *apiv2.ProjectMember { + Project1Member = func() *apiv2.ProjectMember { return &apiv2.ProjectMember{ Id: "16d6e8ba-f574-494f-8d5e-74f6cb2d8db0", Role: apiv2.ProjectRole_PROJECT_ROLE_OWNER, @@ -63,7 +63,7 @@ var ( CreatedAt: timestamppb.New(e2e.TimeBubbleStartTime()), } } - Project2Members = func() *apiv2.ProjectMember { + Project2Member = func() *apiv2.ProjectMember { return &apiv2.ProjectMember{ Id: "40c0da4b-9eb9-4371-91aa-1ae62193fa54", Role: apiv2.ProjectRole_PROJECT_ROLE_EDITOR, diff --git a/tests/e2e/testresources/switch.go b/tests/e2e/testresources/switch.go index ac51006..b970054 100644 --- a/tests/e2e/testresources/switch.go +++ b/tests/e2e/testresources/switch.go @@ -68,7 +68,7 @@ var ( return &apiv2.SwitchNic{ Name: "Ethernet0", Identifier: "oid:0x1000000000001", - Mac: new("52:54:00:ab:cd:01"), + Mac: new("52:54:00:ab:cd:01"), //nolint:staticcheck Vrf: new("default"), State: &apiv2.NicState{ Desired: new(apiv2.SwitchPortStatus_SWITCH_PORT_STATUS_UP), @@ -99,7 +99,7 @@ var ( return &apiv2.SwitchNic{ Name: "Ethernet4", Identifier: "oid:0x1000000000002", - Mac: new("52:54:00:ab:cd:02"), + Mac: new("52:54:00:ab:cd:02"), //nolint:staticcheck State: &apiv2.NicState{ Desired: new(apiv2.SwitchPortStatus_SWITCH_PORT_STATUS_UP), Actual: apiv2.SwitchPortStatus_SWITCH_PORT_STATUS_DOWN, diff --git a/tests/e2e/testresources/tenants.go b/tests/e2e/testresources/tenants.go index c8eb5b4..cf25580 100644 --- a/tests/e2e/testresources/tenants.go +++ b/tests/e2e/testresources/tenants.go @@ -57,7 +57,7 @@ var ( } } - Tenant1Members = func() *apiv2.TenantMember { + Tenant1Member = func() *apiv2.TenantMember { return &apiv2.TenantMember{ Id: "16d6e8ba-f574-494f-8d5e-74f6cb2d8db0", Role: apiv2.TenantRole_TENANT_ROLE_OWNER, @@ -65,7 +65,7 @@ var ( Projects: []string{Project1().Uuid, Project2().Uuid}, } } - Tenant2Members = func() *apiv2.TenantMember { + Tenant2Member = func() *apiv2.TenantMember { return &apiv2.TenantMember{ Id: "40c0da4b-9eb9-4371-91aa-1ae62193fa54", Role: apiv2.TenantRole_TENANT_ROLE_EDITOR,