diff --git a/go.mod b/go.mod index 2907a24d5c..8775a6d844 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/mattn/go-colorable v0.1.6 // indirect github.com/mitchellh/go-homedir v1.1.0 github.com/mongodb/go-client-mongodb-atlas v0.2.1-0.20200423095452-c3661e38ae5c - github.com/mongodb/go-client-mongodb-ops-manager v0.2.0 + github.com/mongodb/go-client-mongodb-ops-manager v0.2.1 github.com/pelletier/go-toml v1.6.0 // indirect github.com/spf13/afero v1.2.2 github.com/spf13/cast v1.3.1 // indirect diff --git a/go.sum b/go.sum index ee9dc65c43..674571bd9b 100644 --- a/go.sum +++ b/go.sum @@ -104,8 +104,8 @@ github.com/mongodb/go-client-mongodb-atlas v0.2.0 h1:UH02byrzAlboirAMgV0sMIJqXbE github.com/mongodb/go-client-mongodb-atlas v0.2.0/go.mod h1:LS8O0YLkA+sbtOb3fZLF10yY3tJM+1xATXMJ3oU35LU= github.com/mongodb/go-client-mongodb-atlas v0.2.1-0.20200423095452-c3661e38ae5c h1:k1l2qLwwy4eCOli7f3oRWDbtIvXQF5SpO7JAXXcW3qI= github.com/mongodb/go-client-mongodb-atlas v0.2.1-0.20200423095452-c3661e38ae5c/go.mod h1:LS8O0YLkA+sbtOb3fZLF10yY3tJM+1xATXMJ3oU35LU= -github.com/mongodb/go-client-mongodb-ops-manager v0.2.0 h1:OfC6UXGJqivsMTNnxnZBu+e4Oz4s/9RdSwpv23eWBus= -github.com/mongodb/go-client-mongodb-ops-manager v0.2.0/go.mod h1:IWna/sWCgLSOLUeOYLg2nYf6HJcQnnqNI73K5NEMq9Q= +github.com/mongodb/go-client-mongodb-ops-manager v0.2.1 h1:60nEpqO5P74K5Y81OP3HHNt3e8n0NKKDX5wWqZDLuLY= +github.com/mongodb/go-client-mongodb-ops-manager v0.2.1/go.mod h1:IWna/sWCgLSOLUeOYLg2nYf6HJcQnnqNI73K5NEMq9Q= github.com/mwielbut/pointy v1.1.0 h1:U5/YEfoIkaGCHv0St3CgjduqXID4FNRoyZgLM1kY9vg= github.com/mwielbut/pointy v1.1.0/go.mod h1:MvvO+uMFj9T5DMda33HlvogsFBX7pWWKAkFIn4teYwY= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= diff --git a/internal/cli/cloud_manager.go b/internal/cli/cloud_manager.go index fa9a531057..6a8eadc457 100644 --- a/internal/cli/cloud_manager.go +++ b/internal/cli/cloud_manager.go @@ -36,6 +36,7 @@ func CloudManagerBuilder() *cobra.Command { cmd.AddCommand(AtlasEventsBuilder()) cmd.AddCommand(OpsManagerProcessesBuilder()) cmd.AddCommand(OpsManagerMeasurementsBuilder()) + cmd.AddCommand(OpsManagerLogsBuilder()) return cmd } diff --git a/internal/cli/ops_manager.go b/internal/cli/ops_manager.go index 505d77352b..e477f23da1 100644 --- a/internal/cli/ops_manager.go +++ b/internal/cli/ops_manager.go @@ -37,6 +37,7 @@ func OpsManagerBuilder() *cobra.Command { cmd.AddCommand(AtlasEventsBuilder()) cmd.AddCommand(OpsManagerProcessesBuilder()) cmd.AddCommand(OpsManagerMeasurementsBuilder()) + cmd.AddCommand(OpsManagerLogsBuilder()) return cmd } diff --git a/internal/cli/ops_manager_logs.go b/internal/cli/ops_manager_logs.go new file mode 100644 index 0000000000..239b6298f0 --- /dev/null +++ b/internal/cli/ops_manager_logs.go @@ -0,0 +1,31 @@ +// Copyright 2020 MongoDB Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cli + +import ( + "github.com/mongodb/mongocli/internal/description" + "github.com/spf13/cobra" +) + +func OpsManagerLogsBuilder() *cobra.Command { + cmd := &cobra.Command{ + Use: "logs", + Aliases: []string{"log"}, + Short: description.LogCollection, + } + cmd.AddCommand(OpsManagerLogsCollectOptsBuilder()) + + return cmd +} diff --git a/internal/cli/ops_manager_logs_collect.go b/internal/cli/ops_manager_logs_collect.go new file mode 100644 index 0000000000..491a6962ce --- /dev/null +++ b/internal/cli/ops_manager_logs_collect.go @@ -0,0 +1,103 @@ +// Copyright 2020 MongoDB Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cli + +import ( + "fmt" + "strings" + + om "github.com/mongodb/go-client-mongodb-ops-manager/opsmngr" + "github.com/mongodb/mongocli/internal/description" + "github.com/mongodb/mongocli/internal/flags" + "github.com/mongodb/mongocli/internal/json" + "github.com/mongodb/mongocli/internal/search" + "github.com/mongodb/mongocli/internal/store" + "github.com/mongodb/mongocli/internal/usage" + "github.com/spf13/cobra" +) + +type opsManagerLogsCollectOpts struct { + globalOpts + resourceType string + resourceName string + logTypes []string + sizeRequestedPerFileBytes int64 + redacted bool + store store.LogCollector +} + +func (opts *opsManagerLogsCollectOpts) initStore() error { + var err error + opts.store, err = store.New() + return err +} + +func (opts *opsManagerLogsCollectOpts) Run() error { + result, err := opts.store.Collect(opts.ProjectID(), opts.newLog()) + if err != nil { + return err + } + return json.PrettyPrint(result) +} + +func (opts *opsManagerLogsCollectOpts) newLog() *om.LogCollectionJob { + return &om.LogCollectionJob{ + ResourceType: opts.resourceType, + ResourceName: opts.resourceName, + Redacted: &opts.redacted, + SizeRequestedPerFileBytes: opts.sizeRequestedPerFileBytes, + LogTypes: opts.logTypes, + } +} + +// mongocli om logs collect resourceType resourceName --sizeRequestedPerFileBytes size --type type --redacted redacted [--projectId projectId] +func OpsManagerLogsCollectOptsBuilder() *cobra.Command { + opts := &opsManagerLogsCollectOpts{} + cmd := &cobra.Command{ + Use: "collect [resourceType] [resourceName]", + Short: description.StartLogCollectionJob, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 2 { + return fmt.Errorf("accepts %d arg(s), received %d", 2, len(args)) + } + + args[0] = strings.ToLower(args[0]) + if !search.StringInSlice(cmd.ValidArgs, args[0]) { + return fmt.Errorf("invalid resource type '%s', expected one of %v", args[0], cmd.ValidArgs) + } + return nil + }, + ValidArgs: []string{"cluster", "process", "replicaset"}, + PreRunE: func(cmd *cobra.Command, args []string) error { + return opts.PreRunE(opts.initStore) + }, + RunE: func(cmd *cobra.Command, args []string) error { + opts.resourceType = args[0] + opts.resourceName = args[1] + return opts.Run() + }, + } + + cmd.Flags().StringArrayVar(&opts.logTypes, flags.Type, nil, usage.LogTypes) + cmd.Flags().Int64Var(&opts.sizeRequestedPerFileBytes, flags.SizeRequestedPerFileBytes, 0, usage.SizeRequestedPerFileBytes) + cmd.Flags().BoolVar(&opts.redacted, flags.Redacted, false, usage.LogRedacted) + + _ = cmd.MarkFlagRequired(flags.SizeRequestedPerFileBytes) + _ = cmd.MarkFlagRequired(flags.Type) + + cmd.Flags().StringVar(&opts.projectID, flags.ProjectID, "", usage.ProjectID) + + return cmd +} diff --git a/internal/cli/ops_manager_logs_collect_test.go b/internal/cli/ops_manager_logs_collect_test.go new file mode 100644 index 0000000000..a9e39c7873 --- /dev/null +++ b/internal/cli/ops_manager_logs_collect_test.go @@ -0,0 +1,49 @@ +// Copyright 2020 MongoDB Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package cli + +import ( + "testing" + + "github.com/golang/mock/gomock" + om "github.com/mongodb/go-client-mongodb-ops-manager/opsmngr" + "github.com/mongodb/mongocli/internal/mocks" +) + +func TestOpsManagerLogsCollectOpts_Run(t *testing.T) { + ctrl := gomock.NewController(t) + mockStore := mocks.NewMockLogCollector(ctrl) + + defer ctrl.Finish() + + expected := &om.LogCollectionJob{ID: "1"} + + listOpts := &opsManagerLogsCollectOpts{ + redacted: false, + sizeRequestedPerFileBytes: 64, + resourceType: "CLUSTER", + resourceName: "", + logTypes: []string{"AUTOMATION_AGENT"}, + store: mockStore, + } + + mockStore. + EXPECT().Collect(listOpts.projectID, listOpts.newLog()). + Return(expected, nil). + Times(1) + + if err := listOpts.Run(); err != nil { + t.Fatalf("Run() unexpected error: %v", err) + } +} diff --git a/internal/description/description.go b/internal/description/description.go index abeed0b3ca..0d1dbac364 100644 --- a/internal/description/description.go +++ b/internal/description/description.go @@ -53,50 +53,52 @@ const ( The dbusers command retrieves, creates and modifies the MongoDB database users in your cluster. Each user has a set of roles that provide access to the project’s databases. A user’s roles apply to all the clusters in the project.` - CreateDBUser = "Create a database user for a project." - DeleteDBUser = "Delete a database user for a project." - ListDBUsers = "List Atlas database users for a project." - ListEvents = "List events for an organization or project" - UpdateDBUser = "Update a MongoDB dbuser in Atlas." - ProcessMeasurements = "Get measurements for a given host." - Disks = "List available disks or disks measurements for a given host." - ListDisks = "List available disks for a given host." - DescribeDisks = "Describe disks measurements for a given host partition." - Databases = "List available databases or databases measurements for a given host." - ListDatabases = "List available databases for a given host." - Whitelist = "Manage the IP whitelist for a project." - CreateWhitelist = "Create an IP whitelist for a project." - DeleteWhitelist = "Delete a database user for a project." - DescribeWhitelist = "Describe an Atlas whitelist." - ListWhitelist = "List Atlas whitelist for a project." - CloudManager = "Cloud Manager operations." - ShutdownOMCluster = "Shutdown a Cloud Manager cluster." - StartUpOMCluster = "Startup a Cloud Manager cluster." - UpdateOMCluster = "Update a Cloud Manager cluster." - ConfigDescription = "Configure the tool." - SetConfig = "Configure the tool." - IAM = "Authentication operations." - Organization = "Organization operations." - OrganizationLong = "Create, list and manage your MongoDB organizations." - CreateOrganization = "Create an organization." - DeleteOrganization = "Delete an organization." - ListOrganizations = "List organizations." - Projects = "Project operations." - ProjectsLong = "Create, list and manage your MongoDB projects." - CreateProject = "Create a project." - DeleteProject = "Delete a project." - ListProjects = "List projects." - OpsManager = "Ops Manager operations." - ListGlobalAlerts = "List global alerts." - Automation = "Manage Ops Manager automation config." - ShowAutomationStatus = "Show the current status of the automation config." - Global = "Manage Ops Manager global properties." - Owner = "Manage Ops Manager owners." - CreateOwner = "Create the first user for Ops Manager." - Servers = "Manage Ops Manager servers." - ListServer = "List all available servers running an automation agent for the given project." - Security = "Manage clusters security configuration." - EnableSecurity = "Enable authentication mechanisms for the project." - Events = "Manage events for your project." - Measurements = "Get measurements on the state of the MongoDB process." + CreateDBUser = "Create a database user for a project." + DeleteDBUser = "Delete a database user for a project." + ListDBUsers = "List Atlas database users for a project." + ListEvents = "List events for an organization or project" + UpdateDBUser = "Update a MongoDB dbuser in Atlas." + ProcessMeasurements = "Get measurements for a given host." + Disks = "List available disks or disks measurements for a given host." + ListDisks = "List available disks for a given host." + DescribeDisks = "Describe disks measurements for a given host partition." + Databases = "List available databases or databases measurements for a given host." + ListDatabases = "List available databases for a given host." + Whitelist = "Manage the IP whitelist for a project." + CreateWhitelist = "Create an IP whitelist for a project." + DeleteWhitelist = "Delete a database user for a project." + DescribeWhitelist = "Describe an Atlas whitelist." + ListWhitelist = "List Atlas whitelist for a project." + CloudManager = "Cloud Manager operations." + ShutdownOMCluster = "Shutdown a Cloud Manager cluster." + StartUpOMCluster = "Startup a Cloud Manager cluster." + UpdateOMCluster = "Update a Cloud Manager cluster." + ConfigDescription = "Configure the tool." + SetConfig = "Configure the tool." + IAM = "Authentication operations." + Organization = "Organization operations." + OrganizationLong = "Create, list and manage your MongoDB organizations." + CreateOrganization = "Create an organization." + DeleteOrganization = "Delete an organization." + ListOrganizations = "List organizations." + Projects = "Project operations." + ProjectsLong = "Create, list and manage your MongoDB projects." + CreateProject = "Create a project." + DeleteProject = "Delete a project." + ListProjects = "List projects." + OpsManager = "Ops Manager operations." + ListGlobalAlerts = "List global alerts." + Automation = "Manage Ops Manager automation config." + ShowAutomationStatus = "Show the current status of the automation config." + Global = "Manage Ops Manager global properties." + LogCollection = "Manage log collection jobs." + StartLogCollectionJob = "Start a job to collect logs." + Owner = "Manage Ops Manager owners." + CreateOwner = "Create the first user for Ops Manager." + Servers = "Manage Ops Manager servers." + ListServer = "List all available servers running an automation agent for the given project." + Security = "Manage clusters security configuration." + EnableSecurity = "Enable authentication mechanisms for the project." + Events = "Manage events for your project." + Measurements = "Get measurements on the state of the MongoDB process." ) diff --git a/internal/flags/flags.go b/internal/flags/flags.go index fb0cdc1d88..37788125e2 100644 --- a/internal/flags/flags.go +++ b/internal/flags/flags.go @@ -115,4 +115,6 @@ const ( Normalization = "normalization" // Normalization flag Backwards = "backwards" // Backwards flag Strength = "strength" // Strength flag + SizeRequestedPerFileBytes = "sizeRequestedPerFileBytes" //SizeRequestedPerFileBytes flag + Redacted = "redacted" // Redacted flag ) diff --git a/internal/mocks/mock_logs.go b/internal/mocks/mock_logs.go index b5b665fefa..7281e9d9e3 100644 --- a/internal/mocks/mock_logs.go +++ b/internal/mocks/mock_logs.go @@ -7,6 +7,7 @@ package mocks import ( gomock "github.com/golang/mock/gomock" mongodbatlas "github.com/mongodb/go-client-mongodb-atlas/mongodbatlas" + opsmngr "github.com/mongodb/go-client-mongodb-ops-manager/opsmngr" io "io" reflect "reflect" ) @@ -47,3 +48,41 @@ func (mr *MockLogsDownloaderMockRecorder) DownloadLog(arg0, arg1, arg2, arg3, ar mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DownloadLog", reflect.TypeOf((*MockLogsDownloader)(nil).DownloadLog), arg0, arg1, arg2, arg3, arg4) } + +// MockLogCollector is a mock of LogCollector interface +type MockLogCollector struct { + ctrl *gomock.Controller + recorder *MockLogCollectorMockRecorder +} + +// MockLogCollectorMockRecorder is the mock recorder for MockLogCollector +type MockLogCollectorMockRecorder struct { + mock *MockLogCollector +} + +// NewMockLogCollector creates a new mock instance +func NewMockLogCollector(ctrl *gomock.Controller) *MockLogCollector { + mock := &MockLogCollector{ctrl: ctrl} + mock.recorder = &MockLogCollectorMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockLogCollector) EXPECT() *MockLogCollectorMockRecorder { + return m.recorder +} + +// Collect mocks base method +func (m *MockLogCollector) Collect(arg0 string, arg1 *opsmngr.LogCollectionJob) (*opsmngr.LogCollectionJob, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Collect", arg0, arg1) + ret0, _ := ret[0].(*opsmngr.LogCollectionJob) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Collect indicates an expected call of Collect +func (mr *MockLogCollectorMockRecorder) Collect(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Collect", reflect.TypeOf((*MockLogCollector)(nil).Collect), arg0, arg1) +} diff --git a/internal/store/logs.go b/internal/store/logs.go index 6a52c33bd9..219ce0f035 100644 --- a/internal/store/logs.go +++ b/internal/store/logs.go @@ -20,6 +20,7 @@ import ( "io" atlas "github.com/mongodb/go-client-mongodb-atlas/mongodbatlas" + om "github.com/mongodb/go-client-mongodb-ops-manager/opsmngr" "github.com/mongodb/mongocli/internal/config" ) @@ -27,6 +28,20 @@ type LogsDownloader interface { DownloadLog(string, string, string, io.Writer, *atlas.DateRangetOptions) error } +type LogCollector interface { + Collect(string, *om.LogCollectionJob) (*om.LogCollectionJob, error) +} + +func (s *Store) Collect(groupID string, newLog *om.LogCollectionJob) (*om.LogCollectionJob, error) { + switch s.service { + case config.OpsManagerService, config.CloudManagerService: + log, _, err := s.client.(*om.Client).LogCollections.Create(context.Background(), groupID, newLog) + return log, err + default: + return nil, fmt.Errorf("unsupported service: %s", s.service) + } +} + // ProcessDisks encapsulate the logic to manage different cloud providers func (s *Store) DownloadLog(groupID, host, name string, out io.Writer, opts *atlas.DateRangetOptions) error { switch s.service { diff --git a/internal/usage/usage.go b/internal/usage/usage.go index 1a3d4249b2..8ba2e8cfda 100644 --- a/internal/usage/usage.go +++ b/internal/usage/usage.go @@ -81,6 +81,9 @@ const ( RSName = "The replica set that the index is built on." Key = "Index keys. Should be formatted as field:type." Unique = "Create a unique key index." + LogTypes = "Array of strings specifying the types of logs to collect." + SizeRequestedPerFileBytes = "Size for each log file in bytes." + LogRedacted = "If set to true, emails, hostnames, IP addresses, and namespaces in API responses involving this job are replaced with random string values." Sparse = "Create a sparse index." Locale = "Locale that the ICU defines." CaseLevel = "If set to true, the index uses case comparison. This field applies only if the strength level is set to 1 or 2."