diff --git a/.github/workflows/config.hcl b/.github/workflows/config.hcl new file mode 100644 index 00000000..e74d17ed --- /dev/null +++ b/.github/workflows/config.hcl @@ -0,0 +1,20 @@ +source = ["./sumocli"] +bundle_id = "com.thepublicclouds.sumocli" + +apple_id { + username = "@env:AC_USERNAME" + password = "@env:AC_PASSWORD" +} + +sign { + application_identity = "08C9A4B04024CDEF2C5D93593710C60591FB4614" +} + +dmg { + output_path = "sumocli.dmg" + volume_name = "sumocli" +} + +zip { + output_path = "sumocli-macos-arm64.zip" +} diff --git a/.github/workflows/sumocli-build.yml b/.github/workflows/sumocli-build.yml index db11b17a..354d347c 100644 --- a/.github/workflows/sumocli-build.yml +++ b/.github/workflows/sumocli-build.yml @@ -154,6 +154,9 @@ jobs: -X 'github.com/wizedkyle/sumocli/internal/build.Build=$build' ` -X 'github.com/wizedkyle/sumocli/internal/build.Date=$time'" ` ./cmd/sumocli + env: + GOOS: darwin + GOARCH: amd64 - name: Zip Releases run: | zip -r sumocli-macos-amd64.zip sumocli @@ -181,18 +184,41 @@ jobs: - name: Download Dependencies run: go mod download - name: Build Sumocli - shell: pwsh + shell: bash run: | - $build = $Env:GITHUB_RUN_NUMBER - $time = Get-Date - go build GOOS=darwin GOARCH=arm64 -ldflags ` - "-X 'github.com/wizedkyle/sumocli/internal/build.Version=${{ needs.create_semver.outputs.semvertag }}' ` - -X 'github.com/wizedkyle/sumocli/internal/build.Build=$build' ` - -X 'github.com/wizedkyle/sumocli/internal/build.Date=$time'" ` + time=$(date) + go build -ldflags \ + "-X 'github.com/wizedkyle/sumocli/internal/build.Version=${{ needs.create_semver.outputs.semvertag }}' \ + -X 'github.com/wizedkyle/sumocli/internal/build.Build=$GITHUB_RUN_NUMBER' \ + -X 'github.com/wizedkyle/sumocli/internal/build.Date=$time'" \ ./cmd/sumocli - - name: Zip Releases + env: + GOOS: darwin + GOARCH: arm64 + - name: Install gon + shell: bash run: | - zip -r sumocli-macos-arm64.zip sumocli + brew tap mitchellh/gon + brew install mitchellh/gon/gon + - name: Sign and Notarize Binary + shell: bash + run: | + # Install signing certificate + CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12 + KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db + echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode --output $CERTIFICATE_PATH + security create-keychain -p $KEYCHAIN_PASSWORD $KEYCHAIN_PATH + security set-keychain-settings -lut 21600 $KEYCHAIN_PATH + security unlock-keychain -p $KEYCHAIN_PASSWORD $KEYCHAIN_PATH + security import $CERTIFICATE_PATH -P $P12_PASSWORD -A -t cert -f pkcs12 -k $KEYCHAIN_PATH + security list-keychain -d user -s $KEYCHAIN_PATH + + # Sign macOS build + gon .github/workflows/config.hcl + env: + APPLE_DEV_CERT: ${{ secrets.APPLE_DEV_CERT }} + APPLE_DEV_CERT_PASSWORD: ${{ secrets.APPLE_DEV_CERT_PASSWORD }} + KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} - name: Upload macOS Release Asset uses: actions/upload-release-asset@v1 env: diff --git a/.gitignore b/.gitignore index b59e1a2e..53c2b909 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ dist/ -sumocli.exe \ No newline at end of file +sumocli.exedist/ diff --git a/api/content.go b/api/content.go new file mode 100644 index 00000000..bc5081c3 --- /dev/null +++ b/api/content.go @@ -0,0 +1,294 @@ +package api + +type DashboardSyncDefinition struct { + Type string `json:"type"` + Name string `json:"name"` + Description string `json:"description"` + DetailLevel int `json:"detailLevel"` + Properties string `json:"properties"` + Panels []reportPanelSyncDefinition `json:"panels"` + Filters []filtersSyncDefinition `json:"filters"` +} + +type FolderSyncDefinition struct { + Type string `json:"type"` + Name string `json:"name"` + Description string `json:"description"` + Children []contentSyncDefinition `json:"children"` +} + +type GetContentResponse struct { + CreatedAt string `json:"createdAt"` + CreatedBy string `json:"createdBy"` + ModifiedAt string `json:"modifiedAt"` + ModifiedBy string `json:"modifiedBy"` + Id string `json:"id"` + Name string `json:"name"` + ItemType string `json:"itemType"` + ParentId string `json:"parentId"` + Permissions []string `json:"permissions"` +} + +type GetPathResponse struct { + Path string `json:"path"` +} + +type MetricsSavedSearchSyncDefinition struct { + Type string `json:"type"` + Name string `json:"name"` + Description string `json:"description"` + TimeRange timeRangeDefinition `json:"timeRange"` + LogQuery string `json:"logQuery"` + MetricsQueries []metricsQueriesDefinition `json:"metricsQueries"` + DesiredQuantizationInSecs int `json:"desiredQuantizationInSecs"` + Properties string `json:"properties"` +} + +type MetricsSearchSyncDefinition struct { + Type string `json:"type"` + Name string `json:"name"` + TimeRange timeRangeDefinition `json:"timeRange"` + Description string `json:"description"` + Queries []queries `json:"queries"` + VisualSettings string `json:"visualSettings"` +} + +type LookupTableSyncDefinition struct { + Type string `json:"type"` + Name string `json:"name"` + Description string `json:"description"` + Fields []fields `json:"fields"` + PrimaryKeys []string `json:"primaryKeys"` + TTL int `json:"ttl"` + SizeLimitAction string `json:"sizeLimitAction"` +} + +type MewboardSyncDefinition struct { + Type string `json:"type"` + Name string `json:"name"` + Description string `json:"description"` + Title string `json:"title"` + RootPanel rootPanelDefinition `json:"rootPanel"` + Theme string `json:"theme"` + TopologyLabelMap topologyLabelMap `json:"topologyLabelMap"` + RefreshInterval int `json:"refreshInterval"` + TimeRange timeRangeDefinition `json:"timeRange"` + Layout layout `json:"layout"` + Panels panelsDefinition `json:"panels"` + Variables variablesDefinition `json:"variables"` + ColoringRules coloringRulesDefinition `json:"coloringRules"` +} + +type ResponseType struct { + Type string `json:"type"` +} + +type SavedSearchWithScheduleSyncDefinition struct { + Type string `json:"type"` + Name string `json:"name"` + Search search `json:"search"` + SearchSchedule searchSchedule `json:"searchSchedule"` + Description string `json:"description"` +} + +type StartExportResponse struct { + Id string `json:"id"` +} + +type ExportStatusResponse struct { + Status string `json:"status"` + StatusMessage string `json:"statusMessage,omitempty"` + Error exportError `json:"error,omitempty"` +} + +type autoComplete struct { + AutoCompleteType string `json:"autoCompleteType"` + AutoCompleteKey string `json:"autoCompleteKey"` + AutoCompleteValues []autoCompleteValues `json:"autoCompleteValues"` + LookupFileName string `json:"lookupFileName"` + LookupLabelColumn string `json:"lookupLabelColumn"` + LookupValueColumn string `json:"lookupValueColumn"` +} + +type autoCompleteValues struct { + Label string `json:"label"` + Value string `json:"value"` +} + +type autoParsingInfo struct { + Mode string `json:"mode"` +} + +type coloringRulesDefinition struct { + Scope string `json:"scope"` + SingleSeriesAggregateFunction string `json:"singleSeriesAggregateFunction"` + MultipleSeriesAggregateFunction string `json:"multipleSeriesAggregateFunction"` + ColorThresholds colorThresholds `json:"colorThresholds"` +} + +type colorThresholds struct { + Color string `json:"color"` + Min int `json:"min"` + Max int `json:"max"` +} + +type contentSyncDefinition struct { + Type string `json:"type"` + Name string `json:"name"` +} + +type exportError struct { + Code string `json:"code,omitempty"` + Message string `json:"message,omitempty"` + Detail string `json:"detail,omitempty"` +} + +type fields struct { + FieldName string `json:"fieldName"` + FieldType string `json:"fieldType"` +} + +type filtersSyncDefinition struct { + FieldName string `json:"fieldName"` + Label string `json:"label"` + DefaultValue string `json:"defaultValue"` + FilterType string `json:"filterType"` + Properties string `json:"properties"` + PanelIds []string `json:"panelIds"` +} + +type layout struct { + LayoutType string `json:"layouType"` + LayoutStructures []layoutStructure `json:"layoutStructures"` +} + +type layoutStructure struct { + Key string `json:"key"` + Structure string `json:"structure"` +} + +type metricsQueriesDefinition struct { + Query string `json:"query"` + RowId string `json:"rowId"` +} + +type panelsDefinition struct { + Id string `json:"id"` + Key string `json:"key"` + Title string `json:"title"` + visualSettings string `json:"visualSettings"` + KeepVisualSettingsConsistentWithParent bool `json:"keepVisualSettingsConsistentWithParent"` + PanelType string `json:"panelType"` +} + +type queries struct { + QueryString string `json:"queryString"` + QueryType string `json:"queryType"` + QueryKey string `json:"queryKey"` +} + +type queryParameters struct { + Name string `json:"name"` + Label string `json:"label"` + Description string `json:"description"` + DataType string `json:"dataType"` + Value string `json:"value"` + AutoComplete autoComplete `json:"autoComplete"` +} + +type rootPanelDefinition struct { + Id string `json:"id"` + Key string `json:"key"` + Title string `json:"title"` + VisualSettings string `json:"visualSettings"` + KeepVisualSettingsConsistentWithParent bool `json:"keepVisualSettingsConsistentWithParent"` + PanelType string `json:"panelType"` + Layout layout `json:"layout"` + Panels []panelsDefinition `json:"panels"` + Variables []variablesDefinition `json:"variables"` + ColoringRules []coloringRulesDefinition `json:"coloringRules"` +} + +type reportPanelSyncDefinition struct { + Name string `json:"name"` + ViewerType string `json:"viewerType"` + DetailLevel int `json:"detailLevel"` + QueryString string `json:"queryString"` + MetricsQueries []metricsQueriesDefinition `json:"metricsQueries"` + TimeRange timeRangeDefinition `json:"timeRange"` + X int `json:"x"` + Y int `json:"Y"` + Width int `json:"width"` + Height int `json:"height"` + Properties string `json:"properties"` + Id string `json:"id"` + DesiredQuantizationInSecs int `json:"desiredQuantizationInSecs"` + QueryParameters []queryParameters `json:"queryParameters"` + AutoParsingInfo autoParsingInfo `json:"autoParsingInfo"` +} + +type search struct { + QueryText string `json:"queryText"` + DefaultTimeRange string `json:"defaultTimeRange"` + ByReceiptTime bool `json:"byReceiptTime"` + ViewName string `json:"viewName"` + ViewStartTime string `json:"viewStartTime"` + QueryParameters []queryParameters `json:"queryParameters"` + ParsingMode string `json:"parsingMode"` +} + +type searchSchedule struct { + CronExpression string `json:"cronExpression"` + DisplayableTimeRange string `json:"displayableTimeRange"` + ParseableTimeRange timeRangeDefinition `json:"parseableTimeRange"` + TimeZone string `json:"timeZone"` + Threshold searchScheduleThreshold `json:"threshold"` + Notification searchScheduleNotification `json:"notification"` + ScheduleType string `json:"scheduleType"` + MuteErrorEmails bool `json:"muteErrorEmails"` + Parameters []searchScheduleParamters `json:"parameters"` +} + +type searchScheduleNotification struct { + TaskType string `json:"taskType"` +} + +type searchScheduleParamters struct { + Name string `json:"name"` + Value string `json:"value"` +} + +type searchScheduleThreshold struct { + ThresholdType string `json:"thresholdType"` + Operator string `json:"operator"` + Count int `json:"count"` +} + +type timeRangeDefinition struct { + Type string `json:"type"` + From timeRangeFromDefinition `json:"from"` +} + +type timeRangeFromDefinition struct { + Type string `json:"type"` + RelativeTime string `json:"relativeTime"` +} + +type topologyLabelMap struct { + Service []string `json:"service"` +} + +type variablesDefinition struct { + Id string `json:"id"` + Name string `json:"name"` + DisplayName string `json:"displayName"` + DefaultValue string `json:"defaultValue"` + SourceDefinition variablesSourceDefinition `json:"sourceDefinition"` + AllowMultiSelect bool `json:"allowMultiSelect"` + IncludeAllOption bool `json:"includeAllOption"` + HideFromUI bool `json:"hideFromUI"` +} + +type variablesSourceDefinition struct { + VariableSourceType string `json:"variableSourceType"` +} diff --git a/api/lookup-tables.go b/api/lookup-tables.go new file mode 100644 index 00000000..2c71e4ae --- /dev/null +++ b/api/lookup-tables.go @@ -0,0 +1,9 @@ +package api + +type GetLookupTableResponse struct { + CreatedAt string `json:"createdAt"` + CreatedBy string `json:"createdBy"` + ModifiedAt string `json:"modifiedAt"` + ModifiedBy string `json:"modifiedBy"` + Description string `json:"description"` +} diff --git a/pkg/cmd/collectors/create/create_test.go b/pkg/cmd/collectors/create/create_test.go index 49980eb5..42b2cf78 100644 --- a/pkg/cmd/collectors/create/create_test.go +++ b/pkg/cmd/collectors/create/create_test.go @@ -9,7 +9,7 @@ import ( func TestCollector(t *testing.T) { httpmock.Activate() defer httpmock.DeactivateAndReset() - httpmock.RegisterResponder("POST", "https://api.au.sumologic.com/api/v1/collectors", + httpmock.RegisterResponder("POST", "v1/collectors", httpmock.NewStringResponder(200, ` { "collector":{ diff --git a/pkg/cmd/content/content.go b/pkg/cmd/content/content.go new file mode 100644 index 00000000..6961977c --- /dev/null +++ b/pkg/cmd/content/content.go @@ -0,0 +1,24 @@ +package content + +import ( + "github.com/spf13/cobra" + cmdContentExportResult "github.com/wizedkyle/sumocli/pkg/cmd/content/export-result" + cmdContentExportStatus "github.com/wizedkyle/sumocli/pkg/cmd/content/export-status" + cmdContentGet "github.com/wizedkyle/sumocli/pkg/cmd/content/get" + cmdContentGetPath "github.com/wizedkyle/sumocli/pkg/cmd/content/get-path" + cmdContentStartExport "github.com/wizedkyle/sumocli/pkg/cmd/content/start-export" +) + +func NewCmdContent() *cobra.Command { + cmd := &cobra.Command{ + Use: "content ", + Short: "Manage content", + Long: "Commands that allow you to manage content in your Sumo Logic tenant", + } + cmd.AddCommand(cmdContentExportResult.NewCmdExportResult()) + cmd.AddCommand(cmdContentExportStatus.NewCmdExportStatus()) + cmd.AddCommand(cmdContentGet.NewCmdGet()) + cmd.AddCommand(cmdContentGetPath.NewCmdGetPath()) + cmd.AddCommand(cmdContentStartExport.NewCmdStartExport()) + return cmd +} diff --git a/pkg/cmd/content/export-result/export-result.go b/pkg/cmd/content/export-result/export-result.go new file mode 100644 index 00000000..822dc657 --- /dev/null +++ b/pkg/cmd/content/export-result/export-result.go @@ -0,0 +1,178 @@ +package export_result + +import ( + "encoding/json" + "fmt" + "github.com/rs/zerolog/log" + "github.com/spf13/cobra" + "github.com/wizedkyle/sumocli/api" + "github.com/wizedkyle/sumocli/pkg/cmd/factory" + "github.com/wizedkyle/sumocli/pkg/logging" + "io" + "os" + "path/filepath" +) + +func NewCmdExportResult() *cobra.Command { + var ( + contentId string + jobId string + isAdminMode bool + saveToFile bool + filePath string + fileName string + ) + + cmd := &cobra.Command{ + Use: "export-result", + Short: "Gets results from content export job for the given job identifier", + Run: func(cmd *cobra.Command, args []string) { + exportResult(contentId, jobId, isAdminMode, saveToFile, filePath, fileName) + }, + } + cmd.Flags().StringVar(&contentId, "contentId", "", "Specify the id of the content item to export") + cmd.Flags().StringVar(&jobId, "jobId", "", "Specify the job id for the export (returned from running sumocli content start-export)") + cmd.Flags().BoolVar(&isAdminMode, "isAdminMode", false, "Set to true if you want to perform the request as a content administrator") + cmd.Flags().BoolVar(&saveToFile, "saveToFile", false, "Saves the export results to a file") + cmd.Flags().StringVar(&filePath, "filePath", "", "Folder path to save results to. If this is empty then the results will be saved to the users home directory") + cmd.Flags().StringVar(&fileName, "fileName", "", "File name for the results.") + cmd.MarkFlagRequired("contentId") + cmd.MarkFlagRequired("jobId") + return cmd +} + +func exportResult(contentId string, jobId string, isAdminMode bool, saveToFile bool, filePath string, fileName string) { + var responseType api.ResponseType + log := logging.GetConsoleLogger() + requestUrl := "v2/content/" + contentId + "/export/" + jobId + "/result" + client, request := factory.NewHttpRequest("GET", requestUrl) + if isAdminMode == true { + request.Header.Add("isAdminMode", "true") + } + response, err := client.Do(request) + if err != nil { + log.Error().Err(err).Msg("failed to make http request to " + requestUrl) + } + + defer response.Body.Close() + responseBody, err := io.ReadAll(response.Body) + if err != nil { + log.Error().Err(err).Msg("failed to read response body") + } + + err = json.Unmarshal(responseBody, &responseType) + if err != nil { + log.Error().Err(err).Msg("failed to unmarshal response body") + } + + if responseType.Type == "FolderSyncDefinition" { + var folderSyncResponse api.FolderSyncDefinition + err = json.Unmarshal(responseBody, &folderSyncResponse) + if err != nil { + log.Error().Err(err).Msg("failed to unmarshal response body into DashboardSyncDefinition") + } + folderSyncResponseJson, err := json.MarshalIndent(folderSyncResponse, "", " ") + if err != nil { + log.Error().Err(err).Msg("failed to marshal dashboardSyncResponse") + } + writeResults(folderSyncResponseJson, saveToFile, filePath, fileName) + } else if responseType.Type == "DashboardSyncDefinition" { + var dashboardSyncResponse api.DashboardSyncDefinition + err = json.Unmarshal(responseBody, &dashboardSyncResponse) + if err != nil { + log.Error().Err(err).Msg("failed to unmarshal response body into DashboardSyncDefinition") + } + dashboardSyncResponseJson, err := json.MarshalIndent(dashboardSyncResponse, "", " ") + if err != nil { + log.Error().Err(err).Msg("failed to marshal dashboardSyncResponse") + } + writeResults(dashboardSyncResponseJson, saveToFile, filePath, fileName) + } else if responseType.Type == "MewboardSyncDefinition" { + var mewboardSyncResponse api.MewboardSyncDefinition + err = json.Unmarshal(responseBody, &mewboardSyncResponse) + if err != nil { + log.Error().Err(err).Msg("failed to unmarshal response body into DashboardSyncDefinition") + } + mewboardSyncResponseJson, err := json.MarshalIndent(mewboardSyncResponse, "", " ") + if err != nil { + log.Error().Err(err).Msg("failed to marshal dashboardSyncResponse") + } + writeResults(mewboardSyncResponseJson, saveToFile, filePath, fileName) + } else if responseType.Type == "SavedSearchWithScheduleSyncDefinition" { + var savedSearchWithScheduleSyncResponse api.SavedSearchWithScheduleSyncDefinition + err = json.Unmarshal(responseBody, &savedSearchWithScheduleSyncResponse) + if err != nil { + log.Error().Err(err).Msg("failed to unmarshal response body into DashboardSyncDefinition") + } + savedSearchWithScheduleSyncResponseJson, err := json.MarshalIndent(savedSearchWithScheduleSyncResponse, "", " ") + if err != nil { + log.Error().Err(err).Msg("failed to marshal dashboardSyncResponse") + } + writeResults(savedSearchWithScheduleSyncResponseJson, saveToFile, filePath, fileName) + } else if responseType.Type == "MetricsSavedSearchSyncDefinition" { + var metricsSavedSearchSyncResponse api.MetricsSavedSearchSyncDefinition + err = json.Unmarshal(responseBody, &metricsSavedSearchSyncResponse) + if err != nil { + log.Error().Err(err).Msg("failed to unmarshal response body into DashboardSyncDefinition") + } + metricsSavedSearchSyncResponseJson, err := json.MarshalIndent(metricsSavedSearchSyncResponse, "", " ") + if err != nil { + log.Error().Err(err).Msg("failed to marshal dashboardSyncResponse") + } + writeResults(metricsSavedSearchSyncResponseJson, saveToFile, filePath, fileName) + } else if responseType.Type == "MetricsSearchSyncDefinition" { + var metricsSearchSyncResponse api.MetricsSearchSyncDefinition + err = json.Unmarshal(responseBody, &metricsSearchSyncResponse) + if err != nil { + log.Error().Err(err).Msg("failed to unmarshal response body into DashboardSyncDefinition") + } + metricsSearchSyncResponseJson, err := json.MarshalIndent(metricsSearchSyncResponse, "", " ") + if err != nil { + log.Error().Err(err).Msg("failed to marshal dashboardSyncResponse") + } + writeResults(metricsSearchSyncResponseJson, saveToFile, filePath, fileName) + } else if responseType.Type == "LookupTableSyncDefinition" { + var lookupTableSyncResponse api.LookupTableSyncDefinition + err = json.Unmarshal(responseBody, &lookupTableSyncResponse) + if err != nil { + log.Error().Err(err).Msg("failed to unmarshal response body into DashboardSyncDefinition") + } + lookupTableSyncResponseJson, err := json.MarshalIndent(lookupTableSyncResponse, "", " ") + if err != nil { + log.Error().Err(err).Msg("failed to marshal dashboardSyncResponse") + } + writeResults(lookupTableSyncResponseJson, saveToFile, filePath, fileName) + } +} + +func writeResults(resultsData []byte, saveToFile bool, filePath string, fileName string) { + if saveToFile == true { + if filePath != "" { + if _, err := os.Stat(filePath); os.IsNotExist(err) { + err := os.MkdirAll(filePath, 0755) + if err != nil { + log.Fatal().Err(err).Msg("failed to create file path") + } + } + resultFile := filepath.Join(filePath, fileName) + err := os.WriteFile(resultFile, resultsData, 0644) + if err != nil { + log.Error().Err(err).Msg("failed to write results file " + filePath) + } + fmt.Println("Results written to " + resultFile) + } else { + homeDirectory, err := os.UserHomeDir() + if err != nil { + log.Error().Err(err).Msg("failed to get user home directory") + } + resultFile := filepath.Join(homeDirectory, "results.json") + err = os.WriteFile(resultFile, resultsData, 0644) + if err != nil { + log.Error().Err(err).Msg("failed to write results file " + resultFile) + } + fmt.Println("Results written to " + resultFile) + } + } else { + fmt.Println(string(resultsData)) + } +} diff --git a/pkg/cmd/content/export-status/export-status.go b/pkg/cmd/content/export-status/export-status.go new file mode 100644 index 00000000..41a50c2a --- /dev/null +++ b/pkg/cmd/content/export-status/export-status.go @@ -0,0 +1,69 @@ +package export_status + +import ( + "encoding/json" + "fmt" + "github.com/spf13/cobra" + "github.com/wizedkyle/sumocli/api" + "github.com/wizedkyle/sumocli/pkg/cmd/factory" + "github.com/wizedkyle/sumocli/pkg/logging" + "io" +) + +func NewCmdExportStatus() *cobra.Command { + var ( + contentId string + jobId string + isAdminMode bool + ) + + cmd := &cobra.Command{ + Use: "export-status", + Short: "Get the status of an asynchronous content export request for the given job identifier", + Run: func(cmd *cobra.Command, args []string) { + exportStatus(contentId, jobId, isAdminMode) + }, + } + cmd.Flags().StringVar(&contentId, "contentId", "", "Specify the id of the content item to export") + cmd.Flags().StringVar(&jobId, "jobId", "", "Specify the job id for the export (returned from running sumocli content start-export)") + cmd.Flags().BoolVar(&isAdminMode, "isAdminMode", false, "Set to true if you want to perform the request as a content administrator") + cmd.MarkFlagRequired("contentId") + cmd.MarkFlagRequired("jobId") + return cmd +} + +func exportStatus(contentId string, jobId string, isAdminMode bool) { + var exportStatusResponse api.ExportStatusResponse + log := logging.GetConsoleLogger() + requestUrl := "v2/content/" + contentId + "/export/" + jobId + "/status" + client, request := factory.NewHttpRequest("GET", requestUrl) + if isAdminMode == true { + request.Header.Add("isAdminMode", "true") + } + response, err := client.Do(request) + if err != nil { + log.Error().Err(err).Msg("failed to make http request to " + requestUrl) + } + + defer response.Body.Close() + responseBody, err := io.ReadAll(response.Body) + if err != nil { + log.Error().Err(err).Msg("failed to read response body") + } + + err = json.Unmarshal(responseBody, &exportStatusResponse) + if err != nil { + log.Error().Err(err).Msg("failed to unmarshal response body") + } + + exportStatusJson, err := json.MarshalIndent(exportStatusResponse, "", " ") + if err != nil { + log.Error().Err(err).Msg("failed to marshal exportStatusResponse") + } + + if response.StatusCode != 200 { + factory.HttpError(response.StatusCode, responseBody, log) + } else { + fmt.Println(string(exportStatusJson)) + } +} diff --git a/pkg/cmd/content/get-path/get-path.go b/pkg/cmd/content/get-path/get-path.go new file mode 100644 index 00000000..9d1722cf --- /dev/null +++ b/pkg/cmd/content/get-path/get-path.go @@ -0,0 +1,59 @@ +package get_path + +import ( + "encoding/json" + "fmt" + "github.com/spf13/cobra" + "github.com/wizedkyle/sumocli/api" + "github.com/wizedkyle/sumocli/pkg/cmd/factory" + "github.com/wizedkyle/sumocli/pkg/logging" + "io" +) + +func NewCmdGetPath() *cobra.Command { + var id string + + cmd := &cobra.Command{ + Use: "get-path", + Short: "Gets the full path of a content item with the given identifier", + Run: func(cmd *cobra.Command, args []string) { + getPath(id) + }, + } + cmd.Flags().StringVar(&id, "id", "", "Specify the id of the content") + cmd.MarkFlagRequired("id") + return cmd +} + +func getPath(id string) { + var pathResponse api.GetPathResponse + log := logging.GetConsoleLogger() + requestUrl := "v2/content/" + id + "/path" + client, request := factory.NewHttpRequest("GET", requestUrl) + response, err := client.Do(request) + if err != nil { + log.Error().Err(err).Msg("failed to make http request to " + requestUrl) + } + + defer response.Body.Close() + responseBody, err := io.ReadAll(response.Body) + if err != nil { + log.Error().Err(err).Msg("failed to read response body") + } + + err = json.Unmarshal(responseBody, &pathResponse) + if err != nil { + log.Error().Err(err).Msg("failed to unmarshal response body") + } + + pathJson, err := json.MarshalIndent(pathResponse, "", " ") + if err != nil { + log.Error().Err(err).Msg("failed to marshal pathResponse") + } + + if response.StatusCode != 200 { + factory.HttpError(response.StatusCode, responseBody, log) + } else { + fmt.Println(string(pathJson)) + } +} diff --git a/pkg/cmd/content/get/get.go b/pkg/cmd/content/get/get.go new file mode 100644 index 00000000..edf13a6d --- /dev/null +++ b/pkg/cmd/content/get/get.go @@ -0,0 +1,63 @@ +package get + +import ( + "encoding/json" + "fmt" + "github.com/spf13/cobra" + "github.com/wizedkyle/sumocli/api" + "github.com/wizedkyle/sumocli/pkg/cmd/factory" + "github.com/wizedkyle/sumocli/pkg/logging" + "io" + "net/url" +) + +func NewCmdGet() *cobra.Command { + var path string + + cmd := &cobra.Command{ + Use: "get", + Short: "Gets a content item corresponding to the provided path", + Run: func(cmd *cobra.Command, args []string) { + getContent(path) + }, + } + cmd.Flags().StringVar(&path, "path", "", "Specify the path of the content you want to retrieve") + cmd.MarkFlagRequired("path") + return cmd +} + +func getContent(path string) { + var contentResponse api.GetContentResponse + log := logging.GetConsoleLogger() + requestUrl := "v2/content/path" + client, request := factory.NewHttpRequest("GET", requestUrl) + query := url.Values{} + query.Add("path", path) + request.URL.RawQuery = query.Encode() + response, err := client.Do(request) + if err != nil { + log.Error().Err(err).Msg("failed to make http request to " + requestUrl) + } + + defer response.Body.Close() + responseBody, err := io.ReadAll(response.Body) + if err != nil { + log.Error().Err(err).Msg("failed to read response body") + } + + err = json.Unmarshal(responseBody, &contentResponse) + if err != nil { + log.Error().Err(err).Msg("failed to unmarshal response body") + } + + contentJson, err := json.MarshalIndent(contentResponse, "", " ") + if err != nil { + log.Error().Err(err).Msg("failed to marshal contentResponse") + } + + if response.StatusCode != 200 { + factory.HttpError(response.StatusCode, responseBody, log) + } else { + fmt.Println(string(contentJson)) + } +} diff --git a/pkg/cmd/content/start-export/start-export.go b/pkg/cmd/content/start-export/start-export.go new file mode 100644 index 00000000..de996cab --- /dev/null +++ b/pkg/cmd/content/start-export/start-export.go @@ -0,0 +1,61 @@ +package start_export + +import ( + "encoding/json" + "fmt" + "github.com/spf13/cobra" + "github.com/wizedkyle/sumocli/api" + "github.com/wizedkyle/sumocli/pkg/cmd/factory" + "github.com/wizedkyle/sumocli/pkg/logging" + "io" +) + +func NewCmdStartExport() *cobra.Command { + var id string + + cmd := &cobra.Command{ + Use: "start-export", + Short: "Starts an asynchronous export of content with the given identifier. You will be given a job identifier" + + "which can be used with the sumocli content export-status command." + + "If the content is a folder everything under that folder is exported recursively.", + Run: func(cmd *cobra.Command, args []string) { + startExport(id) + }, + } + cmd.Flags().StringVar(&id, "id", "", "Specify the id of the content item to export") + cmd.MarkFlagRequired("id") + return cmd +} + +func startExport(id string) { + var exportResponse api.StartExportResponse + log := logging.GetConsoleLogger() + requestUrl := "v2/content/" + id + "/export" + client, request := factory.NewHttpRequest("POST", requestUrl) + response, err := client.Do(request) + if err != nil { + log.Error().Err(err).Msg("failed to make http request to " + requestUrl) + } + + defer response.Body.Close() + responseBody, err := io.ReadAll(response.Body) + if err != nil { + log.Error().Err(err).Msg("failed to read response body") + } + + err = json.Unmarshal(responseBody, &exportResponse) + if err != nil { + log.Error().Err(err).Msg("failed to unmarshal response body") + } + + exportJson, err := json.MarshalIndent(exportResponse, "", " ") + if err != nil { + log.Error().Err(err).Msg("failed to marshal exportResponse") + } + + if response.StatusCode != 200 { + factory.HttpError(response.StatusCode, responseBody, log) + } else { + fmt.Println(string(exportJson)) + } +} diff --git a/pkg/cmd/content/start-import/start-import.go b/pkg/cmd/content/start-import/start-import.go new file mode 100644 index 00000000..33bf2c50 --- /dev/null +++ b/pkg/cmd/content/start-import/start-import.go @@ -0,0 +1,51 @@ +package start_import + +import ( + "encoding/json" + "github.com/spf13/cobra" + "github.com/wizedkyle/sumocli/api" + "github.com/wizedkyle/sumocli/pkg/logging" + "os" +) + +func NewCmdStartImport() *cobra.Command { + var ( + file string + folderId string + isAdminMode bool + overwrite bool + ) + + cmd := &cobra.Command{ + Use: "start-import", + Short: "Schedule an asynchronous import of content inside an existing folder with the given identifier. The start-import command can be used to create or update content within a folder.", + Run: func(cmd *cobra.Command, args []string) { + startImport(file, folderId, isAdminMode, overwrite) + }, + } + cmd.Flags().StringVar(&file, "file", "", "File path that contains Sumo Logic content in JSON format") + cmd.Flags().StringVar(&folderId, "folderId", "", "Specify the folder ID to import into must be in hexadecimal format. Use sumocli content get-path to get the ID of a folder") + cmd.Flags().BoolVar(&isAdminMode, "isAdminMode", false, "Set to true if you want to perform the request as a content administrator") + cmd.Flags().BoolVar(&overwrite, "overwrite", false, "Set to true if you want to overwrite existing content with the same name") + cmd.MarkFlagRequired("folderId") + return cmd +} + +func startImport(file string, folderId string, isAdminMode bool, overwrite bool) { + var responseType api.ResponseType + log := logging.GetConsoleLogger() + fileData, err := os.ReadFile(file) + if err != nil { + log.Fatal().Err(err).Msg("failed to read file") + } + err = json.Unmarshal(fileData, responseType) + if err != nil { + log.Error().Err(err).Msg("failed to unmarshal file data") + } + + // Read file + // Unmarshall type and have if statements check which type it is + // create requestBodySchema + + //requestUrl := "v2/content/folders/" + folderId + "/import" +} diff --git a/pkg/cmd/login/login.go b/pkg/cmd/login/login.go index 7edcb9b7..2d099bdc 100644 --- a/pkg/cmd/login/login.go +++ b/pkg/cmd/login/login.go @@ -10,7 +10,6 @@ import ( "github.com/spf13/viper" "github.com/wizedkyle/sumocli/api" "github.com/wizedkyle/sumocli/pkg/logging" - "io/ioutil" "os" "path/filepath" "strings" @@ -124,7 +123,7 @@ func getCredentials() { } } credentialFile, _ := json.MarshalIndent(credentials, "", " ") - err = ioutil.WriteFile(configPath(), credentialFile, 0644) + err = os.WriteFile(configPath(), credentialFile, 0644) if err != nil { Logger.Fatal().Err(err) } else { diff --git a/pkg/cmd/lookup-tables/get/get.go b/pkg/cmd/lookup-tables/get/get.go new file mode 100644 index 00000000..40fd43b1 --- /dev/null +++ b/pkg/cmd/lookup-tables/get/get.go @@ -0,0 +1,23 @@ +package get + +import ( + "github.com/spf13/cobra" +) + +func NewCmdLookupTablesGet() *cobra.Command { + var id string + cmd := &cobra.Command{ + Use: "get", + Short: "Gets a Sumo Logic lookup table based on the given identifier", + Run: func(cmd *cobra.Command, args []string) { + + }, + } + cmd.Flags().StringVar(&id, "id", "", "Specify the id of the lookup table you want to retrieve") + cmd.MarkFlagRequired("id") + return cmd +} + +func getLookupTable(id string) { + +} diff --git a/pkg/cmd/lookup-tables/lookup-tables.go b/pkg/cmd/lookup-tables/lookup-tables.go new file mode 100644 index 00000000..9fc5fe67 --- /dev/null +++ b/pkg/cmd/lookup-tables/lookup-tables.go @@ -0,0 +1,16 @@ +package lookup_tables + +import ( + "github.com/spf13/cobra" + cmdLookupTablesGet "github.com/wizedkyle/sumocli/pkg/cmd/lookup-tables/get" +) + +func NewCmdLookupTables() *cobra.Command { + cmd := &cobra.Command{ + Use: "lookup-tables", + Short: "Manage lookup tables", + Long: "Commands that allow you to manage Lookup Tables in your Sumo Logic tenant", + } + cmd.AddCommand(cmdLookupTablesGet.NewCmdLookupTablesGet()) + return cmd +} diff --git a/pkg/cmd/roles/get/get.go b/pkg/cmd/roles/get/get.go index ed194b57..749c813e 100644 --- a/pkg/cmd/roles/get/get.go +++ b/pkg/cmd/roles/get/get.go @@ -3,7 +3,6 @@ package get import ( "encoding/json" "fmt" - "github.com/rs/zerolog" "github.com/spf13/cobra" "github.com/wizedkyle/sumocli/api" "github.com/wizedkyle/sumocli/pkg/cmd/factory" @@ -20,36 +19,43 @@ func NewCmdRoleGet() *cobra.Command { Run: func(cmd *cobra.Command, args []string) { logger := logging.GetLoggerForCommand(cmd) logger.Debug().Msg("Role get request started.") - getRole(id, logger) + getRole(id) logger.Debug().Msg("Role get request finished.") }, } - cmd.Flags().StringVar(&id, "id", "", "Specify the id of the role to get") cmd.MarkFlagRequired("id") return cmd } -func getRole(id string, logger zerolog.Logger) { +func getRole(id string) { var roleInfo api.RoleData - + log := logging.GetConsoleLogger() requestUrl := "v1/roles/" + id client, request := factory.NewHttpRequest("GET", requestUrl) response, err := client.Do(request) - logging.LogError(err, logger) + if err != nil { + log.Error().Err(err).Msg("failed to make http request to " + requestUrl) + } defer response.Body.Close() responseBody, err := io.ReadAll(response.Body) - logging.LogError(err, logger) + if err != nil { + log.Error().Err(err).Msg("failed to read response body") + } - jsonErr := json.Unmarshal(responseBody, &roleInfo) - logging.LogError(jsonErr, logger) + err = json.Unmarshal(responseBody, &roleInfo) + if err != nil { + log.Error().Err(err).Msg("failed to unmarshal response body") + } roleInfoJson, err := json.MarshalIndent(roleInfo, "", " ") - logging.LogError(err, logger) + if err != nil { + log.Error().Err(err).Msg("failed to marshal roleInfo") + } if response.StatusCode != 200 { - factory.HttpError(response.StatusCode, responseBody, logger) + factory.HttpError(response.StatusCode, responseBody, log) } else { fmt.Println(string(roleInfoJson)) } diff --git a/pkg/cmd/roles/roles.go b/pkg/cmd/roles/roles.go index dcd7f3e0..ab6a15cc 100644 --- a/pkg/cmd/roles/roles.go +++ b/pkg/cmd/roles/roles.go @@ -15,7 +15,7 @@ func NewCmdRole() *cobra.Command { cmd := &cobra.Command{ Use: "roles ", Short: "Manage roles", - Long: "Work with Sumo Logic roles", + Long: "Commands that allow you to manage roles in your Sumo Logic tenant", } cmd.AddCommand(cmdRoleAssign.NewCmdRoleAssign()) diff --git a/pkg/cmd/root/root.go b/pkg/cmd/root/root.go index d8cb5d8a..44dd450c 100644 --- a/pkg/cmd/root/root.go +++ b/pkg/cmd/root/root.go @@ -4,8 +4,10 @@ import ( "github.com/spf13/cobra" azureCmd "github.com/wizedkyle/sumocli/pkg/cmd/azure" collectorCmd "github.com/wizedkyle/sumocli/pkg/cmd/collectors" + contentCmd "github.com/wizedkyle/sumocli/pkg/cmd/content" liveTailCmd "github.com/wizedkyle/sumocli/pkg/cmd/live-tail" loginCmd "github.com/wizedkyle/sumocli/pkg/cmd/login" + lookupTablesCmd "github.com/wizedkyle/sumocli/pkg/cmd/lookup-tables" roleCmd "github.com/wizedkyle/sumocli/pkg/cmd/roles" sourcesCmd "github.com/wizedkyle/sumocli/pkg/cmd/sources" usersCmd "github.com/wizedkyle/sumocli/pkg/cmd/users" @@ -24,8 +26,10 @@ func NewCmdRoot() *cobra.Command { // Add subcommands cmd.AddCommand(azureCmd.NewCmdAzure()) cmd.AddCommand(collectorCmd.NewCmdCollectors()) + cmd.AddCommand(contentCmd.NewCmdContent()) cmd.AddCommand(liveTailCmd.NewCmdLiveTail()) cmd.AddCommand(loginCmd.NewCmdLogin()) + cmd.AddCommand(lookupTablesCmd.NewCmdLookupTables()) cmd.AddCommand(roleCmd.NewCmdRole()) cmd.AddCommand(sourcesCmd.NewCmdSources()) cmd.AddCommand(usersCmd.NewCmdUser())