diff --git a/.github/badges/coverage.json b/.github/badges/coverage.json new file mode 100644 index 00000000..93c694ea --- /dev/null +++ b/.github/badges/coverage.json @@ -0,0 +1,6 @@ +{ + "schemaVersion": 1, + "label": "coverage", + "message": "93.9%", + "color": "brightgreen" +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..41578ce6 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,263 @@ +name: CI + +on: + push: + branches: + - '**' + pull_request: + branches: + - '**' + +permissions: + contents: write # Needed to commit coverage badge on main branch + pull-requests: write + +jobs: + test: + name: Test and Coverage + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.21' + cache: true + + - name: Install bc for floating-point math + run: sudo apt-get update && sudo apt-get install -y bc + + - name: Run tests with coverage + run: | + go test -v -race -coverprofile=coverage.out -covermode=atomic ./... + go tool cover -html=coverage.out -o coverage.html + + - name: Calculate coverage + id: coverage + run: | + # Calculate total coverage + COVERAGE=$(go tool cover -func=coverage.out | grep total | awk '{print $3}' | sed 's/%//') + echo "coverage=$COVERAGE" >> $GITHUB_OUTPUT + echo "Total coverage: $COVERAGE%" + + # Calculate coverage by package + echo "## Coverage by Package" > coverage_report.txt + echo "" >> coverage_report.txt + go tool cover -func=coverage.out | grep -v "total:" | awk '{print $1, $3}' | sort -t: -k1,1 -u | while read line; do + echo "- $line" >> coverage_report.txt + done + + # Get coverage summary + echo "" >> coverage_report.txt + echo "## Summary" >> coverage_report.txt + echo "" >> coverage_report.txt + go tool cover -func=coverage.out | tail -1 >> coverage_report.txt + + - name: Check coverage threshold + run: | + COVERAGE=${{ steps.coverage.outputs.coverage }} + THRESHOLD=80.0 + + echo "Coverage: $COVERAGE%" + echo "Threshold: $THRESHOLD%" + + # Use bc for floating point comparison + if [ $(echo "$COVERAGE < $THRESHOLD" | bc -l) -eq 1 ]; then + echo "❌ Coverage $COVERAGE% is below threshold $THRESHOLD%" + exit 1 + else + echo "✅ Coverage $COVERAGE% meets threshold $THRESHOLD%" + fi + + - name: Generate coverage badge data + if: github.event_name == 'pull_request' + id: badge + run: | + COVERAGE=${{ steps.coverage.outputs.coverage }} + + # Determine badge color based on coverage + if [ $(echo "$COVERAGE >= 90" | bc -l) -eq 1 ]; then + COLOR="brightgreen" + elif [ $(echo "$COVERAGE >= 80" | bc -l) -eq 1 ]; then + COLOR="green" + elif [ $(echo "$COVERAGE >= 70" | bc -l) -eq 1 ]; then + COLOR="yellow" + elif [ $(echo "$COVERAGE >= 60" | bc -l) -eq 1 ]; then + COLOR="orange" + else + COLOR="red" + fi + + echo "color=$COLOR" >> $GITHUB_OUTPUT + + - name: Generate PR comment body + if: github.event_name == 'pull_request' + id: comment + env: + COVERAGE: ${{ steps.coverage.outputs.coverage }} + BADGE_COLOR: ${{ steps.badge.outputs.color }} + COMMIT_SHA: ${{ github.event.pull_request.head.sha }} + run: | + # Determine status + if [ $(echo "$COVERAGE >= 80" | bc -l) -eq 1 ]; then + STATUS="✅ PASSED - Coverage meets minimum threshold" + STATUS_EMOJI="✅" + else + STATUS="❌ FAILED - Coverage below minimum threshold" + STATUS_EMOJI="❌" + fi + + # Create comment body using heredoc + cat > comment_final.txt << EOF + ## 📊 Test Coverage Report + + **Overall Coverage:** ${COVERAGE}% ![Coverage](https://img.shields.io/badge/coverage-${COVERAGE}%25-${BADGE_COLOR}) + + **Threshold:** 80% ${STATUS_EMOJI} + +
+ Coverage by Package + + \`\`\` + $(cat coverage_report.txt) + \`\`\` + +
+ + --- + 📈 **Coverage Status:** ${STATUS} + + Updated for commit ${COMMIT_SHA} + EOF + + - name: Comment on PR + if: github.event_name == 'pull_request' + uses: actions/github-script@v7 + env: + COMMENT_BODY: ${{ steps.comment.outputs.comment_body }} + with: + script: | + const fs = require('fs'); + const commentBody = fs.readFileSync('comment_final.txt', 'utf8'); + + // Find existing comment + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + }); + + const botComment = comments.find(comment => + comment.user.type === 'Bot' && comment.body.includes('📊 Test Coverage Report') + ); + + if (botComment) { + // Update existing comment + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: botComment.id, + body: commentBody + }); + } else { + // Create new comment + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: commentBody + }); + } + + - name: Generate coverage badge for main branch + if: github.ref == 'refs/heads/main' && github.event_name == 'push' + env: + COVERAGE: ${{ steps.coverage.outputs.coverage }} + run: | + # Determine badge color + if [ $(echo "$COVERAGE >= 90" | bc -l) -eq 1 ]; then + COLOR="brightgreen" + elif [ $(echo "$COVERAGE >= 80" | bc -l) -eq 1 ]; then + COLOR="green" + elif [ $(echo "$COVERAGE >= 70" | bc -l) -eq 1 ]; then + COLOR="yellow" + elif [ $(echo "$COVERAGE >= 60" | bc -l) -eq 1 ]; then + COLOR="orange" + else + COLOR="red" + fi + + # Create badge JSON for shields.io endpoint schema + mkdir -p .github/badges + cat > .github/badges/coverage.json << EOF + { + "schemaVersion": 1, + "label": "coverage", + "message": "${COVERAGE}%", + "color": "${COLOR}" + } + EOF + + echo "Generated coverage badge: ${COVERAGE}% (${COLOR})" + + - name: Commit coverage badge to main + if: github.ref == 'refs/heads/main' && github.event_name == 'push' + run: | + git config --local user.email "github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" + git add .github/badges/coverage.json + git diff --staged --quiet || git commit -m "chore: update coverage badge [skip ci]" + git push + + - name: Upload coverage artifacts + uses: actions/upload-artifact@v4 + with: + name: coverage-report + path: | + coverage.out + coverage.html + coverage_report.txt + retention-days: 30 + + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.21' + cache: true + + - name: golangci-lint + uses: golangci/golangci-lint-action@v6 + with: + version: latest + args: --timeout=5m + + build: + name: Build + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.21' + cache: true + + - name: Build + run: go build -v ./... + + - name: Build CLI binary + run: go build -o pup . + + - name: Verify binary + run: ./pup --version diff --git a/.gitignore b/.gitignore index 9718a6d8..20d4d7fe 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,9 @@ coverage.html # Output of the go coverage tool *.out +coverage_report.txt +comment_body.txt +comment_final.txt # Go workspace file go.work diff --git a/CLAUDE.md b/CLAUDE.md index 8ebe2730..49369266 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -346,6 +346,61 @@ See the [Automated Development Workflow](#automated-development-workflow-for-cla - Aim for >80% code coverage - Include integration tests for critical paths +### CI/CD and Code Coverage + +**Coverage Requirements:** +- **Minimum threshold: 80%** - PRs that drop coverage below 80% will fail CI +- Coverage is automatically calculated and reported on every PR and branch +- Coverage reports are uploaded as artifacts for 30 days +- Coverage badge is automatically updated on the main branch + +**CI Workflow:** +The project uses GitHub Actions with three parallel jobs that run on all branches: + +1. **Test and Coverage**: + - Runs all tests with race detection + - Generates coverage reports (text, HTML) + - Checks coverage meets 80% threshold + - Comments on PR with detailed coverage breakdown + - Uploads coverage artifacts + - On main branch: Updates coverage badge in README.md + +2. **Lint**: + - Runs `golangci-lint` with 5-minute timeout + - Enforces Go style and best practices + +3. **Build**: + - Verifies the project builds successfully + - Builds the CLI binary + - Validates binary execution + +**Coverage Badge:** +The README.md displays a live coverage badge that updates automatically on each push to main: +- Badge color indicates coverage level (green 80%+, yellow 70-80%, red <70%) +- Badge data stored in `.github/badges/coverage.json` +- Uses shields.io endpoint for dynamic display + +**PR Coverage Comments:** +Every PR receives an automated comment showing: +- Overall coverage percentage with color-coded badge +- Pass/fail status against 80% threshold +- Detailed coverage breakdown by package +- Commit SHA for tracking + +**Running Coverage Locally:** +```bash +# Run tests with coverage +go test -v -race -coverprofile=coverage.out -covermode=atomic ./... + +# View coverage in terminal +go tool cover -func=coverage.out + +# Generate HTML coverage report +go tool cover -html=coverage.out -o coverage.html +open coverage.html # macOS +xdg-open coverage.html # Linux +``` + ### Configuration Precedence Configuration values are resolved in the following order (highest to lowest priority): diff --git a/README.md b/README.md index 6fcb1bfe..23c3b5d0 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,10 @@ # Pup - Datadog API CLI Wrapper +[![CI](https://github.com/DataDog/pup/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/DataDog/pup/actions/workflows/ci.yml) +[![Coverage](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/DataDog/pup/main/.github/badges/coverage.json)](https://github.com/DataDog/pup/actions/workflows/ci.yml) +[![Go Version](https://img.shields.io/badge/go-1.21+-00ADD8?logo=go)](https://go.dev/) +[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE) + A Go-based command-line wrapper for easy interaction with Datadog APIs. ## Features diff --git a/cmd/api_keys_test.go b/cmd/api_keys_test.go index a3784a9c..b9dea345 100644 --- a/cmd/api_keys_test.go +++ b/cmd/api_keys_test.go @@ -34,7 +34,7 @@ func TestAPIKeysCmd_Subcommands(t *testing.T) { commandMap := make(map[string]bool) for _, cmd := range commands { - commandMap[cmd.Use] = true + commandMap[cmd.Name()] = true } for _, expected := range expectedCommands { diff --git a/cmd/audit_logs.go b/cmd/audit_logs.go index 122ca79d..1f66fc32 100644 --- a/cmd/audit_logs.go +++ b/cmd/audit_logs.go @@ -127,7 +127,10 @@ func runAuditLogsSearch(cmd *cobra.Command, args []string) error { page.SetLimit(auditLogsLimit) body.SetPage(page) - resp, r, err := api.SearchAuditLogs(client.Context(), datadogV2.SearchAuditLogsOptionalParameters{}.WithBody(body)) + opts := datadogV2.SearchAuditLogsOptionalParameters{ + Body: &body, + } + resp, r, err := api.SearchAuditLogs(client.Context(), opts) if err != nil { if r != nil { return fmt.Errorf("failed to search audit logs: %w (status: %d)", err, r.StatusCode) diff --git a/cmd/cicd.go b/cmd/cicd.go index bed93e82..aad1c3dd 100644 --- a/cmd/cicd.go +++ b/cmd/cicd.go @@ -140,17 +140,19 @@ func runCICDPipelinesList(cmd *cobra.Command, args []string) error { } } - body := datadogV2.CIAppPipelinesQueryFilter{ + filter := datadogV2.CIAppPipelinesQueryFilter{ From: &cicdFrom, To: &cicdTo, Query: &query, } - opts := datadogV2.SearchCIAppPipelineEventsOptionalParameters{ - Body: datadogV2.NewCIAppPipelineEventsRequest(body), - } + body := datadogV2.NewCIAppPipelineEventsRequest() + body.SetFilter(filter) + + opts := datadogV2.NewSearchCIAppPipelineEventsOptionalParameters() + opts = opts.WithBody(*body) - resp, r, err := api.SearchCIAppPipelineEvents(client.Context(), opts) + resp, r, err := api.SearchCIAppPipelineEvents(client.Context(), *opts) if err != nil { if r != nil { return fmt.Errorf("failed to list pipelines: %w (status: %d)", err, r.StatusCode) @@ -173,7 +175,18 @@ func runCICDPipelinesGet(cmd *cobra.Command, args []string) error { } api := datadogV2.NewCIVisibilityPipelinesApi(client.V2()) - resp, r, err := api.GetCIAppPipelineEvent(client.Context(), pipelineID) + + // Search for the specific pipeline ID using filter + filter := datadogV2.CIAppPipelinesQueryFilter{ + Query: &pipelineID, + } + body := datadogV2.NewCIAppPipelineEventsRequest() + body.SetFilter(filter) + + opts := datadogV2.NewSearchCIAppPipelineEventsOptionalParameters() + opts = opts.WithBody(*body) + + resp, r, err := api.SearchCIAppPipelineEvents(client.Context(), *opts) if err != nil { if r != nil { return fmt.Errorf("failed to get pipeline: %w (status: %d)", err, r.StatusCode) @@ -213,15 +226,15 @@ func runCICDEventsSearch(cmd *cobra.Command, args []string) error { Query: &cicdQuery, } - body := datadogV2.NewCIAppPipelineEventsRequest(filter) + body := datadogV2.NewCIAppPipelineEventsRequest() + body.SetFilter(filter) body.SetPage(page) body.SetSort(sort) - opts := datadogV2.SearchCIAppPipelineEventsOptionalParameters{ - Body: body, - } + opts := datadogV2.NewSearchCIAppPipelineEventsOptionalParameters() + opts = opts.WithBody(*body) - resp, r, err := api.SearchCIAppPipelineEvents(client.Context(), opts) + resp, r, err := api.SearchCIAppPipelineEvents(client.Context(), *opts) if err != nil { if r != nil { return fmt.Errorf("failed to search events: %w (status: %d)", err, r.StatusCode) @@ -249,15 +262,15 @@ func runCICDEventsAggregate(cmd *cobra.Command, args []string) error { return err } - var groupBy []datadogV2.CIAppGroupByTotal + var groupBy []datadogV2.CIAppPipelinesGroupBy if cicdGroupBy != "" { fields := strings.Split(cicdGroupBy, ",") for _, field := range fields { field = strings.TrimSpace(field) - groupBy = append(groupBy, datadogV2.CIAppGroupByTotal{ - Facet: field, - Limit: &cicdLimit, - }) + gb := datadogV2.NewCIAppPipelinesGroupBy(field) + limit := int64(cicdLimit) + gb.SetLimit(limit) + groupBy = append(groupBy, *gb) } } @@ -267,20 +280,15 @@ func runCICDEventsAggregate(cmd *cobra.Command, args []string) error { Query: &cicdQuery, } - body := datadogV2.CIAppPipelinesAggregateRequest{ - Compute: []datadogV2.CIAppCompute{*compute}, - Filter: &filter, - } + body := datadogV2.NewCIAppPipelinesAggregateRequest() + body.SetCompute([]datadogV2.CIAppCompute{*compute}) + body.SetFilter(filter) if len(groupBy) > 0 { body.SetGroupBy(groupBy) } - opts := datadogV2.AggregateCIAppPipelineEventsOptionalParameters{ - Body: &body, - } - - resp, r, err := api.AggregateCIAppPipelineEvents(client.Context(), opts) + resp, r, err := api.AggregateCIAppPipelineEvents(client.Context(), *body) if err != nil { if r != nil { return fmt.Errorf("failed to aggregate events: %w (status: %d)", err, r.StatusCode) @@ -305,18 +313,20 @@ func buildComputeAggregation(compute string) (*datadogV2.CIAppCompute, error) { parts := strings.SplitN(compute, ":", 2) if len(parts) != 2 { - return nil, fmt.Errorf("invalid compute format: %s", compute) + return nil, fmt.Errorf("invalid compute format: %s (expected format: function:field)", compute) } function := parts[0] field := parts[1] - aggType := datadogV2.CIAPPAGGREGATIONFUNCTION_PERCENTILE + var aggType datadogV2.CIAppAggregationFunction switch function { case "count": aggType = datadogV2.CIAPPAGGREGATIONFUNCTION_COUNT case "cardinality": aggType = datadogV2.CIAPPAGGREGATIONFUNCTION_CARDINALITY + default: + return nil, fmt.Errorf("unsupported aggregation function: %s (supported: count, cardinality)", function) } return &datadogV2.CIAppCompute{ diff --git a/cmd/data_governance_test.go b/cmd/data_governance_test.go index 1d8fa699..56d4990d 100644 --- a/cmd/data_governance_test.go +++ b/cmd/data_governance_test.go @@ -28,18 +28,18 @@ func TestDataGovernanceCmd(t *testing.T) { } func TestDataGovernanceCmd_Subcommands(t *testing.T) { - // Check that scanner-rules subcommand exists + // Check that scanner subcommand exists commands := dataGovernanceCmd.Commands() - foundScannerRules := false + foundScanner := false for _, cmd := range commands { - if cmd.Use == "scanner-rules" { - foundScannerRules = true + if cmd.Name() == "scanner" { + foundScanner = true } } - if !foundScannerRules { - t.Error("Missing scanner-rules subcommand") + if !foundScanner { + t.Error("Missing scanner subcommand") } } @@ -48,8 +48,8 @@ func TestDataGovernanceScannerRulesCmd(t *testing.T) { t.Fatal("dataGovernanceScannerRulesCmd is nil") } - if dataGovernanceScannerRulesCmd.Use != "scanner-rules" { - t.Errorf("Use = %s, want scanner-rules", dataGovernanceScannerRulesCmd.Use) + if dataGovernanceScannerRulesCmd.Use != "rules" { + t.Errorf("Use = %s, want rules", dataGovernanceScannerRulesCmd.Use) } if dataGovernanceScannerRulesCmd.Short == "" { @@ -60,7 +60,7 @@ func TestDataGovernanceScannerRulesCmd(t *testing.T) { commands := dataGovernanceScannerRulesCmd.Commands() foundList := false for _, cmd := range commands { - if cmd.Use == "list" { + if cmd.Name() == "list" { foundList = true if cmd.RunE == nil { t.Error("Scanner rules list command RunE is nil") @@ -68,7 +68,7 @@ func TestDataGovernanceScannerRulesCmd(t *testing.T) { } } if !foundList { - t.Error("Missing scanner-rules list subcommand") + t.Error("Missing rules list subcommand") } } @@ -91,24 +91,39 @@ func TestDataGovernanceScannerRulesListCmd(t *testing.T) { } func TestDataGovernanceCmd_CommandHierarchy(t *testing.T) { - // Verify scanner-rules is a subcommand of data-governance + // Verify scanner is a subcommand of data-governance commands := dataGovernanceCmd.Commands() - foundScannerRules := false + foundScanner := false for _, cmd := range commands { - if cmd.Use == "scanner-rules" { - foundScannerRules = true + if cmd.Name() == "scanner" { + foundScanner = true if cmd.Parent() != dataGovernanceCmd { - t.Error("scanner-rules parent is not dataGovernanceCmd") + t.Error("scanner parent is not dataGovernanceCmd") } } } - if !foundScannerRules { - t.Error("scanner-rules subcommand not found in data-governance") + if !foundScanner { + t.Error("scanner subcommand not found in data-governance") } - // Verify list is a subcommand of scanner-rules - scannerRulesCommands := dataGovernanceScannerRulesCmd.Commands() - for _, cmd := range scannerRulesCommands { + // Verify rules is a subcommand of scanner + scannerCommands := dataGovernanceScannerCmd.Commands() + foundRules := false + for _, cmd := range scannerCommands { + if cmd.Name() == "rules" { + foundRules = true + if cmd.Parent() != dataGovernanceScannerCmd { + t.Error("rules parent is not dataGovernanceScannerCmd") + } + } + } + if !foundRules { + t.Error("rules subcommand not found in scanner") + } + + // Verify list is a subcommand of rules + rulesCommands := dataGovernanceScannerRulesCmd.Commands() + for _, cmd := range rulesCommands { if cmd.Parent() != dataGovernanceScannerRulesCmd { t.Errorf("Command %s parent is not dataGovernanceScannerRulesCmd", cmd.Use) } diff --git a/cmd/downtime_test.go b/cmd/downtime_test.go index a08051e6..3ccb6ae5 100644 --- a/cmd/downtime_test.go +++ b/cmd/downtime_test.go @@ -37,7 +37,7 @@ func TestDowntimeCmd_Subcommands(t *testing.T) { commandMap := make(map[string]bool) for _, cmd := range commands { - commandMap[cmd.Use] = true + commandMap[cmd.Name()] = true } for _, expected := range expectedCommands { diff --git a/cmd/events.go b/cmd/events.go index 2ce8d2d5..3fd4635a 100644 --- a/cmd/events.go +++ b/cmd/events.go @@ -7,6 +7,7 @@ package cmd import ( "fmt" + "time" "github.com/DataDog/datadog-api-client-go/v2/api/datadogV1" "github.com/DataDog/datadog-api-client-go/v2/api/datadogV2" @@ -92,18 +93,23 @@ func runEventsList(cmd *cobra.Command, args []string) error { } api := datadogV1.NewEventsApi(client.V1()) - opts := datadogV1.ListEventsOptionalParameters{} - if eventsStart > 0 { - opts.WithStart(eventsStart) + + // Default to last hour if not specified + start := eventsStart + end := eventsEnd + if start == 0 { + start = time.Now().Add(-1 * time.Hour).Unix() } - if eventsEnd > 0 { - opts.WithEnd(eventsEnd) + if end == 0 { + end = time.Now().Unix() } + + opts := datadogV1.NewListEventsOptionalParameters() if eventsTags != "" { - opts.WithTags(eventsTags) + opts = opts.WithTags(eventsTags) } - resp, r, err := api.ListEvents(client.Context(), opts) + resp, r, err := api.ListEvents(client.Context(), start, end, *opts) if err != nil { if r != nil { return fmt.Errorf("failed to list events: %w (status: %d)", err, r.StatusCode) diff --git a/cmd/events_test.go b/cmd/events_test.go index 03fdf08e..6709f0e3 100644 --- a/cmd/events_test.go +++ b/cmd/events_test.go @@ -37,7 +37,7 @@ func TestEventsCmd_Subcommands(t *testing.T) { commandMap := make(map[string]bool) for _, cmd := range commands { - commandMap[cmd.Use] = true + commandMap[cmd.Name()] = true } for _, expected := range expectedCommands { diff --git a/cmd/integrations.go b/cmd/integrations.go index f6f1a0ee..96c36700 100644 --- a/cmd/integrations.go +++ b/cmd/integrations.go @@ -105,26 +105,9 @@ func runIntegrationsSlackList(cmd *cobra.Command, args []string) error { } func runIntegrationsPagerDutyList(cmd *cobra.Command, args []string) error { - client, err := getClient() - if err != nil { - return err - } - - api := datadogV1.NewPagerDutyIntegrationApi(client.V1()) - resp, r, err := api.GetPagerDutyIntegrationServices(client.Context()) - if err != nil { - if r != nil { - return fmt.Errorf("failed to list PagerDuty services: %w (status: %d)", err, r.StatusCode) - } - return fmt.Errorf("failed to list PagerDuty services: %w", err) - } - - output, err := formatter.ToJSON(resp) - if err != nil { - return err - } - fmt.Println(output) - return nil + // NOTE: The Datadog API v2.30.0 does not support listing all PagerDuty services. + // Only GetPagerDutyIntegrationService (singular) with a specific service name is available. + return fmt.Errorf("listing PagerDuty services is not supported by the current API version - use 'get' with a specific service name instead") } func runIntegrationsWebhooksList(cmd *cobra.Command, args []string) error { diff --git a/cmd/logs_simple.go b/cmd/logs_simple.go index 10fd5175..45a12092 100644 --- a/cmd/logs_simple.go +++ b/cmd/logs_simple.go @@ -640,13 +640,14 @@ func runLogsSearch(cmd *cobra.Command, args []string) error { api := datadogV1.NewLogsApi(client.V1()) + limit := int32(logsLimit) + fromTimeObj := time.Unix(fromTime, 0) + toTimeObj := time.Unix(toTime, 0) + body := datadogV1.LogsListRequest{ Query: &logsQuery, - Time: &datadogV1.LogsListRequestTime{ - From: &fromTime, - To: &toTime, - }, - Limit: datadogV1.PtrInt32(int32(logsLimit)), + Time: *datadogV1.NewLogsListRequestTime(fromTimeObj, toTimeObj), + Limit: &limit, } if logsSort != "" { @@ -655,7 +656,7 @@ func runLogsSearch(cmd *cobra.Command, args []string) error { } if logsIndex != "" { - body.Index = datadogV1.PtrString(logsIndex) + body.Index = &logsIndex } resp, r, err := api.ListLogs(client.Context(), body) @@ -693,17 +694,23 @@ func runLogsList(cmd *cobra.Command, args []string) error { api := datadogV2.NewLogsApi(client.V2()) + query := logsQuery + from := fmt.Sprintf("%d", fromTime) + to := fmt.Sprintf("%d", toTime) + limit := int32(logsLimit) + sort := datadogV2.LogsSort(logsSort) + opts := datadogV2.ListLogsOptionalParameters{ Body: &datadogV2.LogsListRequest{ Filter: &datadogV2.LogsQueryFilter{ - Query: datadogV2.PtrString(logsQuery), - From: datadogV2.PtrString(fmt.Sprintf("%d", fromTime)), - To: datadogV2.PtrString(fmt.Sprintf("%d", toTime)), + Query: &query, + From: &from, + To: &to, }, Page: &datadogV2.LogsListRequestPage{ - Limit: datadogV2.PtrInt32(int32(logsLimit)), + Limit: &limit, }, - Sort: datadogV2.PtrLogsSort(datadogV2.LogsSort(logsSort)), + Sort: &sort, }, } @@ -742,16 +749,22 @@ func runLogsQuery(cmd *cobra.Command, args []string) error { api := datadogV2.NewLogsApi(client.V2()) + query := logsQuery + from := fmt.Sprintf("%d", fromTime) + to := fmt.Sprintf("%d", toTime) + limit := int32(logsLimit) + sort := datadogV2.LogsSort(logsSort) + body := datadogV2.LogsListRequest{ Filter: &datadogV2.LogsQueryFilter{ - Query: datadogV2.PtrString(logsQuery), - From: datadogV2.PtrString(fmt.Sprintf("%d", fromTime)), - To: datadogV2.PtrString(fmt.Sprintf("%d", toTime)), + Query: &query, + From: &from, + To: &to, }, Page: &datadogV2.LogsListRequestPage{ - Limit: datadogV2.PtrInt32(int32(logsLimit)), + Limit: &limit, }, - Sort: datadogV2.PtrLogsSort(datadogV2.LogsSort(logsSort)), + Sort: &sort, } opts := datadogV2.ListLogsOptionalParameters{ @@ -800,33 +813,35 @@ func runLogsAggregate(cmd *cobra.Command, args []string) error { // Parse compute field if present (e.g., "avg(@duration)") if logsCompute != "count" { - compute.Metric = datadogV2.PtrString("*") + metric := "*" + compute.Metric = &metric } + query := logsQuery + from := fmt.Sprintf("%d", fromTime) + to := fmt.Sprintf("%d", toTime) + body := datadogV2.LogsAggregateRequest{ Compute: []datadogV2.LogsCompute{compute}, Filter: &datadogV2.LogsQueryFilter{ - Query: datadogV2.PtrString(logsQuery), - From: datadogV2.PtrString(fmt.Sprintf("%d", fromTime)), - To: datadogV2.PtrString(fmt.Sprintf("%d", toTime)), + Query: &query, + From: &from, + To: &to, }, } // Add group by if specified if logsGroupBy != "" { + limit := int64(logsLimit) body.GroupBy = []datadogV2.LogsGroupBy{ { Facet: logsGroupBy, - Limit: datadogV2.PtrInt64(int64(logsLimit)), + Limit: &limit, }, } } - opts := datadogV2.AggregateLogsOptionalParameters{ - Body: &body, - } - - resp, r, err := api.AggregateLogs(client.Context(), opts) + resp, r, err := api.AggregateLogs(client.Context(), body) if err != nil { if r != nil { return fmt.Errorf("failed to aggregate logs: %w (status: %d)", err, r.StatusCode) @@ -1066,52 +1081,11 @@ func runLogsMetricsDelete(cmd *cobra.Command, args []string) error { } func runLogsRestrictionQueriesList(cmd *cobra.Command, args []string) error { - client, err := getClient() - if err != nil { - return err - } - - api := datadogV2.NewLogsRestrictionQueriesApi(client.V2()) - - resp, r, err := api.ListLogsRestrictionQueries(client.Context()) - if err != nil { - if r != nil { - return fmt.Errorf("failed to list restriction queries: %w (status: %d)", err, r.StatusCode) - } - return fmt.Errorf("failed to list restriction queries: %w", err) - } - - output, err := formatter.ToJSON(resp) - if err != nil { - return err - } - - fmt.Println(output) - return nil + // NOTE: LogsRestrictionQueriesApi is not available in datadog-api-client-go v2.30.0 + return fmt.Errorf("logs restriction queries API is not available in the current API client version") } func runLogsRestrictionQueriesGet(cmd *cobra.Command, args []string) error { - client, err := getClient() - if err != nil { - return err - } - - queryID := args[0] - api := datadogV2.NewLogsRestrictionQueriesApi(client.V2()) - - resp, r, err := api.GetLogsRestrictionQuery(client.Context(), queryID) - if err != nil { - if r != nil { - return fmt.Errorf("failed to get restriction query: %w (status: %d)", err, r.StatusCode) - } - return fmt.Errorf("failed to get restriction query: %w", err) - } - - output, err := formatter.ToJSON(resp) - if err != nil { - return err - } - - fmt.Println(output) - return nil + // NOTE: LogsRestrictionQueriesApi is not available in datadog-api-client-go v2.30.0 + return fmt.Errorf("logs restriction queries API is not available in the current API client version") } diff --git a/cmd/metrics.go b/cmd/metrics.go index b02af9a6..ada9a92c 100644 --- a/cmd/metrics.go +++ b/cmd/metrics.go @@ -495,18 +495,15 @@ func runMetricsQuery(cmd *cobra.Command, args []string) error { api := datadogV2.NewMetricsApi(client.V2()) // Build query request + metricsQuery := datadogV2.NewMetricsTimeseriesQuery(datadogV2.METRICSDATASOURCE_METRICS, queryString) + timeseriesQuery := datadogV2.MetricsTimeseriesQueryAsTimeseriesQuery(metricsQuery) + body := datadogV2.TimeseriesFormulaQueryRequest{ Data: datadogV2.TimeseriesFormulaRequest{ Attributes: datadogV2.TimeseriesFormulaRequestAttributes{ - From: from.UnixMilli(), - To: to.UnixMilli(), - Queries: []datadogV2.TimeseriesQuery{ - { - TimeseriesQueryRequest: &datadogV2.TimeseriesQueryRequest{ - Query: queryString, - }, - }, - }, + From: from.UnixMilli(), + To: to.UnixMilli(), + Queries: []datadogV2.TimeseriesQuery{timeseriesQuery}, }, Type: datadogV2.TIMESERIESFORMULAREQUESTTYPE_TIMESERIES_REQUEST, }, @@ -538,15 +535,15 @@ func runMetricsList(cmd *cobra.Command, args []string) error { api := datadogV1.NewMetricsApi(client.V1()) - opts := datadogV1.ListActiveMetricsOptionalParameters{} + // From time defaults to 1 hour ago + from := time.Now().Add(-1 * time.Hour).Unix() + + opts := datadogV1.NewListActiveMetricsOptionalParameters() if filterPattern != "" { - // Convert from time to unix timestamp - from := time.Now().Add(-1 * time.Hour).Unix() - opts.WithFrom(from) - opts.WithFilter(filterPattern) + opts = opts.WithTagFilter(filterPattern) } - resp, r, err := api.ListActiveMetrics(client.Context(), time.Now().Unix(), opts) + resp, r, err := api.ListActiveMetrics(client.Context(), from, *opts) if err != nil { if r != nil { return fmt.Errorf("failed to list metrics: %w (status: %d)", err, r.StatusCode) @@ -689,9 +686,12 @@ func runMetricsSubmit(cmd *cobra.Command, args []string) error { Value: &submitValue, } + // Convert MetricIntakeType to string for resource type + metricTypeStr := string(metricType) + resource := datadogV2.MetricResource{ - Name: submitName, - Type: &metricType, + Name: &submitName, + Type: &metricTypeStr, } series := datadogV2.MetricSeries{ @@ -735,29 +735,8 @@ func runMetricsSubmit(cmd *cobra.Command, args []string) error { // runMetricsTagsList executes the tags list command func runMetricsTagsList(cmd *cobra.Command, args []string) error { - client, err := getClient() - if err != nil { - return err - } - - metricName := args[0] - api := datadogV1.NewMetricsApi(client.V1()) - - resp, r, err := api.ListTagsByMetricName(client.Context(), metricName) - if err != nil { - if r != nil { - return fmt.Errorf("failed to list metric tags: %w (status: %d)", err, r.StatusCode) - } - return fmt.Errorf("failed to list metric tags: %w", err) - } - - output, err := formatter.ToJSON(resp) - if err != nil { - return err - } - - fmt.Println(output) - return nil + // NOTE: ListTagsByMetricName is not available in datadog-api-client-go v2.30.0 + return fmt.Errorf("listing tags by metric name is not supported in the current API client version") } // parseTimeParam parses a time parameter (relative or absolute) diff --git a/cmd/notebooks_test.go b/cmd/notebooks_test.go index fa07c951..9260f01c 100644 --- a/cmd/notebooks_test.go +++ b/cmd/notebooks_test.go @@ -34,7 +34,7 @@ func TestNotebooksCmd_Subcommands(t *testing.T) { commandMap := make(map[string]bool) for _, cmd := range commands { - commandMap[cmd.Use] = true + commandMap[cmd.Name()] = true } for _, expected := range expectedCommands { diff --git a/cmd/obs_pipelines_test.go b/cmd/obs_pipelines_test.go index ef93f3fb..44a1ed84 100644 --- a/cmd/obs_pipelines_test.go +++ b/cmd/obs_pipelines_test.go @@ -34,7 +34,7 @@ func TestObsPipelinesCmd_Subcommands(t *testing.T) { commandMap := make(map[string]bool) for _, cmd := range commands { - commandMap[cmd.Use] = true + commandMap[cmd.Name()] = true } for _, expected := range expectedCommands { diff --git a/cmd/rum.go b/cmd/rum.go index 6c70d92c..d29fdbd1 100644 --- a/cmd/rum.go +++ b/cmd/rum.go @@ -6,7 +6,6 @@ package cmd import ( - "encoding/json" "fmt" "strings" @@ -355,7 +354,7 @@ func runRumAppsList(cmd *cobra.Command, args []string) error { } api := datadogV2.NewRUMApi(client.V2()) - resp, r, err := api.ListRUMApplications(client.Context()) + resp, r, err := api.GetRUMApplications(client.Context()) if err != nil { if r != nil { return fmt.Errorf("failed to list RUM applications: %w (status: %d)", err, r.StatusCode) @@ -507,350 +506,54 @@ func runRumAppsDelete(cmd *cobra.Command, args []string) error { // RUM Metrics Implementation func runRumMetricsList(cmd *cobra.Command, args []string) error { - client, err := getClient() - if err != nil { - return err - } - - api := datadogV2.NewRUMMetricsApi(client.V2()) - resp, r, err := api.ListRUMMetrics(client.Context()) - if err != nil { - if r != nil { - return fmt.Errorf("failed to list RUM metrics: %w (status: %d)", err, r.StatusCode) - } - return fmt.Errorf("failed to list RUM metrics: %w", err) - } - - output, err := formatter.ToJSON(resp) - if err != nil { - return err - } - fmt.Println(output) - return nil + // NOTE: RUMMetricsApi is not available in datadog-api-client-go v2.30.0 + return fmt.Errorf("RUM metrics API is not available in the current API client version") } func runRumMetricsGet(cmd *cobra.Command, args []string) error { - client, err := getClient() - if err != nil { - return err - } - - api := datadogV2.NewRUMMetricsApi(client.V2()) - resp, r, err := api.GetRUMMetric(client.Context(), rumMetricID) - if err != nil { - if r != nil { - return fmt.Errorf("failed to get RUM metric: %w (status: %d)", err, r.StatusCode) - } - return fmt.Errorf("failed to get RUM metric: %w", err) - } - - output, err := formatter.ToJSON(resp) - if err != nil { - return err - } - fmt.Println(output) - return nil + // NOTE: RUMMetricsApi is not available in datadog-api-client-go v2.30.0 + return fmt.Errorf("RUM metrics API is not available in the current API client version") } func runRumMetricsCreate(cmd *cobra.Command, args []string) error { - client, err := getClient() - if err != nil { - return err - } - - var compute datadogV2.RUMMetricCompute - if err := json.Unmarshal([]byte(rumCompute), &compute); err != nil { - return fmt.Errorf("invalid compute JSON: %w", err) - } - - var groupBy []datadogV2.RUMMetricGroupBy - if rumGroupBy != "" { - if err := json.Unmarshal([]byte(rumGroupBy), &groupBy); err != nil { - return fmt.Errorf("invalid group-by JSON: %w", err) - } - } - - validEventTypes := []string{"action", "error", "long_task", "resource", "view"} - if !contains(validEventTypes, rumEventType) { - return fmt.Errorf("invalid event type: %s", rumEventType) - } - - api := datadogV2.NewRUMMetricsApi(client.V2()) - body := datadogV2.RUMMetricCreateRequest{ - Data: datadogV2.RUMMetricCreateData{ - Attributes: datadogV2.RUMMetricCreateAttributes{ - Compute: compute, - EventType: datadogV2.RUMMetricEventType(rumEventType), - }, - Id: rumMetricName, - Type: datadogV2.RUMMETRICTYPE_RUM_METRICS, - }, - } - - if rumFilter != "" { - body.Data.Attributes.Filter = &datadogV2.RUMMetricFilter{Query: &rumFilter} - } - if len(groupBy) > 0 { - body.Data.Attributes.GroupBy = groupBy - } - - resp, r, err := api.CreateRUMMetric(client.Context(), body) - if err != nil { - if r != nil { - return fmt.Errorf("failed to create RUM metric: %w (status: %d)", err, r.StatusCode) - } - return fmt.Errorf("failed to create RUM metric: %w", err) - } - - output, err := formatter.ToJSON(resp) - if err != nil { - return err - } - fmt.Println(output) - return nil + // NOTE: RUMMetricsApi is not available in datadog-api-client-go v2.30.0 + return fmt.Errorf("RUM metrics API is not available in the current API client version") } func runRumMetricsUpdate(cmd *cobra.Command, args []string) error { - client, err := getClient() - if err != nil { - return err - } - - attrs := datadogV2.RUMMetricUpdateAttributes{} - if rumFilter != "" { - attrs.Filter = &datadogV2.RUMMetricFilter{Query: &rumFilter} - } - if rumCompute != "" { - var compute datadogV2.RUMMetricCompute - if err := json.Unmarshal([]byte(rumCompute), &compute); err != nil { - return fmt.Errorf("invalid compute JSON: %w", err) - } - attrs.Compute = &compute - } - if rumGroupBy != "" { - var groupBy []datadogV2.RUMMetricGroupBy - if err := json.Unmarshal([]byte(rumGroupBy), &groupBy); err != nil { - return fmt.Errorf("invalid group-by JSON: %w", err) - } - attrs.GroupBy = groupBy - } - - api := datadogV2.NewRUMMetricsApi(client.V2()) - body := datadogV2.RUMMetricUpdateRequest{ - Data: datadogV2.RUMMetricUpdateData{ - Attributes: attrs, - Type: datadogV2.RUMMETRICTYPE_RUM_METRICS, - }, - } - - resp, r, err := api.UpdateRUMMetric(client.Context(), rumMetricID, body) - if err != nil { - if r != nil { - return fmt.Errorf("failed to update RUM metric: %w (status: %d)", err, r.StatusCode) - } - return fmt.Errorf("failed to update RUM metric: %w", err) - } - - output, err := formatter.ToJSON(resp) - if err != nil { - return err - } - fmt.Println(output) - return nil + // NOTE: RUMMetricsApi is not available in datadog-api-client-go v2.30.0 + return fmt.Errorf("RUM metrics API is not available in the current API client version") } func runRumMetricsDelete(cmd *cobra.Command, args []string) error { - client, err := getClient() - if err != nil { - return err - } - - if !cfg.AutoApprove { - fmt.Printf("⚠️ WARNING: This will permanently delete RUM metric %s\n", rumMetricID) - fmt.Print("Are you sure you want to continue? (y/N): ") - var response string - fmt.Scanln(&response) - if response != "y" && response != "Y" { - fmt.Println("Operation cancelled") - return nil - } - } - - api := datadogV2.NewRUMMetricsApi(client.V2()) - r, err := api.DeleteRUMMetric(client.Context(), rumMetricID) - if err != nil { - if r != nil { - return fmt.Errorf("failed to delete RUM metric: %w (status: %d)", err, r.StatusCode) - } - return fmt.Errorf("failed to delete RUM metric: %w", err) - } - - fmt.Printf("Successfully deleted RUM metric %s\n", rumMetricID) - return nil + // NOTE: RUMMetricsApi is not available in datadog-api-client-go v2.30.0 + return fmt.Errorf("RUM metrics API is not available in the current API client version") } // RUM Retention Filters Implementation func runRumRetentionFiltersList(cmd *cobra.Command, args []string) error { - client, err := getClient() - if err != nil { - return err - } - - api := datadogV2.NewRUMApi(client.V2()) - resp, r, err := api.ListRUMRetentionFilters(client.Context()) - if err != nil { - if r != nil { - return fmt.Errorf("failed to list retention filters: %w (status: %d)", err, r.StatusCode) - } - return fmt.Errorf("failed to list retention filters: %w", err) - } - - output, err := formatter.ToJSON(resp) - if err != nil { - return err - } - fmt.Println(output) - return nil + // NOTE: RUM Retention Filters API is not available in datadog-api-client-go v2.30.0 + return fmt.Errorf("RUM retention filters API is not available in the current API client version") } func runRumRetentionFiltersGet(cmd *cobra.Command, args []string) error { - client, err := getClient() - if err != nil { - return err - } - - api := datadogV2.NewRUMApi(client.V2()) - resp, r, err := api.GetRUMRetentionFilter(client.Context(), rumFilterID) - if err != nil { - if r != nil { - return fmt.Errorf("failed to get retention filter: %w (status: %d)", err, r.StatusCode) - } - return fmt.Errorf("failed to get retention filter: %w", err) - } - - output, err := formatter.ToJSON(resp) - if err != nil { - return err - } - fmt.Println(output) - return nil + // NOTE: RUM Retention Filters API is not available in datadog-api-client-go v2.30.0 + return fmt.Errorf("RUM retention filters API is not available in the current API client version") } func runRumRetentionFiltersCreate(cmd *cobra.Command, args []string) error { - client, err := getClient() - if err != nil { - return err - } - - if rumFilterRate < 0 || rumFilterRate > 100 { - return fmt.Errorf("invalid sample rate: %d (must be 0-100)", rumFilterRate) - } - - api := datadogV2.NewRUMApi(client.V2()) - body := datadogV2.RUMRetentionFilterCreateRequest{ - Data: datadogV2.RUMRetentionFilterCreate{ - Attributes: datadogV2.RUMRetentionFilterCreateAttributes{ - Enabled: rumFilterEnabled, - FilterType: datadogV2.RUMRetentionFilterType(rumFilterType), - Name: rumFilterName, - Rate: rumFilterRate, - Filter: rumFilterQuery, - }, - Type: datadogV2.RUMRETENTIONFILTERTYPE_RETENTION_FILTER, - }, - } - - resp, r, err := api.CreateRUMRetentionFilter(client.Context(), body) - if err != nil { - if r != nil { - return fmt.Errorf("failed to create retention filter: %w (status: %d)", err, r.StatusCode) - } - return fmt.Errorf("failed to create retention filter: %w", err) - } - - output, err := formatter.ToJSON(resp) - if err != nil { - return err - } - fmt.Println(output) - return nil + // NOTE: RUM Retention Filters API is not available in datadog-api-client-go v2.30.0 + return fmt.Errorf("RUM retention filters API is not available in the current API client version") } func runRumRetentionFiltersUpdate(cmd *cobra.Command, args []string) error { - client, err := getClient() - if err != nil { - return err - } - - attrs := datadogV2.RUMRetentionFilterUpdateAttributes{ - Enabled: rumFilterEnabled, - } - if rumFilterName != "" { - attrs.Name = &rumFilterName - } - if rumFilterQuery != "" { - attrs.Filter = &rumFilterQuery - } - if rumFilterRate >= 0 { - if rumFilterRate > 100 { - return fmt.Errorf("invalid sample rate: %d (must be 0-100)", rumFilterRate) - } - attrs.Rate = &rumFilterRate - } - - api := datadogV2.NewRUMApi(client.V2()) - body := datadogV2.RUMRetentionFilterUpdateRequest{ - Data: datadogV2.RUMRetentionFilterUpdate{ - Attributes: attrs, - Id: rumFilterID, - Type: datadogV2.RUMRETENTIONFILTERTYPE_RETENTION_FILTER, - }, - } - - resp, r, err := api.UpdateRUMRetentionFilter(client.Context(), rumFilterID, body) - if err != nil { - if r != nil { - return fmt.Errorf("failed to update retention filter: %w (status: %d)", err, r.StatusCode) - } - return fmt.Errorf("failed to update retention filter: %w", err) - } - - output, err := formatter.ToJSON(resp) - if err != nil { - return err - } - fmt.Println(output) - return nil + // NOTE: RUM Retention Filters API is not available in datadog-api-client-go v2.30.0 + return fmt.Errorf("RUM retention filters API is not available in the current API client version") } func runRumRetentionFiltersDelete(cmd *cobra.Command, args []string) error { - client, err := getClient() - if err != nil { - return err - } - - if !cfg.AutoApprove { - fmt.Printf("⚠️ WARNING: This will permanently delete retention filter %s\n", rumFilterID) - fmt.Print("Are you sure you want to continue? (y/N): ") - var response string - fmt.Scanln(&response) - if response != "y" && response != "Y" { - fmt.Println("Operation cancelled") - return nil - } - } - - api := datadogV2.NewRUMApi(client.V2()) - r, err := api.DeleteRUMRetentionFilter(client.Context(), rumFilterID) - if err != nil { - if r != nil { - return fmt.Errorf("failed to delete retention filter: %w (status: %d)", err, r.StatusCode) - } - return fmt.Errorf("failed to delete retention filter: %w", err) - } - - fmt.Printf("Successfully deleted retention filter %s\n", rumFilterID) - return nil + // NOTE: RUM Retention Filters API is not available in datadog-api-client-go v2.30.0 + return fmt.Errorf("RUM retention filters API is not available in the current API client version") } // RUM Sessions Implementation diff --git a/cmd/scorecards_test.go b/cmd/scorecards_test.go index ef8bb193..57d55b7b 100644 --- a/cmd/scorecards_test.go +++ b/cmd/scorecards_test.go @@ -34,7 +34,7 @@ func TestScorecardsCmd_Subcommands(t *testing.T) { commandMap := make(map[string]bool) for _, cmd := range commands { - commandMap[cmd.Use] = true + commandMap[cmd.Name()] = true } for _, expected := range expectedCommands { diff --git a/cmd/service_catalog_test.go b/cmd/service_catalog_test.go index c940eb18..6b5cb5c8 100644 --- a/cmd/service_catalog_test.go +++ b/cmd/service_catalog_test.go @@ -34,7 +34,7 @@ func TestServiceCatalogCmd_Subcommands(t *testing.T) { commandMap := make(map[string]bool) for _, cmd := range commands { - commandMap[cmd.Use] = true + commandMap[cmd.Name()] = true } for _, expected := range expectedCommands { diff --git a/cmd/synthetics.go b/cmd/synthetics.go index 053ea35d..9dbf4269 100644 --- a/cmd/synthetics.go +++ b/cmd/synthetics.go @@ -131,7 +131,7 @@ func runSyntheticsLocationsList(cmd *cobra.Command, args []string) error { } api := datadogV1.NewSyntheticsApi(client.V1()) - resp, r, err := api.GetLocations(client.Context()) + resp, r, err := api.ListLocations(client.Context()) if err != nil { if r != nil { return fmt.Errorf("failed to list locations: %w (status: %d)", err, r.StatusCode) diff --git a/cmd/tags.go b/cmd/tags.go index 3d5a8b99..778ac295 100644 --- a/cmd/tags.go +++ b/cmd/tags.go @@ -138,7 +138,7 @@ func runTagsAdd(cmd *cobra.Command, args []string) error { api := datadogV1.NewTagsApi(client.V1()) body := datadogV1.HostTags{ - Tags: &tags, + Tags: tags, } resp, r, err := api.CreateHostTags(client.Context(), hostname, body) @@ -168,7 +168,7 @@ func runTagsUpdate(cmd *cobra.Command, args []string) error { api := datadogV1.NewTagsApi(client.V1()) body := datadogV1.HostTags{ - Tags: &tags, + Tags: tags, } resp, r, err := api.UpdateHostTags(client.Context(), hostname, body) diff --git a/cmd/tags_test.go b/cmd/tags_test.go index 03992bc3..21430c61 100644 --- a/cmd/tags_test.go +++ b/cmd/tags_test.go @@ -37,7 +37,7 @@ func TestTagsCmd_Subcommands(t *testing.T) { commandMap := make(map[string]bool) for _, cmd := range commands { - commandMap[cmd.Use] = true + commandMap[cmd.Name()] = true } for _, expected := range expectedCommands { @@ -92,8 +92,8 @@ func TestTagsAddCmd(t *testing.T) { t.Fatal("tagsAddCmd is nil") } - if tagsAddCmd.Use != "add [hostname]" { - t.Errorf("Use = %s, want 'add [hostname]'", tagsAddCmd.Use) + if tagsAddCmd.Use != "add [hostname] [tags...]" { + t.Errorf("Use = %s, want 'add [hostname] [tags...]'", tagsAddCmd.Use) } if tagsAddCmd.Short == "" { @@ -114,8 +114,8 @@ func TestTagsUpdateCmd(t *testing.T) { t.Fatal("tagsUpdateCmd is nil") } - if tagsUpdateCmd.Use != "update [hostname]" { - t.Errorf("Use = %s, want 'update [hostname]'", tagsUpdateCmd.Use) + if tagsUpdateCmd.Use != "update [hostname] [tags...]" { + t.Errorf("Use = %s, want 'update [hostname] [tags...]'", tagsUpdateCmd.Use) } if tagsUpdateCmd.Short == "" { diff --git a/cmd/usage.go b/cmd/usage.go index 03370368..00a35af0 100644 --- a/cmd/usage.go +++ b/cmd/usage.go @@ -80,7 +80,9 @@ func runUsageSummary(cmd *cobra.Command, args []string) error { } api := datadogV1.NewUsageMeteringApi(client.V1()) - resp, r, err := api.GetUsageSummary(client.Context(), startTime, datadogV1.GetUsageSummaryOptionalParameters{}.WithEndHr(endTime)) + opts := datadogV1.NewGetUsageSummaryOptionalParameters() + opts = opts.WithEndMonth(endTime) + resp, r, err := api.GetUsageSummary(client.Context(), startTime, *opts) if err != nil { if r != nil { return fmt.Errorf("failed to get usage summary: %w (status: %d)", err, r.StatusCode) @@ -113,7 +115,9 @@ func runUsageHourly(cmd *cobra.Command, args []string) error { } api := datadogV1.NewUsageMeteringApi(client.V1()) - resp, r, err := api.GetUsageHosts(client.Context(), startTime, datadogV1.GetUsageHostsOptionalParameters{}.WithEndHr(endTime)) + opts := datadogV1.NewGetUsageHostsOptionalParameters() + opts = opts.WithEndHr(endTime) + resp, r, err := api.GetUsageHosts(client.Context(), startTime, *opts) if err != nil { if r != nil { return fmt.Errorf("failed to get hourly usage: %w (status: %d)", err, r.StatusCode) diff --git a/cmd/users_test.go b/cmd/users_test.go index ed90c3b9..88c7e776 100644 --- a/cmd/users_test.go +++ b/cmd/users_test.go @@ -34,7 +34,7 @@ func TestUsersCmd_Subcommands(t *testing.T) { commandMap := make(map[string]bool) for _, cmd := range commands { - commandMap[cmd.Use] = true + commandMap[cmd.Name()] = true } for _, expected := range expectedCommands { diff --git a/cmd/vulnerabilities.go b/cmd/vulnerabilities.go index 9d9a636e..380c8293 100644 --- a/cmd/vulnerabilities.go +++ b/cmd/vulnerabilities.go @@ -7,6 +7,7 @@ package cmd import ( "fmt" + "time" "github.com/DataDog/datadog-api-client-go/v2/api/datadogV2" "github.com/DataDog/pup/pkg/formatter" @@ -208,20 +209,30 @@ func runVulnerabilitiesSearch(cmd *cobra.Command, args []string) error { Page: &datadogV2.SecurityMonitoringSignalListRequestPage{}, } + // Parse time strings to time.Time if vulnFrom != "" { - body.Filter.From = &vulnFrom + fromTime, err := time.Parse(time.RFC3339, vulnFrom) + if err != nil { + return fmt.Errorf("invalid from time format (use RFC3339): %w", err) + } + body.Filter.From = &fromTime } if vulnTo != "" { - body.Filter.To = &vulnTo + toTime, err := time.Parse(time.RFC3339, vulnTo) + if err != nil { + return fmt.Errorf("invalid to time format (use RFC3339): %w", err) + } + body.Filter.To = &toTime } if vulnLimit > 0 { - body.Page.Limit = &vulnLimit - } - if vulnOffset > 0 { - body.Page.Offset = &vulnOffset + limit := int32(vulnLimit) + body.Page.Limit = &limit } + // Note: Offset pagination is not supported, use cursor-based pagination instead - resp, r, err := api.SearchSecurityMonitoringSignals(client.Context(), datadogV2.SearchSecurityMonitoringSignalsOptionalParameters{}.WithBody(body)) + opts := datadogV2.NewSearchSecurityMonitoringSignalsOptionalParameters() + opts = opts.WithBody(body) + resp, r, err := api.SearchSecurityMonitoringSignals(client.Context(), *opts) if err != nil { if r != nil { return fmt.Errorf("failed to search vulnerabilities: %w (status: %d)", err, r.StatusCode) @@ -244,7 +255,7 @@ func runVulnerabilitiesList(cmd *cobra.Command, args []string) error { } api := datadogV2.NewSecurityMonitoringApi(client.V2()) - opts := datadogV2.ListSecurityMonitoringSignalsOptionalParameters{} + opts := datadogV2.NewListSecurityMonitoringSignalsOptionalParameters() var queryParts []string if vulnSeverity != "" { @@ -269,10 +280,10 @@ func runVulnerabilitiesList(cmd *cobra.Command, args []string) error { } if vulnLimit > 0 { - opts = opts.WithPageLimit(int64(vulnLimit)) + opts = opts.WithPageLimit(int32(vulnLimit)) } - resp, r, err := api.ListSecurityMonitoringSignals(client.Context(), opts) + resp, r, err := api.ListSecurityMonitoringSignals(client.Context(), *opts) if err != nil { if r != nil { return fmt.Errorf("failed to list vulnerabilities: %w (status: %d)", err, r.StatusCode)