diff --git a/cmd/completion/tenant.go b/cmd/completion/tenant.go new file mode 100644 index 00000000..b05ba601 --- /dev/null +++ b/cmd/completion/tenant.go @@ -0,0 +1,18 @@ +package completion + +import ( + "github.com/metal-stack/metal-go/api/client/tenant" + "github.com/spf13/cobra" +) + +func (c *Completion) TenantListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + resp, err := c.client.Tenant().ListTenants(tenant.NewListTenantsParams(), nil) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + var names []string + for _, p := range resp.Payload { + names = append(names, p.Meta.ID+"\t/"+p.Name) + } + return names, cobra.ShellCompDirectiveNoFileComp +} diff --git a/cmd/root.go b/cmd/root.go index 73bf53f8..9f30265a 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -119,6 +119,7 @@ metalctl machine list -o template --template "{{ .id }}:{{ .size.id }}" rootCmd.AddCommand(newMachineCmd(c)) rootCmd.AddCommand(newFirewallCmd(c)) rootCmd.AddCommand(newProjectCmd(c)) + rootCmd.AddCommand(newTenantCmd(c)) rootCmd.AddCommand(newSizeCmd(c)) rootCmd.AddCommand(newFilesystemLayoutCmd(c)) rootCmd.AddCommand(newImageCmd(c)) diff --git a/cmd/sorters/tenant.go b/cmd/sorters/tenant.go new file mode 100644 index 00000000..860c96f5 --- /dev/null +++ b/cmd/sorters/tenant.go @@ -0,0 +1,21 @@ +package sorters + +import ( + "github.com/metal-stack/metal-go/api/models" + "github.com/metal-stack/metal-lib/pkg/multisort" + p "github.com/metal-stack/metal-lib/pkg/pointer" +) + +func TenantSorter() *multisort.Sorter[*models.V1TenantResponse] { + return multisort.New(multisort.FieldMap[*models.V1TenantResponse]{ + "id": func(a, b *models.V1TenantResponse, descending bool) multisort.CompareResult { + return multisort.Compare(p.SafeDeref(a.Meta).ID, p.SafeDeref(b.Meta).ID, descending) + }, + "name": func(a, b *models.V1TenantResponse, descending bool) multisort.CompareResult { + return multisort.Compare(a.Name, b.Name, descending) + }, + "description": func(a, b *models.V1TenantResponse, descending bool) multisort.CompareResult { + return multisort.Compare(a.Description, b.Description, descending) + }, + }, multisort.Keys{{ID: "id"}}) +} diff --git a/cmd/tableprinters/printer.go b/cmd/tableprinters/printer.go index 1eb7665f..a6f11301 100644 --- a/cmd/tableprinters/printer.go +++ b/cmd/tableprinters/printer.go @@ -73,6 +73,10 @@ func (t *TablePrinter) ToHeaderAndRows(data any, wide bool) ([]string, [][]strin return t.ProjectTable(pointer.WrapInSlice(d), wide) case []*models.V1ProjectResponse: return t.ProjectTable(d, wide) + case *models.V1TenantResponse: + return t.TenantTable(pointer.WrapInSlice(d), wide) + case []*models.V1TenantResponse: + return t.TenantTable(d, wide) case []*models.V1MachineIPMIResponse: return t.MachineIPMITable(d, wide) case *models.V1MachineIPMIResponse: diff --git a/cmd/tableprinters/tenant.go b/cmd/tableprinters/tenant.go new file mode 100644 index 00000000..5cc74c77 --- /dev/null +++ b/cmd/tableprinters/tenant.go @@ -0,0 +1,66 @@ +package tableprinters + +import ( + "strconv" + "strings" + + "github.com/metal-stack/metal-go/api/models" + "github.com/metal-stack/metal-lib/pkg/genericcli" +) + +func (t *TablePrinter) TenantTable(data []*models.V1TenantResponse, wide bool) ([]string, [][]string, error) { + var ( + rows [][]string + ) + + header := []string{"ID", "Name", "Description", "Labels", "Annotations"} + if wide { + header = []string{"ID", "Name", "Description", "Labels", "Annotations", "Quotas"} + } + + for _, pr := range data { + var ( + clusterQuota = "∞" + machineQuota = "∞" + ipQuota = "∞" + ) + + if pr.Quotas != nil { + qs := pr.Quotas + if qs.Cluster != nil { + if qs.Cluster.Quota != 0 { + clusterQuota = strconv.FormatInt(int64(qs.Cluster.Quota), 10) + } + } + if qs.Machine != nil { + if qs.Machine.Quota != 0 { + machineQuota = strconv.FormatInt(int64(qs.Machine.Quota), 10) + } + } + if qs.IP != nil { + if qs.IP.Quota != 0 { + ipQuota = strconv.FormatInt(int64(qs.IP.Quota), 10) + } + } + } + + quotas := []string{ + clusterQuota + " Cluster(s)", + machineQuota + " Machine(s)", + ipQuota + " IP(s)", + } + + labels := strings.Join(pr.Meta.Labels, "\n") + + as := genericcli.MapToLabels(pr.Meta.Annotations) + annotations := strings.Join(as, "\n") + + if wide { + rows = append(rows, []string{pr.Meta.ID, pr.Name, pr.Description, labels, annotations, strings.Join(quotas, "\n")}) + } else { + rows = append(rows, []string{pr.Meta.ID, pr.Name, pr.Description, labels, annotations}) + } + } + + return header, rows, nil +} diff --git a/cmd/tenant.go b/cmd/tenant.go new file mode 100644 index 00000000..714a7597 --- /dev/null +++ b/cmd/tenant.go @@ -0,0 +1,210 @@ +package cmd + +import ( + "errors" + "fmt" + + tenantmodel "github.com/metal-stack/metal-go/api/client/tenant" + "github.com/metal-stack/metal-go/api/models" + "github.com/metal-stack/metal-lib/pkg/genericcli" + "github.com/metal-stack/metal-lib/pkg/genericcli/printers" + "github.com/metal-stack/metalctl/cmd/sorters" + + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +type tenantCmd struct { + *config +} + +func newTenantCmd(c *config) *cobra.Command { + w := tenantCmd{ + config: c, + } + + cmdsConfig := &genericcli.CmdsConfig[*models.V1TenantCreateRequest, *models.V1TenantUpdateRequest, *models.V1TenantResponse]{ + BinaryName: binaryName, + GenericCLI: genericcli.NewGenericCLI[*models.V1TenantCreateRequest, *models.V1TenantUpdateRequest, *models.V1TenantResponse](w).WithFS(c.fs), + Singular: "tenant", + Plural: "tenants", + Description: "a tenant belongs to a tenant and groups together entities in metal-stack.", + Sorter: sorters.TenantSorter(), + ValidArgsFn: c.comp.TenantListCompletion, + DescribePrinter: func() printers.Printer { return c.describePrinter }, + ListPrinter: func() printers.Printer { return c.listPrinter }, + CreateRequestFromCLI: w.createFromCLI, + CreateCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().String("id", "", "id of the tenant, max 10 characters.") + cmd.Flags().String("name", "", "name of the tenant, max 10 characters.") + cmd.Flags().String("description", "", "description of the tenant.") + cmd.Flags().StringSlice("labels", nil, "add initial label, can be given multiple times to add multiple labels, e.g. --label=foo --label=bar") + cmd.Flags().StringSlice("annotations", nil, "add initial annotations, must be in the form of key=value, can be given multiple times to add multiple annotations, e.g. --annotation key=value --annotation foo=bar") + cmd.Flags().Int32("cluster-quota", 0, "cluster quota") + cmd.Flags().Int32("machine-quota", 0, "machine quota") + cmd.Flags().Int32("ip-quota", 0, "ip quota") + + cmd.MarkFlagsMutuallyExclusive("file", "name") + cmd.MarkFlagsRequiredTogether("name", "description") + }, + ListCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().StringP("name", "", "", "Name of the tenant.") + cmd.Flags().StringP("id", "", "", "ID of the tenant.") + cmd.Flags().StringSliceP("annotations", "", []string{}, "annotations") + }, + } + + return genericcli.NewCmds(cmdsConfig) +} + +func (c tenantCmd) Get(id string) (*models.V1TenantResponse, error) { + resp, err := c.client.Tenant().GetTenant(tenantmodel.NewGetTenantParams().WithID(id), nil) + if err != nil { + return nil, err + } + + return resp.Payload, nil +} + +func (c tenantCmd) List() ([]*models.V1TenantResponse, error) { + var annotations map[string]string + if viper.IsSet("annotations") { + var err error + annotations, err = genericcli.LabelsToMap(viper.GetStringSlice("annotations")) + if err != nil { + return nil, err + } + } + + resp, err := c.client.Tenant().FindTenants(tenantmodel.NewFindTenantsParams().WithBody(&models.V1TenantFindRequest{ + ID: viper.GetString("id"), + Name: viper.GetString("name"), + Annotations: annotations, + }), nil) + if err != nil { + return nil, err + } + + return resp.Payload, nil +} + +func (c tenantCmd) Delete(id string) (*models.V1TenantResponse, error) { + resp, err := c.client.Tenant().DeleteTenant(tenantmodel.NewDeleteTenantParams().WithID(id), nil) + if err != nil { + return nil, err + } + + return resp.Payload, nil +} + +func (c tenantCmd) Create(rq *models.V1TenantCreateRequest) (*models.V1TenantResponse, error) { + resp, err := c.client.Tenant().CreateTenant(tenantmodel.NewCreateTenantParams().WithBody(rq), nil) + if err != nil { + var r *tenantmodel.CreateTenantConflict + if errors.As(err, &r) { + return nil, genericcli.AlreadyExistsError() + } + return nil, err + } + + return resp.Payload, nil +} + +func (c tenantCmd) Update(rq *models.V1TenantUpdateRequest) (*models.V1TenantResponse, error) { + if rq.Meta == nil { + return nil, fmt.Errorf("tenant meta is nil") + } + + getResp, err := c.Get(rq.Meta.ID) + if err != nil { + return nil, err + } + + rq.Meta.Version = getResp.Meta.Version + + updateResp, err := c.client.Tenant().UpdateTenant(tenantmodel.NewUpdateTenantParams().WithBody(rq), nil) + if err != nil { + return nil, err + } + + return updateResp.Payload, nil +} + +func (c tenantCmd) Convert(r *models.V1TenantResponse) (string, *models.V1TenantCreateRequest, *models.V1TenantUpdateRequest, error) { + if r.Meta == nil { + return "", nil, nil, fmt.Errorf("meta is nil") + } + return r.Meta.ID, tenantResponseToCreate(r), tenantResponseToUpdate(r), nil +} + +func tenantResponseToCreate(r *models.V1TenantResponse) *models.V1TenantCreateRequest { + return &models.V1TenantCreateRequest{ + Meta: &models.V1Meta{ + Apiversion: r.Meta.Apiversion, + Kind: r.Meta.Kind, + ID: r.Meta.ID, + Annotations: r.Meta.Annotations, + Labels: r.Meta.Labels, + Version: r.Meta.Version, + }, + Description: r.Description, + Name: r.Name, + Quotas: r.Quotas, + } +} + +func tenantResponseToUpdate(r *models.V1TenantResponse) *models.V1TenantUpdateRequest { + return &models.V1TenantUpdateRequest{ + Name: r.Name, + Meta: &models.V1Meta{ + Apiversion: r.Meta.Apiversion, + Kind: r.Meta.Kind, + ID: r.Meta.ID, + Annotations: r.Meta.Annotations, + Labels: r.Meta.Labels, + Version: r.Meta.Version, + }, + Description: r.Description, + IamConfig: r.IamConfig, + DefaultQuotas: r.DefaultQuotas, + Quotas: r.Quotas, + } +} + +func (w *tenantCmd) createFromCLI() (*models.V1TenantCreateRequest, error) { + var ( + clusterQuota, machineQuota, ipQuota *models.V1Quota + ) + if viper.IsSet("cluster-quota") { + clusterQuota = &models.V1Quota{Quota: viper.GetInt32("cluster-quota")} + } + if viper.IsSet("machine-quota") { + machineQuota = &models.V1Quota{Quota: viper.GetInt32("machine-quota")} + } + if viper.IsSet("ip-quota") { + ipQuota = &models.V1Quota{Quota: viper.GetInt32("ip-quota")} + } + + annotations, err := genericcli.LabelsToMap(viper.GetStringSlice("annotations")) + if err != nil { + return nil, err + } + + return &models.V1TenantCreateRequest{ + Name: viper.GetString("name"), + Description: viper.GetString("description"), + Quotas: &models.V1QuotaSet{ + Cluster: clusterQuota, + Machine: machineQuota, + IP: ipQuota, + }, + Meta: &models.V1Meta{ + Kind: "Tenant", + Apiversion: "v1", + Annotations: annotations, + Labels: viper.GetStringSlice("labels"), + ID: viper.GetString("id"), + }, + }, + nil +} diff --git a/cmd/tenant_test.go b/cmd/tenant_test.go new file mode 100644 index 00000000..6f7697f8 --- /dev/null +++ b/cmd/tenant_test.go @@ -0,0 +1,338 @@ +package cmd + +import ( + "strconv" + "strings" + "testing" + + "github.com/metal-stack/metal-go/api/client/tenant" + "github.com/metal-stack/metal-go/api/models" + "github.com/metal-stack/metal-go/test/client" + "github.com/metal-stack/metal-lib/pkg/genericcli" + "github.com/metal-stack/metal-lib/pkg/pointer" + "github.com/metal-stack/metal-lib/pkg/testcommon" + "github.com/spf13/afero" + + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +var ( + tenant1 = &models.V1TenantResponse{ + Meta: &models.V1Meta{ + Kind: "Tenant", + Apiversion: "v1", + ID: "1", + Annotations: map[string]string{ + "a": "b", + }, + Labels: []string{"c"}, + Version: 1, + }, + Description: "tenant 1", + Name: "tenant-1", + Quotas: &models.V1QuotaSet{ + Cluster: &models.V1Quota{ + Quota: 1, + Used: 1, + }, + IP: &models.V1Quota{ + Quota: 2, + Used: 2, + }, + Machine: &models.V1Quota{ + Quota: 3, + Used: 3, + }, + }, + } + tenant2 = &models.V1TenantResponse{ + Meta: &models.V1Meta{ + Kind: "Tenant", + Apiversion: "v1", + ID: "2", + Annotations: map[string]string{ + "a": "b", + }, + Labels: []string{"c"}, + Version: 1, + }, + Description: "tenant 2", + Name: "tenant-2", + Quotas: &models.V1QuotaSet{ + Cluster: &models.V1Quota{}, + IP: &models.V1Quota{}, + Machine: &models.V1Quota{}, + }, + } +) + +func Test_TenantCmd_MultiResult(t *testing.T) { + tests := []*test[[]*models.V1TenantResponse]{ + { + name: "list", + cmd: func(want []*models.V1TenantResponse) []string { + return []string{"tenant", "list"} + }, + mocks: &client.MetalMockFns{ + Tenant: func(mock *mock.Mock) { + mock.On("FindTenants", testcommon.MatchIgnoreContext(t, tenant.NewFindTenantsParams().WithBody(&models.V1TenantFindRequest{})), nil).Return(&tenant.FindTenantsOK{ + Payload: []*models.V1TenantResponse{ + tenant2, + tenant1, + }, + }, nil) + }, + }, + want: []*models.V1TenantResponse{ + tenant1, + tenant2, + }, + wantTable: pointer.Pointer(` +ID NAME DESCRIPTION LABELS ANNOTATIONS +1 tenant-1 tenant 1 c a=b +2 tenant-2 tenant 2 c a=b +`), + wantWideTable: pointer.Pointer(` +ID NAME DESCRIPTION LABELS ANNOTATIONS QUOTAS +1 tenant-1 tenant 1 c a=b 1 Cluster(s) + 3 Machine(s) + 2 IP(s) +2 tenant-2 tenant 2 c a=b ∞ Cluster(s) + ∞ Machine(s) + ∞ IP(s) +`), + template: pointer.Pointer("{{ .meta.id }} {{ .name }}"), + wantTemplate: pointer.Pointer(` +1 tenant-1 +2 tenant-2 +`), + wantMarkdown: pointer.Pointer(` +| ID | NAME | DESCRIPTION | LABELS | ANNOTATIONS | +|----|----------|-------------|--------|-------------| +| 1 | tenant-1 | tenant 1 | c | a=b | +| 2 | tenant-2 | tenant 2 | c | a=b | +`), + }, + { + name: "list with filters", + cmd: func(want []*models.V1TenantResponse) []string { + args := []string{"tenant", "list", "--name", "tenant-1", "--annotations", "a=b", "--id", want[0].Meta.ID} + assertExhaustiveArgs(t, args, "sort-by") + return args + }, + mocks: &client.MetalMockFns{ + Tenant: func(mock *mock.Mock) { + mock.On("FindTenants", testcommon.MatchIgnoreContext(t, tenant.NewFindTenantsParams().WithBody(&models.V1TenantFindRequest{ + Name: "tenant-1", + ID: "1", + Annotations: map[string]string{ + "a": "b", + }, + })), nil).Return(&tenant.FindTenantsOK{ + Payload: []*models.V1TenantResponse{ + tenant1, + }, + }, nil) + }, + }, + want: []*models.V1TenantResponse{ + tenant1, + }, + wantTable: pointer.Pointer(` +ID NAME DESCRIPTION LABELS ANNOTATIONS +1 tenant-1 tenant 1 c a=b + `), + wantWideTable: pointer.Pointer(` +ID NAME DESCRIPTION LABELS ANNOTATIONS QUOTAS +1 tenant-1 tenant 1 c a=b 1 Cluster(s) + 3 Machine(s) + 2 IP(s) + `), + template: pointer.Pointer("{{ .meta.id }} {{ .name }}"), + wantTemplate: pointer.Pointer(` + 1 tenant-1 + `), + wantMarkdown: pointer.Pointer(` +| ID | NAME | DESCRIPTION | LABELS | ANNOTATIONS | +|----|----------|-------------|--------|-------------| +| 1 | tenant-1 | tenant 1 | c | a=b | + `), + }, + { + name: "apply", + cmd: func(want []*models.V1TenantResponse) []string { + return appendFromFileCommonArgs("tenant", "apply") + }, + fsMocks: func(fs afero.Fs, want []*models.V1TenantResponse) { + require.NoError(t, afero.WriteFile(fs, "/file.yaml", mustMarshalToMultiYAML(t, want), 0755)) + }, + mocks: &client.MetalMockFns{ + Tenant: func(mock *mock.Mock) { + mock.On("CreateTenant", testcommon.MatchIgnoreContext(t, tenant.NewCreateTenantParams().WithBody(tenantResponseToCreate(tenant1))), nil).Return(nil, &tenant.CreateTenantConflict{}).Once() + mock.On("GetTenant", testcommon.MatchIgnoreContext(t, tenant.NewGetTenantParams().WithID(tenant1.Meta.ID)), nil).Return(&tenant.GetTenantOK{ + Payload: tenant1, + }, nil) + mock.On("UpdateTenant", testcommon.MatchIgnoreContext(t, tenant.NewUpdateTenantParams().WithBody(tenantResponseToUpdate(tenant1))), nil).Return(&tenant.UpdateTenantOK{ + Payload: tenant1, + }, nil) + mock.On("CreateTenant", testcommon.MatchIgnoreContext(t, tenant.NewCreateTenantParams().WithBody(tenantResponseToCreate(tenant2))), nil).Return(&tenant.CreateTenantCreated{ + Payload: tenant2, + }, nil) + }, + }, + want: []*models.V1TenantResponse{ + tenant1, + tenant2, + }, + }, + { + name: "create from file", + cmd: func(want []*models.V1TenantResponse) []string { + return appendFromFileCommonArgs("tenant", "create") + }, + fsMocks: func(fs afero.Fs, want []*models.V1TenantResponse) { + require.NoError(t, afero.WriteFile(fs, "/file.yaml", mustMarshalToMultiYAML(t, want), 0755)) + }, + mocks: &client.MetalMockFns{ + Tenant: func(mock *mock.Mock) { + mock.On("CreateTenant", testcommon.MatchIgnoreContext(t, tenant.NewCreateTenantParams().WithBody(tenantResponseToCreate(tenant1))), nil).Return(&tenant.CreateTenantCreated{ + Payload: tenant1, + }, nil) + }, + }, + want: []*models.V1TenantResponse{ + tenant1, + }, + }, + { + name: "update from file", + cmd: func(want []*models.V1TenantResponse) []string { + return appendFromFileCommonArgs("tenant", "update") + }, + fsMocks: func(fs afero.Fs, want []*models.V1TenantResponse) { + require.NoError(t, afero.WriteFile(fs, "/file.yaml", mustMarshalToMultiYAML(t, want), 0755)) + }, + mocks: &client.MetalMockFns{ + Tenant: func(mock *mock.Mock) { + mock.On("GetTenant", testcommon.MatchIgnoreContext(t, tenant.NewGetTenantParams().WithID(tenant1.Meta.ID)), nil).Return(&tenant.GetTenantOK{ + Payload: tenant1, + }, nil) + mock.On("UpdateTenant", testcommon.MatchIgnoreContext(t, tenant.NewUpdateTenantParams().WithBody(tenantResponseToUpdate(tenant1))), nil).Return(&tenant.UpdateTenantOK{ + Payload: tenant1, + }, nil) + }, + }, + want: []*models.V1TenantResponse{ + tenant1, + }, + }, + { + name: "delete from file", + cmd: func(want []*models.V1TenantResponse) []string { + return appendFromFileCommonArgs("tenant", "delete") + }, + fsMocks: func(fs afero.Fs, want []*models.V1TenantResponse) { + require.NoError(t, afero.WriteFile(fs, "/file.yaml", mustMarshalToMultiYAML(t, want), 0755)) + }, + mocks: &client.MetalMockFns{ + Tenant: func(mock *mock.Mock) { + mock.On("DeleteTenant", testcommon.MatchIgnoreContext(t, tenant.NewDeleteTenantParams().WithID(tenant1.Meta.ID)), nil).Return(&tenant.DeleteTenantOK{ + Payload: tenant1, + }, nil) + }, + }, + want: []*models.V1TenantResponse{ + tenant1, + }, + }, + } + for _, tt := range tests { + tt.testCmd(t) + } +} + +func Test_TenantCmd_SingleResult(t *testing.T) { + tests := []*test[*models.V1TenantResponse]{ + { + name: "describe", + cmd: func(want *models.V1TenantResponse) []string { + return []string{"tenant", "describe", want.Meta.ID} + }, + mocks: &client.MetalMockFns{ + Tenant: func(mock *mock.Mock) { + mock.On("GetTenant", testcommon.MatchIgnoreContext(t, tenant.NewGetTenantParams().WithID(tenant1.Meta.ID)), nil).Return(&tenant.GetTenantOK{ + Payload: tenant1, + }, nil) + }, + }, + want: tenant1, + wantTable: pointer.Pointer(` +ID NAME DESCRIPTION LABELS ANNOTATIONS +1 tenant-1 tenant 1 c a=b +`), + wantWideTable: pointer.Pointer(` +ID NAME DESCRIPTION LABELS ANNOTATIONS QUOTAS +1 tenant-1 tenant 1 c a=b 1 Cluster(s) + 3 Machine(s) + 2 IP(s) +`), + template: pointer.Pointer("{{ .meta.id }} {{ .name }}"), + wantTemplate: pointer.Pointer(` +1 tenant-1 +`), + wantMarkdown: pointer.Pointer(` +| ID | NAME | DESCRIPTION | LABELS | ANNOTATIONS | +|----|----------|-------------|--------|-------------| +| 1 | tenant-1 | tenant 1 | c | a=b | +`), + }, + { + name: "delete", + cmd: func(want *models.V1TenantResponse) []string { + return []string{"tenant", "rm", want.Meta.ID} + }, + mocks: &client.MetalMockFns{ + Tenant: func(mock *mock.Mock) { + mock.On("DeleteTenant", testcommon.MatchIgnoreContext(t, tenant.NewDeleteTenantParams().WithID(tenant1.Meta.ID)), nil).Return(&tenant.DeleteTenantOK{ + Payload: tenant1, + }, nil) + }, + }, + want: tenant1, + }, + { + name: "create", + cmd: func(want *models.V1TenantResponse) []string { + args := []string{"tenant", "create", + "--id", want.Meta.ID, + "--name", want.Name, + "--description", want.Description, + "--labels", strings.Join(want.Meta.Labels, ","), + "--annotations", strings.Join(genericcli.MapToLabels(want.Meta.Annotations), ","), + "--cluster-quota", strconv.FormatInt(int64(want.Quotas.Cluster.Quota), 10), + "--machine-quota", strconv.FormatInt(int64(want.Quotas.Machine.Quota), 10), + "--ip-quota", strconv.FormatInt(int64(want.Quotas.IP.Quota), 10), + } + assertExhaustiveArgs(t, args, commonExcludedFileArgs()...) + return args + }, + mocks: &client.MetalMockFns{ + Tenant: func(mock *mock.Mock) { + p := tenant1 + p.Meta.Version = 0 + p.Quotas.Cluster.Used = 0 + p.Quotas.IP.Used = 0 + p.Quotas.Machine.Used = 0 + mock.On("CreateTenant", testcommon.MatchIgnoreContext(t, tenant.NewCreateTenantParams().WithBody(tenantResponseToCreate(p))), nil).Return(&tenant.CreateTenantCreated{ + Payload: tenant1, + }, nil) + }, + }, + want: tenant1, + }, + } + for _, tt := range tests { + tt.testCmd(t) + } +} diff --git a/docs/metalctl.md b/docs/metalctl.md index 3331ec47..593ddfbf 100644 --- a/docs/metalctl.md +++ b/docs/metalctl.md @@ -50,6 +50,7 @@ a cli to manage entities in the metal-stack api * [metalctl project](metalctl_project.md) - manage project entities * [metalctl size](metalctl_size.md) - manage size entities * [metalctl switch](metalctl_switch.md) - manage switch entities +* [metalctl tenant](metalctl_tenant.md) - manage tenant entities * [metalctl update](metalctl_update.md) - update the program * [metalctl version](metalctl_version.md) - print the client and server version information * [metalctl vpn](metalctl_vpn.md) - access VPN diff --git a/docs/metalctl_tenant.md b/docs/metalctl_tenant.md new file mode 100644 index 00000000..afacabb1 --- /dev/null +++ b/docs/metalctl_tenant.md @@ -0,0 +1,53 @@ +## metalctl tenant + +manage tenant entities + +### Synopsis + +a tenant belongs to a tenant and groups together entities in metal-stack. + +### Options + +``` + -h, --help help for tenant +``` + +### Options inherited from parent commands + +``` + --api-token string api token to authenticate. Can be specified with METALCTL_API_TOKEN environment variable. + --api-url string api server address. Can be specified with METALCTL_API_URL environment variable. + -c, --config string alternative config file path, (default is ~/.metalctl/config.yaml). + Example config.yaml: + + --- + apitoken: "alongtoken" + ... + + + --debug debug output + --force-color force colored output even without tty + --kubeconfig string Path to the kube-config to use for authentication and authorization. Is updated by login. Uses default path if not specified. + --no-headers do not print headers of table output format (default print headers) + -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. + Example for machines: + + metalctl machine list -o template --template "{{ .id }}:{{ .size.id }}" + + + --yes-i-really-mean-it skips security prompts (which can be dangerous to set blindly because actions can lead to data loss or additional costs) +``` + +### SEE ALSO + +* [metalctl](metalctl.md) - a cli to manage entities in the metal-stack api +* [metalctl tenant apply](metalctl_tenant_apply.md) - applies one or more tenants from a given file +* [metalctl tenant create](metalctl_tenant_create.md) - creates the tenant +* [metalctl tenant delete](metalctl_tenant_delete.md) - deletes the tenant +* [metalctl tenant describe](metalctl_tenant_describe.md) - describes the tenant +* [metalctl tenant edit](metalctl_tenant_edit.md) - edit the tenant through an editor and update +* [metalctl tenant list](metalctl_tenant_list.md) - list all tenants +* [metalctl tenant update](metalctl_tenant_update.md) - updates the tenant + diff --git a/docs/metalctl_tenant_apply.md b/docs/metalctl_tenant_apply.md new file mode 100644 index 00000000..4398700a --- /dev/null +++ b/docs/metalctl_tenant_apply.md @@ -0,0 +1,61 @@ +## metalctl tenant apply + +applies one or more tenants from a given file + +``` +metalctl 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: + $ metalctl tenant describe tenant-1 -o yaml > tenant.yaml + $ vi tenant.yaml + $ # either via stdin + $ cat tenant.yaml | metalctl tenant apply -f - + $ # or via file + $ metalctl 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 api token to authenticate. Can be specified with METALCTL_API_TOKEN environment variable. + --api-url string api server address. Can be specified with METALCTL_API_URL environment variable. + -c, --config string alternative config file path, (default is ~/.metalctl/config.yaml). + Example config.yaml: + + --- + apitoken: "alongtoken" + ... + + + --debug debug output + --force-color force colored output even without tty + --kubeconfig string Path to the kube-config to use for authentication and authorization. Is updated by login. Uses default path if not specified. + --no-headers do not print headers of table output format (default print headers) + -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. + Example for machines: + + metalctl machine list -o template --template "{{ .id }}:{{ .size.id }}" + + + --yes-i-really-mean-it skips security prompts (which can be dangerous to set blindly because actions can lead to data loss or additional costs) +``` + +### SEE ALSO + +* [metalctl tenant](metalctl_tenant.md) - manage tenant entities + diff --git a/docs/metalctl_tenant_create.md b/docs/metalctl_tenant_create.md new file mode 100644 index 00000000..4193c366 --- /dev/null +++ b/docs/metalctl_tenant_create.md @@ -0,0 +1,69 @@ +## metalctl tenant create + +creates the tenant + +``` +metalctl tenant create [flags] +``` + +### Options + +``` + --annotations strings add initial annotations, must be in the form of key=value, can be given multiple times to add multiple annotations, e.g. --annotation key=value --annotation foo=bar + --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. + --cluster-quota int32 cluster quota + --description string description of the tenant. + -f, --file string filename of the create or update request in yaml format, or - for stdin. + + Example: + $ metalctl tenant describe tenant-1 -o yaml > tenant.yaml + $ vi tenant.yaml + $ # either via stdin + $ cat tenant.yaml | metalctl tenant create -f - + $ # or via file + $ metalctl tenant create -f tenant.yaml + + the file can also contain multiple documents and perform a bulk operation. + + -h, --help help for create + --id string id of the tenant, max 10 characters. + --ip-quota int32 ip quota + --labels strings add initial label, can be given multiple times to add multiple labels, e.g. --label=foo --label=bar + --machine-quota int32 machine quota + --name string name of the tenant, max 10 characters. + --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 api token to authenticate. Can be specified with METALCTL_API_TOKEN environment variable. + --api-url string api server address. Can be specified with METALCTL_API_URL environment variable. + -c, --config string alternative config file path, (default is ~/.metalctl/config.yaml). + Example config.yaml: + + --- + apitoken: "alongtoken" + ... + + + --debug debug output + --force-color force colored output even without tty + --kubeconfig string Path to the kube-config to use for authentication and authorization. Is updated by login. Uses default path if not specified. + --no-headers do not print headers of table output format (default print headers) + -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. + Example for machines: + + metalctl machine list -o template --template "{{ .id }}:{{ .size.id }}" + + + --yes-i-really-mean-it skips security prompts (which can be dangerous to set blindly because actions can lead to data loss or additional costs) +``` + +### SEE ALSO + +* [metalctl tenant](metalctl_tenant.md) - manage tenant entities + diff --git a/docs/metalctl_tenant_delete.md b/docs/metalctl_tenant_delete.md new file mode 100644 index 00000000..120d46fd --- /dev/null +++ b/docs/metalctl_tenant_delete.md @@ -0,0 +1,61 @@ +## metalctl tenant delete + +deletes the tenant + +``` +metalctl 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: + $ metalctl tenant describe tenant-1 -o yaml > tenant.yaml + $ vi tenant.yaml + $ # either via stdin + $ cat tenant.yaml | metalctl tenant delete -f - + $ # or via file + $ metalctl 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 api token to authenticate. Can be specified with METALCTL_API_TOKEN environment variable. + --api-url string api server address. Can be specified with METALCTL_API_URL environment variable. + -c, --config string alternative config file path, (default is ~/.metalctl/config.yaml). + Example config.yaml: + + --- + apitoken: "alongtoken" + ... + + + --debug debug output + --force-color force colored output even without tty + --kubeconfig string Path to the kube-config to use for authentication and authorization. Is updated by login. Uses default path if not specified. + --no-headers do not print headers of table output format (default print headers) + -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. + Example for machines: + + metalctl machine list -o template --template "{{ .id }}:{{ .size.id }}" + + + --yes-i-really-mean-it skips security prompts (which can be dangerous to set blindly because actions can lead to data loss or additional costs) +``` + +### SEE ALSO + +* [metalctl tenant](metalctl_tenant.md) - manage tenant entities + diff --git a/docs/metalctl_tenant_describe.md b/docs/metalctl_tenant_describe.md new file mode 100644 index 00000000..e5339731 --- /dev/null +++ b/docs/metalctl_tenant_describe.md @@ -0,0 +1,46 @@ +## metalctl tenant describe + +describes the tenant + +``` +metalctl tenant describe [flags] +``` + +### Options + +``` + -h, --help help for describe +``` + +### Options inherited from parent commands + +``` + --api-token string api token to authenticate. Can be specified with METALCTL_API_TOKEN environment variable. + --api-url string api server address. Can be specified with METALCTL_API_URL environment variable. + -c, --config string alternative config file path, (default is ~/.metalctl/config.yaml). + Example config.yaml: + + --- + apitoken: "alongtoken" + ... + + + --debug debug output + --force-color force colored output even without tty + --kubeconfig string Path to the kube-config to use for authentication and authorization. Is updated by login. Uses default path if not specified. + --no-headers do not print headers of table output format (default print headers) + -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. + Example for machines: + + metalctl machine list -o template --template "{{ .id }}:{{ .size.id }}" + + + --yes-i-really-mean-it skips security prompts (which can be dangerous to set blindly because actions can lead to data loss or additional costs) +``` + +### SEE ALSO + +* [metalctl tenant](metalctl_tenant.md) - manage tenant entities + diff --git a/docs/metalctl_tenant_edit.md b/docs/metalctl_tenant_edit.md new file mode 100644 index 00000000..14a408eb --- /dev/null +++ b/docs/metalctl_tenant_edit.md @@ -0,0 +1,46 @@ +## metalctl tenant edit + +edit the tenant through an editor and update + +``` +metalctl tenant edit [flags] +``` + +### Options + +``` + -h, --help help for edit +``` + +### Options inherited from parent commands + +``` + --api-token string api token to authenticate. Can be specified with METALCTL_API_TOKEN environment variable. + --api-url string api server address. Can be specified with METALCTL_API_URL environment variable. + -c, --config string alternative config file path, (default is ~/.metalctl/config.yaml). + Example config.yaml: + + --- + apitoken: "alongtoken" + ... + + + --debug debug output + --force-color force colored output even without tty + --kubeconfig string Path to the kube-config to use for authentication and authorization. Is updated by login. Uses default path if not specified. + --no-headers do not print headers of table output format (default print headers) + -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. + Example for machines: + + metalctl machine list -o template --template "{{ .id }}:{{ .size.id }}" + + + --yes-i-really-mean-it skips security prompts (which can be dangerous to set blindly because actions can lead to data loss or additional costs) +``` + +### SEE ALSO + +* [metalctl tenant](metalctl_tenant.md) - manage tenant entities + diff --git a/docs/metalctl_tenant_list.md b/docs/metalctl_tenant_list.md new file mode 100644 index 00000000..360c4092 --- /dev/null +++ b/docs/metalctl_tenant_list.md @@ -0,0 +1,50 @@ +## metalctl tenant list + +list all tenants + +``` +metalctl tenant list [flags] +``` + +### Options + +``` + --annotations strings annotations + -h, --help help for list + --id string ID of the tenant. + --name string Name of the tenant. + --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: description|id|name +``` + +### Options inherited from parent commands + +``` + --api-token string api token to authenticate. Can be specified with METALCTL_API_TOKEN environment variable. + --api-url string api server address. Can be specified with METALCTL_API_URL environment variable. + -c, --config string alternative config file path, (default is ~/.metalctl/config.yaml). + Example config.yaml: + + --- + apitoken: "alongtoken" + ... + + + --debug debug output + --force-color force colored output even without tty + --kubeconfig string Path to the kube-config to use for authentication and authorization. Is updated by login. Uses default path if not specified. + --no-headers do not print headers of table output format (default print headers) + -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. + Example for machines: + + metalctl machine list -o template --template "{{ .id }}:{{ .size.id }}" + + + --yes-i-really-mean-it skips security prompts (which can be dangerous to set blindly because actions can lead to data loss or additional costs) +``` + +### SEE ALSO + +* [metalctl tenant](metalctl_tenant.md) - manage tenant entities + diff --git a/docs/metalctl_tenant_update.md b/docs/metalctl_tenant_update.md new file mode 100644 index 00000000..fea0ff00 --- /dev/null +++ b/docs/metalctl_tenant_update.md @@ -0,0 +1,61 @@ +## metalctl tenant update + +updates the tenant + +``` +metalctl 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: + $ metalctl tenant describe tenant-1 -o yaml > tenant.yaml + $ vi tenant.yaml + $ # either via stdin + $ cat tenant.yaml | metalctl tenant update -f - + $ # or via file + $ metalctl 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 api token to authenticate. Can be specified with METALCTL_API_TOKEN environment variable. + --api-url string api server address. Can be specified with METALCTL_API_URL environment variable. + -c, --config string alternative config file path, (default is ~/.metalctl/config.yaml). + Example config.yaml: + + --- + apitoken: "alongtoken" + ... + + + --debug debug output + --force-color force colored output even without tty + --kubeconfig string Path to the kube-config to use for authentication and authorization. Is updated by login. Uses default path if not specified. + --no-headers do not print headers of table output format (default print headers) + -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. + Example for machines: + + metalctl machine list -o template --template "{{ .id }}:{{ .size.id }}" + + + --yes-i-really-mean-it skips security prompts (which can be dangerous to set blindly because actions can lead to data loss or additional costs) +``` + +### SEE ALSO + +* [metalctl tenant](metalctl_tenant.md) - manage tenant entities + diff --git a/go.mod b/go.mod index 095de6bf..62a59535 100644 --- a/go.mod +++ b/go.mod @@ -9,8 +9,8 @@ require ( github.com/go-openapi/runtime v0.26.0 github.com/go-openapi/strfmt v0.21.7 github.com/google/go-cmp v0.5.9 - github.com/metal-stack/metal-go v0.22.12 - github.com/metal-stack/metal-lib v0.13.1 + github.com/metal-stack/metal-go v0.24.0 + github.com/metal-stack/metal-lib v0.13.3 github.com/metal-stack/updater v1.1.5 github.com/metal-stack/v v1.0.3 github.com/olekukonko/tablewriter v0.0.5 @@ -83,7 +83,7 @@ require ( github.com/google/go-github/v53 v53.2.0 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/nftables v0.1.1-0.20230115205135-9aa6fdf5a28c // indirect - github.com/google/uuid v1.3.0 // indirect + github.com/google/uuid v1.3.1 // indirect github.com/gorilla/mux v1.8.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hdevalence/ed25519consensus v0.1.0 // indirect @@ -116,7 +116,7 @@ require ( github.com/mdlayher/netlink v1.7.2 // indirect github.com/mdlayher/sdnotify v1.0.0 // indirect github.com/mdlayher/socket v0.4.1 // indirect - github.com/metal-stack/security v0.6.6 // indirect + github.com/metal-stack/security v0.6.7 // indirect github.com/miekg/dns v1.1.55 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-ps v1.0.0 // indirect @@ -147,7 +147,7 @@ require ( github.com/vishvananda/netlink v1.2.1-beta.2 // indirect github.com/vishvananda/netns v0.0.4 // indirect github.com/x448/float16 v0.8.4 // indirect - go.mongodb.org/mongo-driver v1.12.0 // indirect + go.mongodb.org/mongo-driver v1.12.1 // indirect go.opentelemetry.io/otel v1.14.0 // indirect go.opentelemetry.io/otel/trace v1.14.0 // indirect go.uber.org/multierr v1.11.0 // indirect diff --git a/go.sum b/go.sum index ce9d873f..162d9918 100644 --- a/go.sum +++ b/go.sum @@ -330,8 +330,8 @@ github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLe github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= @@ -443,12 +443,12 @@ 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.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= -github.com/metal-stack/metal-go v0.22.12 h1:WUwMaTgZK6kNNUdsNwHG5/oA9R5BFtJRuFbQzwFTFco= -github.com/metal-stack/metal-go v0.22.12/go.mod h1:n0KALbtB6JGAICDmgSU5B/jekEFODuqcluTHEAXMPng= -github.com/metal-stack/metal-lib v0.13.1 h1:DxtvFZnV3uyv0C+ZpXSlo9eozDv/hzS+8bfjmar5Z5k= -github.com/metal-stack/metal-lib v0.13.1/go.mod h1:l18VEuS1YkxnVE35iF8AMP6QRxoYjRZ9e2NE3aGxVY0= -github.com/metal-stack/security v0.6.6 h1:KSPNN8YZd2EJEjsJ0xCBcd5o53uU0iFupahHA9Twuh0= -github.com/metal-stack/security v0.6.6/go.mod h1:WchPm3+2Xjj1h7AxM+DsnR9EWgLw+ktoGCl/0gcmgSA= +github.com/metal-stack/metal-go v0.24.0 h1:Y+nGpA5iUhIX6UOCY4yVuJ8S+dsge02rEdpVkIyif9s= +github.com/metal-stack/metal-go v0.24.0/go.mod h1:jNJ0dWIBRwKeJoP+RGqTyE5qLsdZFISFrNHU5m3IDwA= +github.com/metal-stack/metal-lib v0.13.3 h1:BOhwcKHILmBZd2pz2YMOhj8QxzDaz3G0F/CGuYhnu8o= +github.com/metal-stack/metal-lib v0.13.3/go.mod h1:BAR7fjdoV7DDg8i9GpJQBDaNSFirOcBs0vLYTBnhHQU= +github.com/metal-stack/security v0.6.7 h1:8wstGy0pdUmphVclAlT+9RKQmx9lF+cIGklJZAB5cIc= +github.com/metal-stack/security v0.6.7/go.mod h1:dXyrQ8PYZuUiodWFQ/NwSROxu6tajwRBc5yR/PoK5uE= github.com/metal-stack/updater v1.1.5 h1:+Q4yyqQ50t+lQTjNkkQhCk7p//a+p+nyD0jt+DjIXFk= github.com/metal-stack/updater v1.1.5/go.mod h1:7jBXg9MWVcyQSFgLcOdS0xfC7dApizGLewTU7rMkIlI= github.com/metal-stack/v v1.0.3 h1:Sh2oBlnxrCUD+mVpzfC8HiqL045YWkxs0gpTvkjppqs= @@ -590,8 +590,8 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t go.mongodb.org/mongo-driver v1.7.3/go.mod h1:NqaYOwnXWr5Pm7AOpO5QFxKJ503nbMse/R79oO62zWg= go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng= go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAVEYRhCXrA8= -go.mongodb.org/mongo-driver v1.12.0 h1:aPx33jmn/rQuJXPQLZQ8NtfPQG8CaqgLThFtqRb0PiE= -go.mongodb.org/mongo-driver v1.12.0/go.mod h1:AZkxhPnFJUoH7kZlFkVKucV20K387miPfm7oimrSmK0= +go.mongodb.org/mongo-driver v1.12.1 h1:nLkghSU8fQNaK7oUmDhQFsnrtcoNy7Z6LVFKsEecqgE= +go.mongodb.org/mongo-driver v1.12.1/go.mod h1:/rGBTebI3XYboVmgz+Wv3Bcbl3aD0QF9zl6kDDw18rQ= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=