diff --git a/.acceptance.goreleaser.yml b/.acceptance.goreleaser.yml index f96f03d5..b3e10084 100644 --- a/.acceptance.goreleaser.yml +++ b/.acceptance.goreleaser.yml @@ -12,10 +12,15 @@ builds: env: - CC=x86_64-linux-gnu-gcc - CXX=x86_64-linux-gnu-g++ + - CGO_ENABLED=1 + - GOFLAGS= + - CGO_LDFLAGS= ldflags: - -s -w -X main.version={{.Version}} -X main.date={{.Date}} -X main.commit={{.Commit}} -X main.builtBy=goreleaser + tags: [] + archives: - id: homebrew format: tar.gz diff --git a/.github/workflows/01-tailpipe-release.yaml b/.github/workflows/01-tailpipe-release.yaml index c2e8e2ea..4345a0fd 100644 --- a/.github/workflows/01-tailpipe-release.yaml +++ b/.github/workflows/01-tailpipe-release.yaml @@ -67,7 +67,7 @@ jobs: build_and_release: name: Build and Release Tailpipe needs: [ensure_branch_in_homebrew] - runs-on: ubuntu-latest + runs-on: ubuntu-24.04-arm steps: - name: validate if: github.ref == 'refs/heads/develop' @@ -110,6 +110,20 @@ jobs: token: ${{ secrets.GH_ACCESS_TOKEN }} ref: main + - name: Set up Docker + uses: docker/setup-buildx-action@v3 + + - name: Install Docker (if needed) + run: | + if ! command -v docker &> /dev/null; then + sudo apt-get update + sudo apt-get install -y docker.io + fi + + - name: Verify Docker installation + run: | + docker --version + - name: Calculate version id: calculate_version run: | diff --git a/.github/workflows/11-test-acceptance.yaml b/.github/workflows/11-test-acceptance.yaml index cc81641a..cbcbc901 100644 --- a/.github/workflows/11-test-acceptance.yaml +++ b/.github/workflows/11-test-acceptance.yaml @@ -16,7 +16,7 @@ env: jobs: goreleaser: name: Build - runs-on: ubuntu-latest + runs-on: ubuntu-24.04-arm steps: - name: Checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 diff --git a/.goreleaser.yml b/.goreleaser.yml index d8e8a667..f8834287 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,40 +1,50 @@ version: 2 builds: - - id: tailpipe-linux-arm64 + - id: tailpipe-linux-amd64 binary: tailpipe goos: - linux goarch: - - arm64 + - amd64 env: - - CC=aarch64-linux-gnu-gcc - - CXX=aarch64-linux-gnu-g++ + - CC=x86_64-linux-gnu-gcc + - CXX=x86_64-linux-gnu-g++ + - CGO_ENABLED=1 + - GOFLAGS= + - CGO_LDFLAGS= # Custom ldflags. # # Default: '-s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}} -X main.builtBy=goreleaser' # Templates: allowed ldflags: - # Go Releaser analyzes your Git repository and identifies the most recent Git tag (typically the highest version number) as the version for your release. + # Goreleaser analyzes your Git repository and identifies the most recent Git tag (typically the highest version number) as the version for your release. # This is how it determines the value of {{.Version}}. - -s -w -X main.version={{.Version}} -X main.date={{.Date}} -X main.commit={{.Commit}} -X main.builtBy=goreleaser - - id: tailpipe-linux-amd64 + tags: [] + + - id: tailpipe-linux-arm64 binary: tailpipe goos: - linux goarch: - - amd64 + - arm64 env: - - CC=x86_64-linux-gnu-gcc - - CXX=x86_64-linux-gnu-g++ + - CC=gcc + - CXX=g++ + - CGO_ENABLED=1 + - GOFLAGS= + - CGO_LDFLAGS= ldflags: - -s -w -X main.version={{.Version}} -X main.date={{.Date}} -X main.commit={{.Commit}} -X main.builtBy=goreleaser + tags: [] + - id: tailpipe-darwin-arm64 binary: tailpipe goos: diff --git a/CHANGELOG.md b/CHANGELOG.md index 03a15fd7..3508990a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,36 @@ +## v0.7.1 [2025-10-07] +_Bug Fixes_ +- Build: Restored CentOS/RHEL 9 compatibility by pinning the build image to an older libstdc++/GCC baseline. Previous build linked against newer GLIBCXX symbols, causing Tailpipe to fail on CentOS/RHEL 9. + +## v0.7.0 [2025-09-22] + +### _Major Changes_ +* Replace native Parquet conversion with a **DuckLake database backend**. ([#546](https://github.com/turbot/tailpipe/issues/546)) + - DuckLake is DuckDB’s new lakehouse format: data remains in Parquet files, but metadata is efficiently tracked in a + separate DuckDB database. + - DuckLake supports function-based partitioning, which allows data to be partitioned by year and month. This enables + efficient file pruning on `tp_timestamp` without needing a separate `tp_date` filter. A `tp_date` column will still + be present for compatibility, but it is no longer required for efficient query filtering. + - Existing data will be **automatically migrated** the next time Tailpipe runs. Migration does **not** + occur if progress output is disabled (`--progress=false`) or when using machine-readable output (`json`, `line`, + `csv`). + + **Note:** For CentOS/RHEL users, the minimum supported version is now **CentOS Stream 10 / RHEL 10** due to `libstdc++` library compatibility. + +* The `connect` command now returns the path to an **initialisation SQL script** instead of the database path. ([#550](https://github.com/turbot/tailpipe/issues/550)) + - The script sets up DuckDB with required extensions, attaches the Tailpipe database, and defines views with optional + filters. + - You can pass the generated script to DuckDB using the `--init` argument to immediately configure the session. For + example: + ```sh + duckdb --init $(tailpipe connect) + ``` + **Note:** The minimum supported DuckDB version is 1.4.0. + +### _Bug Fixes_ +* Include partitions for local plugins in the `tailpipe plugin list` command. ([#538](https://github.com/turbot/tailpipe/issues/538)) + + ## v0.6.2 [2025-07-24] _Bug fixes_ * Fix issue where `--to` was not respected for zero granularity data. ([#483](https://github.com/turbot/tailpipe/issues/483)) diff --git a/Makefile b/Makefile index 42138790..74f25773 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ OUTPUT_DIR?=/usr/local/bin PACKAGE_NAME := github.com/turbot/tailpipe -GOLANG_CROSS_VERSION ?= gcc13-osxcross-20250912194615 +GOLANG_CROSS_VERSION ?= gcc13-osxcross-20251006102018 # sed 's/[\/_]/-/g': Replaces both slashes (/) and underscores (_) with hyphens (-). # sed 's/[^a-zA-Z0-9.-]//g': Removes any character that isn’t alphanumeric, a dot (.), or a hyphen (-). @@ -30,6 +30,7 @@ release-dry-run: release-acceptance: @docker run \ --rm \ + --platform=linux/arm64 \ -e CGO_ENABLED=1 \ -v /var/run/docker.sock:/var/run/docker.sock \ -v `pwd`:/go/src/tailpipe \ @@ -48,6 +49,7 @@ release: fi docker run \ --rm \ + --platform=linux/arm64 \ -e CGO_ENABLED=1 \ --env-file .release-env \ -v /var/run/docker.sock:/var/run/docker.sock \ diff --git a/README.md b/README.md index 2ed4cfd4..e8ae6611 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -[![plugins](https://img.shields.io/endpoint?url=https://turbot.com/api/badge-stats?stat=tp_plugins)](https://hub.tailpipe-io.vercel.app/)   -[![mods](https://img.shields.io/endpoint?url=https://turbot.com/api/badge-stats?stat=tp_mods)](https://hub.tailpipe-io.vercel.app/)   +[![plugins](https://img.shields.io/endpoint?url=https://turbot.com/api/badge-stats?stat=tp_plugins)](https://hub.tailpipe.io/)   +[![mods](https://img.shields.io/endpoint?url=https://turbot.com/api/badge-stats?stat=tp_mods)](https://hub.tailpipe.io/)   [![slack](https://img.shields.io/endpoint?url=https://turbot.com/api/badge-stats?stat=slack)](https://turbot.com/community/join?utm_id=gspreadme&utm_source=github&utm_medium=repo&utm_campaign=github&utm_content=readme)   [![maintained by](https://img.shields.io/badge/maintained%20by-Turbot-blue)](https://turbot.com?utm_id=gspreadme&utm_source=github&utm_medium=repo&utm_campaign=github&utm_content=readme) diff --git a/cmd/root.go b/cmd/root.go index 69ef0a8a..402c3c0e 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,6 +1,9 @@ package cmd import ( + "errors" + "os" + "github.com/spf13/cobra" "github.com/spf13/viper" "github.com/turbot/pipe-fittings/v2/cmdconfig" @@ -9,6 +12,7 @@ import ( "github.com/turbot/pipe-fittings/v2/filepaths" "github.com/turbot/pipe-fittings/v2/utils" "github.com/turbot/tailpipe/internal/constants" + "github.com/turbot/tailpipe/internal/migration" ) var exitCode int @@ -62,8 +66,18 @@ func Execute() int { utils.LogTime("cmd.root.Execute start") defer utils.LogTime("cmd.root.Execute end") rootCmd := rootCommand() + + // set the error output to stdout (as it;s common usage to redirect stderr to a file to capture logs + rootCmd.SetErr(os.Stdout) + + // if the error is dues to unsupported migration, set a specific exit code - this will bve picked up by powerpipe if err := rootCmd.Execute(); err != nil { - exitCode = -1 + var unsupportedErr *migration.UnsupportedError + if errors.As(err, &unsupportedErr) { + exitCode = pconstants.ExitCodeMigrationUnsupported + } else { + exitCode = 1 + } } return exitCode } diff --git a/go.mod b/go.mod index d3e9648b..0a633771 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ toolchain go1.24.0 replace ( github.com/c-bata/go-prompt => github.com/turbot/go-prompt v0.2.6-steampipe.0.0.20221028122246-eb118ec58d50 -// github.com/turbot/pipe-fittings/v2 => ../pipe-fittings +//github.com/turbot/pipe-fittings/v2 => ../pipe-fittings //github.com/turbot/tailpipe-plugin-core => ../tailpipe-plugin-core // github.com/turbot/tailpipe-plugin-sdk => ../tailpipe-plugin-sdk ) @@ -17,9 +17,9 @@ require ( github.com/mattn/go-isatty v0.0.20 github.com/spf13/cobra v1.9.1 github.com/spf13/viper v1.19.0 - github.com/stretchr/testify v1.10.0 + github.com/stretchr/testify v1.11.0 github.com/turbot/go-kit v1.3.0 - github.com/turbot/pipe-fittings/v2 v2.7.0-rc.2 + github.com/turbot/pipe-fittings/v2 v2.7.0 github.com/turbot/tailpipe-plugin-sdk v0.9.3 github.com/zclconf/go-cty v1.16.3 golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 @@ -39,39 +39,39 @@ require ( github.com/hashicorp/go-plugin v1.6.1 github.com/hashicorp/go-version v1.7.0 github.com/jedib0t/go-pretty/v6 v6.5.9 - github.com/marcboeker/go-duckdb/v2 v2.3.5 + github.com/marcboeker/go-duckdb/v2 v2.4.0 github.com/shiena/ansicolor v0.0.0-20230509054315-a9deabde6e02 github.com/thediveo/enumflag/v2 v2.0.5 github.com/turbot/tailpipe-plugin-core v0.2.10 - golang.org/x/text v0.27.0 - google.golang.org/grpc v1.73.0 - google.golang.org/protobuf v1.36.6 + golang.org/x/text v0.28.0 + google.golang.org/grpc v1.75.0 + google.golang.org/protobuf v1.36.8 ) require ( github.com/goccy/go-json v0.10.5 // indirect github.com/google/flatbuffers v25.2.10+incompatible // indirect - github.com/klauspost/cpuid/v2 v2.2.11 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/pierrec/lz4/v4 v4.1.22 // indirect github.com/zeebo/xxh3 v1.0.2 // indirect ) require ( - cel.dev/expr v0.23.0 // indirect + cel.dev/expr v0.24.0 // indirect cloud.google.com/go v0.121.0 // indirect cloud.google.com/go/auth v0.16.0 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect - cloud.google.com/go/compute/metadata v0.6.0 // indirect + cloud.google.com/go/compute/metadata v0.7.0 // indirect cloud.google.com/go/iam v1.5.0 // indirect cloud.google.com/go/monitoring v1.24.0 // indirect cloud.google.com/go/storage v1.52.0 // indirect github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect github.com/agext/levenshtein v1.2.3 // indirect - github.com/apache/arrow-go/v18 v18.4.0 // indirect + github.com/apache/arrow-go/v18 v18.4.1 // indirect github.com/apparentlymart/go-cidr v1.1.0 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/aws/aws-sdk-go v1.44.183 // indirect @@ -97,7 +97,7 @@ require ( github.com/charmbracelet/lipgloss v1.0.0 // indirect github.com/charmbracelet/x/ansi v0.4.5 // indirect github.com/charmbracelet/x/term v0.2.1 // indirect - github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f // indirect + github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect github.com/containerd/containerd v1.7.27 // indirect github.com/containerd/errdefs v0.3.0 // indirect github.com/containerd/log v0.1.0 // indirect @@ -106,12 +106,12 @@ require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dgraph-io/ristretto v0.2.0 // indirect github.com/dlclark/regexp2 v1.4.0 // indirect - github.com/duckdb/duckdb-go-bindings v0.1.17 // indirect - github.com/duckdb/duckdb-go-bindings/darwin-amd64 v0.1.12 // indirect - github.com/duckdb/duckdb-go-bindings/darwin-arm64 v0.1.12 // indirect - github.com/duckdb/duckdb-go-bindings/linux-amd64 v0.1.12 // indirect - github.com/duckdb/duckdb-go-bindings/linux-arm64 v0.1.12 // indirect - github.com/duckdb/duckdb-go-bindings/windows-amd64 v0.1.12 // indirect + github.com/duckdb/duckdb-go-bindings v0.1.19 // indirect + github.com/duckdb/duckdb-go-bindings/darwin-amd64 v0.1.19 // indirect + github.com/duckdb/duckdb-go-bindings/darwin-arm64 v0.1.19 // indirect + github.com/duckdb/duckdb-go-bindings/linux-amd64 v0.1.19 // indirect + github.com/duckdb/duckdb-go-bindings/linux-arm64 v0.1.19 // indirect + github.com/duckdb/duckdb-go-bindings/windows-amd64 v0.1.19 // indirect github.com/elastic/go-grok v0.3.1 // indirect github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect @@ -122,8 +122,8 @@ require ( github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.6.0 // indirect github.com/go-git/go-git/v5 v5.13.0 // indirect - github.com/go-jose/go-jose/v4 v4.0.5 // indirect - github.com/go-logr/logr v1.4.2 // indirect + github.com/go-jose/go-jose/v4 v4.1.1 // indirect + github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-playground/locales v0.14.1 // indirect @@ -169,8 +169,8 @@ require ( github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/magefile/mage v1.15.0 // indirect github.com/magiconair/properties v1.8.7 // indirect - github.com/marcboeker/go-duckdb/arrowmapping v0.0.10 // indirect - github.com/marcboeker/go-duckdb/mapping v0.0.11 // indirect + github.com/marcboeker/go-duckdb/arrowmapping v0.0.19 // indirect + github.com/marcboeker/go-duckdb/mapping v0.0.19 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect @@ -220,30 +220,30 @@ require ( github.com/zclconf/go-cty-yaml v1.0.3 // indirect github.com/zeebo/errs v1.4.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/contrib/detectors/gcp v1.35.0 // indirect + go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/sdk v1.35.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.35.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/sdk v1.37.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect - golang.org/x/crypto v0.40.0 // indirect - golang.org/x/mod v0.26.0 // indirect - golang.org/x/net v0.42.0 // indirect - golang.org/x/oauth2 v0.29.0 // indirect + golang.org/x/crypto v0.41.0 // indirect + golang.org/x/mod v0.27.0 // indirect + golang.org/x/net v0.43.0 // indirect + golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/sync v0.16.0 // indirect - golang.org/x/sys v0.34.0 // indirect - golang.org/x/term v0.33.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/term v0.34.0 // indirect golang.org/x/time v0.11.0 // indirect - golang.org/x/tools v0.35.0 // indirect + golang.org/x/tools v0.36.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect google.golang.org/api v0.230.0 // indirect google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250414145226-207652e42e2e // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250425173222-7b384671a197 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 7ba9ebf6..1640c3f0 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -cel.dev/expr v0.23.0 h1:wUb94w6OYQS4uXraxo9U+wUAs9jT47Xvl4iPgAwM2ss= -cel.dev/expr v0.23.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= +cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= +cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -184,8 +184,8 @@ cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZ cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= -cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= -cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= +cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU= +cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo= cloud.google.com/go/contactcenterinsights v1.3.0/go.mod h1:Eu2oemoePuEFc/xKFPjbTuPSj0fYJcPls9TFlPNnHHY= cloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck= cloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w= @@ -621,8 +621,8 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 h1:ErKg/3iS1AKcTkf3yixlZ54f9U1rljCkQyEXWUnIUxc= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0/go.mod h1:yAZHSGnqScoU556rBOVkwLze6WP5N+U11RHuWaGVxwY= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 h1:UQUsRi8WTzhZntp5313l+CHIAT95ojUI2lpP/ExlZa4= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0/go.mod h1:Cz6ft6Dkn3Et6l2v2a9/RpN7epQ1GtDlO6lj8bEcOvw= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 h1:fYE9p3esPxA/C0rQ0AHhP0drtPXDRhaWiwg1DPqO7IU= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0/go.mod h1:BnBReJLvVYx2CS/UHOgVz2BXKXD9wsQPxZug20nZhd0= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0 h1:OqVGm6Ei3x5+yZmSJG1Mh2NwHvpVmZ08CB5qJhT9Nuk= @@ -651,8 +651,8 @@ github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHG github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/apache/arrow-go/v18 v18.4.0 h1:/RvkGqH517iY8bZKc4FD5/kkdwXJGjxf28JIXbJ/oB0= -github.com/apache/arrow-go/v18 v18.4.0/go.mod h1:Aawvwhj8x2jURIzD9Moy72cF0FyJXOpkYpdmGRHcw14= +github.com/apache/arrow-go/v18 v18.4.1 h1:q/jVkBWCJOB9reDgaIZIdruLQUb1kbkvOnOFezVH1C4= +github.com/apache/arrow-go/v18 v18.4.1/go.mod h1:tLyFubsAl17bvFdUAy24bsSvA/6ww95Iqi67fTpGu3E= github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI= github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= @@ -743,8 +743,8 @@ github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f h1:C5bqEmzEPLsHm9Mv73lSE9e9bKV23aB1vxOsmZrkl3k= -github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv1aFbZMiM9vblcSArJRf2Irls= +github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= github.com/containerd/containerd v1.7.27 h1:yFyEyojddO3MIGVER2xJLWoCIn+Up4GaHFquP7hsFII= @@ -774,18 +774,18 @@ github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUn github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E= github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/duckdb/duckdb-go-bindings v0.1.17 h1:SjpRwrJ7v0vqnIvLeVFHlhuS72+Lp8xxQ5jIER2LZP4= -github.com/duckdb/duckdb-go-bindings v0.1.17/go.mod h1:pBnfviMzANT/9hi4bg+zW4ykRZZPCXlVuvBWEcZofkc= -github.com/duckdb/duckdb-go-bindings/darwin-amd64 v0.1.12 h1:8CLBnsq9YDhi2Gmt3sjSUeXxMzyMQAKefjqUy9zVPFk= -github.com/duckdb/duckdb-go-bindings/darwin-amd64 v0.1.12/go.mod h1:Ezo7IbAfB8NP7CqPIN8XEHKUg5xdRRQhcPPlCXImXYA= -github.com/duckdb/duckdb-go-bindings/darwin-arm64 v0.1.12 h1:wjO4I0GhMh2xIpiUgRpzuyOT4KxXLoUS/rjU7UUVvCE= -github.com/duckdb/duckdb-go-bindings/darwin-arm64 v0.1.12/go.mod h1:eS7m/mLnPQgVF4za1+xTyorKRBuK0/BA44Oy6DgrGXI= -github.com/duckdb/duckdb-go-bindings/linux-amd64 v0.1.12 h1:HzKQi2C+1jzmwANsPuYH6x9Sfw62SQTjNAEq3OySKFI= -github.com/duckdb/duckdb-go-bindings/linux-amd64 v0.1.12/go.mod h1:1GOuk1PixiESxLaCGFhag+oFi7aP+9W8byymRAvunBk= -github.com/duckdb/duckdb-go-bindings/linux-arm64 v0.1.12 h1:YGSR7AFLw2gJ7IbgLE6DkKYmgKv1LaRSd/ZKF1yh2oE= -github.com/duckdb/duckdb-go-bindings/linux-arm64 v0.1.12/go.mod h1:o7crKMpT2eOIi5/FY6HPqaXcvieeLSqdXXaXbruGX7w= -github.com/duckdb/duckdb-go-bindings/windows-amd64 v0.1.12 h1:2aduW6fnFnT2Q45PlIgHbatsPOxV9WSZ5B2HzFfxaxA= -github.com/duckdb/duckdb-go-bindings/windows-amd64 v0.1.12/go.mod h1:IlOhJdVKUJCAPj3QsDszUo8DVdvp1nBFp4TUJVdw99s= +github.com/duckdb/duckdb-go-bindings v0.1.19 h1:t8fwgKlr/5BEa5TJzvo3Vdr3yAgoYiR7L/TqyMuUQ2k= +github.com/duckdb/duckdb-go-bindings v0.1.19/go.mod h1:pBnfviMzANT/9hi4bg+zW4ykRZZPCXlVuvBWEcZofkc= +github.com/duckdb/duckdb-go-bindings/darwin-amd64 v0.1.19 h1:CdNZfRcFUFxI4Q+1Tu4TBFln9tkIn6bDwVwh9LeEsoo= +github.com/duckdb/duckdb-go-bindings/darwin-amd64 v0.1.19/go.mod h1:Ezo7IbAfB8NP7CqPIN8XEHKUg5xdRRQhcPPlCXImXYA= +github.com/duckdb/duckdb-go-bindings/darwin-arm64 v0.1.19 h1:mVijr3WFz3TXZLtAm5Hb6qEnstacZdFI5QQNuE9R2QQ= +github.com/duckdb/duckdb-go-bindings/darwin-arm64 v0.1.19/go.mod h1:eS7m/mLnPQgVF4za1+xTyorKRBuK0/BA44Oy6DgrGXI= +github.com/duckdb/duckdb-go-bindings/linux-amd64 v0.1.19 h1:jhchUY24T5bQLOwGyK0BzB6+HQmsRjAbgUZDKWo4ajs= +github.com/duckdb/duckdb-go-bindings/linux-amd64 v0.1.19/go.mod h1:1GOuk1PixiESxLaCGFhag+oFi7aP+9W8byymRAvunBk= +github.com/duckdb/duckdb-go-bindings/linux-arm64 v0.1.19 h1:CFcH+Bze2OgTaTLM94P3gJ554alnCCDnt1BH/nO8RJ8= +github.com/duckdb/duckdb-go-bindings/linux-arm64 v0.1.19/go.mod h1:o7crKMpT2eOIi5/FY6HPqaXcvieeLSqdXXaXbruGX7w= +github.com/duckdb/duckdb-go-bindings/windows-amd64 v0.1.19 h1:x/8t04sgCVU8JL0XLUZWmC1FAX13ZjM58EmsyPjvrvY= +github.com/duckdb/duckdb-go-bindings/windows-amd64 v0.1.19/go.mod h1:IlOhJdVKUJCAPj3QsDszUo8DVdvp1nBFp4TUJVdw99s= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= @@ -851,13 +851,13 @@ github.com/go-git/go-git/v5 v5.13.0/go.mod h1:Wjo7/JyVKtQgUNdXYXIepzWfJQkUEIGvkv github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= -github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= +github.com/go-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI= +github.com/go-jose/go-jose/v4 v4.1.1/go.mod h1:BdsZGqgdO3b6tTc6LSE56wcDbMMLuPsw5d4ZD5f94kA= github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= @@ -1100,8 +1100,8 @@ github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrD github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.11 h1:0OwqZRYI2rFrjS4kvkDnqJkKHdHaRnCm68/DY4OxRzU= -github.com/klauspost/cpuid/v2 v2.2.11/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -1125,12 +1125,12 @@ github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg= github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/marcboeker/go-duckdb/arrowmapping v0.0.10 h1:G1W+GVnUefR8uy7jHdNO+CRMsmFG5mFPIHVAespfFCA= -github.com/marcboeker/go-duckdb/arrowmapping v0.0.10/go.mod h1:jccUb8TYD0p5TsEEeN4SXuslNJHo23QaKOqKD+U6uFU= -github.com/marcboeker/go-duckdb/mapping v0.0.11 h1:fusN1b1l7Myxafifp596I6dNLNhN5Uv/rw31qAqBwqw= -github.com/marcboeker/go-duckdb/mapping v0.0.11/go.mod h1:aYBjFLgfKO0aJIbDtXPiaL5/avRQISveX/j9tMf9JhU= -github.com/marcboeker/go-duckdb/v2 v2.3.5 h1:dpLZdPppUPdwd37/kDEE025iVgQoRw2Q4qXFtXroNIo= -github.com/marcboeker/go-duckdb/v2 v2.3.5/go.mod h1:8adNrftF4Ye29XMrpIl5NYNosTVsZu1mz3C82WdVvrk= +github.com/marcboeker/go-duckdb/arrowmapping v0.0.19 h1:kMxJBauR2+jwRoSFjiL/DysQtKRBCkNSLZz7GUvEG8A= +github.com/marcboeker/go-duckdb/arrowmapping v0.0.19/go.mod h1:19JWoch6I++gIrWUz1MLImIoFGri9yL54JaWn/Ujvbo= +github.com/marcboeker/go-duckdb/mapping v0.0.19 h1:xZ7LCyFZZm/4X631lOZY74p3QHINMnWJ+OakKw5d3Ao= +github.com/marcboeker/go-duckdb/mapping v0.0.19/go.mod h1:Kz9xYOkhhkgCaGgAg34ciKaks9ED2V7BzHzG6dnVo/o= +github.com/marcboeker/go-duckdb/v2 v2.4.0 h1:XztCDzB0fYvokiVer1myuFX4QvOdnicdTPRp4D+x2Ok= +github.com/marcboeker/go-duckdb/v2 v2.4.0/go.mod h1:qpTBjqtTS5+cfD3o2Sl/W70cmxKj6zhjtvVxs1Wuy7k= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= @@ -1290,8 +1290,8 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8= +github.com/stretchr/testify v1.11.0/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/thediveo/enumflag/v2 v2.0.5 h1:VJjvlAqUb6m6mxOrB/0tfBJI0Kvi9wJ8ulh38xK87i8= @@ -1308,8 +1308,8 @@ github.com/turbot/go-kit v1.3.0 h1:6cIYPAO5hO9fG7Zd5UBC4Ch3+C6AiiyYS0UQnrUlTV0= github.com/turbot/go-kit v1.3.0/go.mod h1:piKJMYCF8EYmKf+D2B78Csy7kOHGmnQVOWingtLKWWQ= github.com/turbot/go-prompt v0.2.6-steampipe.0.0.20221028122246-eb118ec58d50 h1:zs87uA6QZsYLk4RRxDOIxt8ro/B2V6HzoMWm05Lo7ao= github.com/turbot/go-prompt v0.2.6-steampipe.0.0.20221028122246-eb118ec58d50/go.mod h1:vFnjEGDIIA/Lib7giyE4E9c50Lvl8j0S+7FVlAwDAVw= -github.com/turbot/pipe-fittings/v2 v2.7.0-rc.2 h1:FfKLkfbNmwxyPQIqDCd7m6o9bmtPB7D8a5txbVzjZp4= -github.com/turbot/pipe-fittings/v2 v2.7.0-rc.2/go.mod h1:V619+tgfLaqoEXFDNzA2p24TBZVf4IkDL9FDLQecMnE= +github.com/turbot/pipe-fittings/v2 v2.7.0 h1:eCmpMNlVtV3AxOzsn8njE3O6aoHc74WVAHOntia2hqY= +github.com/turbot/pipe-fittings/v2 v2.7.0/go.mod h1:V619+tgfLaqoEXFDNzA2p24TBZVf4IkDL9FDLQecMnE= github.com/turbot/pipes-sdk-go v0.12.0 h1:esbbR7bALa5L8n/hqroMPaQSSo3gNM/4X0iTmHa3D6U= github.com/turbot/pipes-sdk-go v0.12.0/go.mod h1:Mb+KhvqqEdRbz/6TSZc2QWDrMa5BN3E4Xw+gPt2TRkc= github.com/turbot/tailpipe-plugin-core v0.2.10 h1:2+B7W4hzyS/pBr1y5ns9w84piWGq/x+WdCUjyPaPreQ= @@ -1355,24 +1355,24 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/detectors/gcp v1.35.0 h1:bGvFt68+KTiAKFlacHW6AhA56GF2rS0bdD3aJYEnmzA= -go.opentelemetry.io/contrib/detectors/gcp v1.35.0/go.mod h1:qGWP8/+ILwMRIUf9uIVLloR1uo5ZYAslM4O6OqUi1DA= +go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw= +go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.35.0 h1:PB3Zrjs1sG1GBX51SXyTSoOTqcDglmsk7nT6tkKPb/k= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.35.0/go.mod h1:U2R3XyVPzn0WX7wOIypPuptulsMcPDPs/oiSVOMVnHY= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= -go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= -go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= -go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= @@ -1393,8 +1393,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= -golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= -golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= +golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= +golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1457,8 +1457,8 @@ golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= -golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= +golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= +golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1522,8 +1522,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= -golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= -golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1553,8 +1553,8 @@ golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= -golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98= -golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1671,8 +1671,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= -golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1688,8 +1688,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= -golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg= -golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= +golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= +golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1710,8 +1710,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= -golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1783,8 +1783,8 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= -golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -2005,10 +2005,10 @@ google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOl google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb h1:ITgPrl429bc6+2ZraNSzMDk3I95nmQln2fuPstKwFDE= google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:sAo5UzpjUwgFBCzupwhcLcxHVDK7vG5IqI30YnwX2eE= -google.golang.org/genproto/googleapis/api v0.0.0-20250414145226-207652e42e2e h1:UdXH7Kzbj+Vzastr5nVfccbmFsmYNygVLSPk1pEfDoY= -google.golang.org/genproto/googleapis/api v0.0.0-20250414145226-207652e42e2e/go.mod h1:085qFyf2+XaZlRdCgKNCIZ3afY2p4HHZdoIRpId8F4A= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250425173222-7b384671a197 h1:29cjnHVylHwTzH66WfFZqgSQgnxzvWE+jvBwpZCLRxY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250425173222-7b384671a197/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 h1:FiusG7LWj+4byqhbvmB+Q93B/mOxJLN2DTozDuZm4EU= +google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:kXqgZtrWaf6qS3jZOCnCH7WYfrvFjkC51bM8fz3RsCA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -2050,8 +2050,8 @@ google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5v google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= -google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= -google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= +google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4= +google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -2071,8 +2071,8 @@ google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= -google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= +google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= diff --git a/internal/cmdconfig/cmd_hooks.go b/internal/cmdconfig/cmd_hooks.go index c07cfac7..2272aa00 100644 --- a/internal/cmdconfig/cmd_hooks.go +++ b/internal/cmdconfig/cmd_hooks.go @@ -14,7 +14,8 @@ import ( "github.com/turbot/pipe-fittings/v2/cmdconfig" pconstants "github.com/turbot/pipe-fittings/v2/constants" "github.com/turbot/pipe-fittings/v2/contexthelpers" - "github.com/turbot/pipe-fittings/v2/error_helpers" + perror_helpers "github.com/turbot/pipe-fittings/v2/error_helpers" + "github.com/turbot/pipe-fittings/v2/filepaths" pparse "github.com/turbot/pipe-fittings/v2/parse" "github.com/turbot/pipe-fittings/v2/task" @@ -49,7 +50,7 @@ func preRunHook(cmd *cobra.Command, args []string) error { // display any warnings ew.ShowWarnings() // check for error - error_helpers.FailOnError(ew.Error) + perror_helpers.FailOnError(ew.Error) // pump in the initial set of logs (AFTER we have loaded the config, which may specify log level) displayStartupLog() @@ -76,15 +77,17 @@ func preRunHook(cmd *cobra.Command, args []string) error { // the new metadata model. // start migration err := migration.MigrateDataToDucklake(cmd.Context()) - if error_helpers.IsContextCancelledError(err) { - // suppress Cobra's usage/errors only for this cancelled invocation - // Cobra prints usage when a command returns an error. The cancellation returns an error (context cancelled) - // from preRun, so Cobra assumes "user error" and shows help. - // This conditional block sets cmd.SilenceUsage = true and cmd.SilenceErrors = true only for cancellation, - // telling Cobra "don't print usage or re-print the error". Without it, you get the usage dump. + if err != nil { + // we do not want Cobra usage errors for migration errors - suppress + + // suppress usage and error printing for migration errors cmd.SilenceUsage = true - cmd.SilenceErrors = true + // for cancelled errors, also silence the error message + if perror_helpers.IsCancelledError(err) { + cmd.SilenceErrors = true + } } + // return (possibly nil) error from migration return err } @@ -154,7 +157,7 @@ func runScheduledTasks(ctx context.Context, cmd *cobra.Command, args []string) c } // initConfig reads in config file and ENV variables if set. -func initGlobalConfig(ctx context.Context) error_helpers.ErrorAndWarnings { +func initGlobalConfig(ctx context.Context) perror_helpers.ErrorAndWarnings { utils.LogTime("cmdconfig.initGlobalConfig start") defer utils.LogTime("cmdconfig.initGlobalConfig end") @@ -171,14 +174,14 @@ func initGlobalConfig(ctx context.Context) error_helpers.ErrorAndWarnings { // load workspace profile from the configured install dir loader, err := cmdconfig.GetWorkspaceProfileLoader[*workspace_profile.TailpipeWorkspaceProfile](parseOpts...) if err != nil { - return error_helpers.NewErrorsAndWarning(err) + return perror_helpers.NewErrorsAndWarning(err) } config.GlobalWorkspaceProfile = loader.GetActiveWorkspaceProfile() // create the required data and internal folder for this workspace if needed err = config.GlobalWorkspaceProfile.EnsureWorkspaceDirs() if err != nil { - return error_helpers.NewErrorsAndWarning(err) + return perror_helpers.NewErrorsAndWarning(err) } var cmd = viper.Get(pconstants.ConfigKeyActiveCommand).(*cobra.Command) @@ -199,7 +202,7 @@ func initGlobalConfig(ctx context.Context) error_helpers.ErrorAndWarnings { // NOTE: if this installed the core plugin, the plugin version file will be updated and the updated file returned pluginVersionFile, err := plugin.EnsureCorePlugin(ctx) if err != nil { - return error_helpers.NewErrorsAndWarning(err) + return perror_helpers.NewErrorsAndWarning(err) } // load the connection config and HCL options (passing plugin versions diff --git a/internal/database/compact.go b/internal/database/compact.go index a9e9c7b4..c81b5a0a 100644 --- a/internal/database/compact.go +++ b/internal/database/compact.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "fmt" + "log/slog" "strings" "time" @@ -45,15 +46,13 @@ func CompactDataFiles(ctx context.Context, db *DuckDb, updateFunc func(Compactio return err } - slog.Info("[SKIPPING] Merging adjacent DuckLake parquet files") - // TODO merge_adjacent_files sometimes crashes, awaiting fix from DuckDb https://github.com/turbot/tailpipe/issues/530 // so we should now have multiple, time ordered parquet files // now merge the the parquet files in the duckdb database // the will minimise the parquet file count to the optimum - // if err := mergeParquetFiles(ctx, db); err != nil { - // slog.Error("Failed to merge DuckLake parquet files", "error", err) - // return nil, err - // } + if err := mergeParquetFiles(ctx, db); err != nil { + slog.Error("Failed to merge DuckLake parquet files", "error", err) + return err + } // delete unused files if err := cleanupExpiredFiles(ctx, db); err != nil { @@ -77,17 +76,16 @@ func CompactDataFiles(ctx context.Context, db *DuckDb, updateFunc func(Compactio return nil } -// TODO merge_adjacent_files sometimes crashes, awaiting fix from DuckDb https://github.com/turbot/tailpipe/issues/530 -//// mergeParquetFiles combines adjacent parquet files in the DuckDB database. -//func mergeParquetFiles(ctx context.Context, db *database.DuckDb) error { -// if _, err := db.ExecContext(ctx, "call merge_adjacent_files()"); err != nil { -// if ctx.Err() != nil { -// return err -// } -// return fmt.Errorf("failed to merge parquet files: %w", err) -// } -// return nil -//} +// mergeParquetFiles combines adjacent parquet files in the DuckDB database. +func mergeParquetFiles(ctx context.Context, db *DuckDb) error { + if _, err := db.ExecContext(ctx, "call merge_adjacent_files()"); err != nil { + if ctx.Err() != nil { + return err + } + return fmt.Errorf("failed to merge parquet files: %w", err) + } + return nil +} // we order data files as follows: // - get list of partition keys matching patterns. For each key: diff --git a/internal/database/compaction_types.go b/internal/database/compaction_types.go index 4a2f98e1..2fc88d87 100644 --- a/internal/database/compaction_types.go +++ b/internal/database/compaction_types.go @@ -17,7 +17,7 @@ func getTimeRangesToReorder(ctx context.Context, db *DuckDb, pk *partitionKey, r if reindex { rm, err := newReorderMetadata(ctx, db, pk) if err != nil { - return nil, fmt.Errorf("failed to retiever stats for partition key: %w", err) + return nil, fmt.Errorf("failed to retrieve stats for partition key: %w", err) } // make a single time range @@ -52,7 +52,7 @@ func getTimeRangesToReorder(ctx context.Context, db *DuckDb, pk *partitionKey, r // get stats for the partition key rm, err := newReorderMetadata(ctx, db, pk) if err != nil { - return nil, fmt.Errorf("failed to retiever stats for partition key: %w", err) + return nil, fmt.Errorf("failed to retrieve stats for partition key: %w", err) } rm.unorderedRanges = unorderedRanges return rm, nil @@ -77,7 +77,7 @@ func getFileRangesForPartitionKey(ctx context.Context, db *DuckDb, pk *partition on df.data_file_id = fpv4.data_file_id and fpv4.partition_key_index = 3 join __ducklake_metadata_tailpipe_ducklake.ducklake_table t on df.table_id = t.table_id - join __ducklake_metadata_tailpipe_ducklake.ducklake_file_column_statistics fcs + join __ducklake_metadata_tailpipe_ducklake.ducklake_file_column_stats fcs on df.data_file_id = fcs.data_file_id and df.table_id = fcs.table_id join __ducklake_metadata_tailpipe_ducklake.ducklake_column c diff --git a/internal/database/duck_db.go b/internal/database/duck_db.go index a6f5fcb2..d3033dbb 100644 --- a/internal/database/duck_db.go +++ b/internal/database/duck_db.go @@ -271,7 +271,6 @@ func GetDucklakeInitCommands(readonly bool) []SqlCommand { attachOptions := []string{ fmt.Sprintf("data_path '%s'", config.GlobalWorkspaceProfile.GetDataDir()), "meta_journal_mode 'WAL'", - "meta_synchronous 'NORMAL'", } // if readonly mode is requested, add the option if readonly { diff --git a/internal/database/tables.go b/internal/database/tables.go index 4d8f7acb..a38c1485 100644 --- a/internal/database/tables.go +++ b/internal/database/tables.go @@ -113,6 +113,11 @@ func GetLegacyTableViewSchema(ctx context.Context, viewName string, db *DuckDb) return nil, fmt.Errorf("failed to scan column schema: %w", err) } + // NOTE: legacy tailpipe views may include `rowid` which we must exclude from the schema as this is a DuckDb system column + // that is automatically added to every table + if columnName == "rowid" { + continue + } col := buildColumnSchema(columnName, columnType) ts.Columns = append(ts.Columns, col) } diff --git a/internal/error_helpers/error_helpers.go b/internal/error_helpers/error_helpers.go index 611ba7d4..8c55d866 100644 --- a/internal/error_helpers/error_helpers.go +++ b/internal/error_helpers/error_helpers.go @@ -108,14 +108,14 @@ func PrefixError(err error, prefix string) error { return fmt.Errorf("%s: %s\n", prefix, TransformErrorToTailpipe(err).Error()) } -// isMachineReadableOutput checks if the current output format is machine readable (CSV or JSON) -func isMachineReadableOutput() bool { +// IsMachineReadableOutput checks if the current output format is machine readable (CSV or JSON) +func IsMachineReadableOutput() bool { outputFormat := viper.GetString(constants.ArgOutput) - return outputFormat == constants.OutputFormatCSV || outputFormat == constants.OutputFormatJSON + return outputFormat == constants.OutputFormatCSV || outputFormat == constants.OutputFormatJSON || outputFormat == constants.OutputFormatLine } func GetWarningOutputStream() io.Writer { - if isMachineReadableOutput() { + if IsMachineReadableOutput() { // For machine-readable formats, output warnings and errors to stderr return os.Stderr } diff --git a/internal/migration/errors.go b/internal/migration/errors.go new file mode 100644 index 00000000..231ba6f0 --- /dev/null +++ b/internal/migration/errors.go @@ -0,0 +1,27 @@ +package migration + +import "fmt" + +// UnsupportedError represents an error when migration is not supported +// due to specific command line arguments or configuration +type UnsupportedError struct { + Reason string +} + +func (e *UnsupportedError) Error() string { + msgFormat := "data must be migrated to Ducklake format - migration is not supported with '%s'.\n\nRun 'tailpipe query' to migrate your data to DuckLake format" + return fmt.Sprintf(msgFormat, e.Reason) +} + +func (e *UnsupportedError) Is(target error) bool { + _, ok := target.(*UnsupportedError) + return ok +} + +func (e *UnsupportedError) As(target interface{}) bool { + if t, ok := target.(**UnsupportedError); ok { + *t = e + return true + } + return false +} diff --git a/internal/migration/migration.go b/internal/migration/migration.go index 49c84c21..08893322 100644 --- a/internal/migration/migration.go +++ b/internal/migration/migration.go @@ -1,6 +1,7 @@ package migration import ( + "bufio" "context" "database/sql" "fmt" @@ -12,6 +13,8 @@ import ( "time" "github.com/briandowns/spinner" + "github.com/spf13/viper" + "github.com/turbot/pipe-fittings/v2/constants" perr "github.com/turbot/pipe-fittings/v2/error_helpers" "github.com/turbot/pipe-fittings/v2/utils" "github.com/turbot/tailpipe-plugin-sdk/schema" @@ -21,22 +24,44 @@ import ( "github.com/turbot/tailpipe/internal/filepaths" ) +// StatusType represents different types of migration status messages +type StatusType int + +const ( + InitialisationFailed StatusType = iota + MigrationFailed + CleanupAfterSuccess + PartialSuccess + Success +) + // MigrateDataToDucklake performs migration of views from tailpipe.db and associated parquet files // into the new DuckLake metadata catalog -func MigrateDataToDucklake(ctx context.Context) error { +func MigrateDataToDucklake(ctx context.Context) (err error) { + slog.Info("Starting data migration to DuckLake format") + // define a status message var - this will be set when we encounter any issues - or when we are successful + // this will be printed at the end of the function + var statusMsg string + var partialMigrated bool + + // if there is a status message, print it out at the end + defer func() { + if statusMsg != "" { + if err != nil || partialMigrated { + // if there is an error or a partial migration, show as warning + error_helpers.ShowWarning(statusMsg) + } else { + // show as info if there is no error, or if it is not a partial migration + error_helpers.ShowInfo(statusMsg) + } + } + }() + // Determine source and migration directories dataDefaultDir := config.GlobalWorkspaceProfile.GetDataDir() migratingDefaultDir := config.GlobalWorkspaceProfile.GetMigratingDir() - // failed dir is derived via GetMigrationFailedDir() where needed var matchedTableDirs, unmatchedTableDirs []string - status := NewMigrationStatus(0) - cancelledHandled := false - defer func() { - if ctx.Err() != nil && !cancelledHandled { - _ = onCancelled(status) - } - }() // if the ~/.tailpipe/data directory has a .db file, it means that this is the first time we are migrating // if the ~/.tailpipe/migration/migrating directory has a .db file, it means that this is a resume migration @@ -45,7 +70,7 @@ func MigrateDataToDucklake(ctx context.Context) error { // validate: both should not be true - return that last migration left things in a bad state if initialMigration && continueMigration { - return fmt.Errorf("Invalid migration state: found tailpipe.db in both data and migrating directories. This should not happen. Please contact Turbot support for assistance.") + return fmt.Errorf("invalid migration state: found tailpipe.db in both data and migrating directories. This should not happen. Please contact Turbot support for assistance") } // STEP 1: Check if migration is needed @@ -55,6 +80,36 @@ func MigrateDataToDucklake(ctx context.Context) error { return nil } + // if the output for this command is a machine readable format (csv/json) or progress is false, + // it is possible/likely that tailpipe is being used in a non interactive way - in this case, + // we should not prompt the user, instead return an error + if err := checkMigrationSupported(); err != nil { + return err + } + + // Prompt the user to confirm migration + shouldContinue, err := promptUserForMigration(ctx, dataDefaultDir) + if err != nil { + return fmt.Errorf("failed to get user confirmation: %w", err) + } + if !shouldContinue { + return context.Canceled + } + + logPath := filepath.Join(config.GlobalWorkspaceProfile.GetMigrationDir(), "migration.log") + + // Spinner for migration progress + sp := spinner.New( + spinner.CharSets[14], + 100*time.Millisecond, + spinner.WithHiddenCursor(true), + spinner.WithWriter(os.Stdout), + ) + // set suffix and start + sp.Suffix = " Migrating data to DuckLake format" + sp.Start() + defer sp.Stop() + // Choose DB path for discovery // If this is the first time we are migrating, we need to use .db file from the ~/.tailpipe/data directory // If this is a resume migration, we need to use .db file from the ~/.tailpipe/migration/migrating directory @@ -65,22 +120,24 @@ func MigrateDataToDucklake(ctx context.Context) error { discoveryDbPath = filepath.Join(migratingDefaultDir, "tailpipe.db") } + sp.Suffix = " Migrating data to DuckLake format: discover tables to migrate" // STEP 2: Discover legacy tables and their schemas (from chosen DB path) // This returns the list of views and a map of view name to its schema views, schemas, err := discoverLegacyTablesAndSchemas(ctx, discoveryDbPath) if err != nil { + statusMsg = getStatusMessage(ctx, InitialisationFailed, "") return fmt.Errorf("failed to discover legacy tables: %w", err) } - slog.Info("Views: ", "views", views) - slog.Info("Schemas: ", "schemas", schemas) - // STEP 3: If this is the first time we are migrating(tables in ~/.tailpipe/data) then move the whole contents of data dir // into ~/.tailpipe/migration/migrating respecting the same folder structure. - // First-run: transactionally move contents via moveDirContents: copy data to migrating, move tailpipe.db to migrated, - // then empty the original data directory. On any failure, the migrating directory is removed. + // We do this by simply renaming the directory. if initialMigration { - if err := moveDirContents(ctx, dataDefaultDir, migratingDefaultDir); err != nil { + sp.Suffix = " Migrating data to DuckLake format: moving legacy data to migration area" + + if err := moveDataToMigrating(ctx, dataDefaultDir, migratingDefaultDir); err != nil { + slog.Error("Failed to move data to migrating directory", "error", err) + statusMsg = getStatusMessage(ctx, InitialisationFailed, logPath) return err } } @@ -96,103 +153,205 @@ func MigrateDataToDucklake(ctx context.Context) error { baseDir := migratingDefaultDir matchedTableDirs, unmatchedTableDirs, err = findMatchingTableDirs(baseDir, views) if err != nil { - return err - } - // move the unmatched table directories to 'unmigrated' - if err = archiveUnmatchedDirs(ctx, unmatchedTableDirs); err != nil { - return err + statusMsg = getStatusMessage(ctx, MigrationFailed, logPath) + return fmt.Errorf("failed to find matching table directories: %w", err) } - // Initialize status with total tables to migrate - status.Total = len(matchedTableDirs) - status.update() + if len(unmatchedTableDirs) > 0 { + sp.Suffix = " Migrating data to DuckLake format: archiving tables without views" + // move the unmatched table directories to 'unmigrated' + if err = archiveUnmatchedDirs(ctx, unmatchedTableDirs); err != nil { + statusMsg = getStatusMessage(ctx, MigrationFailed, logPath) + return fmt.Errorf("failed to archive unmatched table directories: %w", err) + } + } // Pre-compute total parquet files across matched directories + sp.Suffix = " Migrating data to DuckLake format: counting parquet files" totalFiles, err := countParquetFiles(ctx, matchedTableDirs) - if err == nil { - status.TotalFiles = totalFiles - status.updateFiles() + if err != nil { + return fmt.Errorf("failed to count parquet files: %w", err) } - // Spinner for migration progress - sp := spinner.New( - spinner.CharSets[14], - 100*time.Millisecond, - spinner.WithHiddenCursor(true), - spinner.WithWriter(os.Stdout), - ) - sp.Suffix = fmt.Sprintf(" Migrating tables to DuckLake (%d/%d, %0.1f%%) | parquet files (%d/%d)", status.Migrated, status.Total, status.ProgressPercent, status.MigratedFiles, status.TotalFiles) - sp.Start() - - updateStatus := func(st *MigrationStatus) { - sp.Suffix = fmt.Sprintf(" Migrating tables to DuckLake (%d/%d, %0.1f%%) | parquet files (%d/%d)", st.Migrated, st.Total, st.ProgressPercent, st.MigratedFiles, st.TotalFiles) + // create an update func to update th espinner + updateFunc := func(st *MigrationStatus) { + sp.Suffix = fmt.Sprintf(" Migrating data to DuckLake format | tables (%d/%d) | parquet files (%d/%d, %0.1f%%)", st.MigratedTables, st.TotalTables, st.MigratedFiles, st.TotalFiles, st.ProgressPercent) } + // Initialize migration status, paaing in the file and table count and status update func + totalTables := len(matchedTableDirs) + status := NewMigrationStatus(totalFiles, totalTables, updateFunc) + // ensure we save the status to file at the end + defer func() { + // add any error to status and write to file before returning + if err != nil { + status.AddError(err) + if perr.IsContextCancelledError(ctx.Err()) { + // set cancel status and prune the tree + _ = onCancelled(status) + } else { + status.Finish("FAILED") + } + } + + // write the status back + _ = status.WriteStatusToFile() + }() + + // call initial update on status - this will set the spinner message correctly + status.update() + // STEP 5: Do Migration: Traverse matched table directories, find leaf nodes with parquet files, // and perform INSERT within a transaction. On success, move leaf dir to migrated. - err = doMigration(ctx, matchedTableDirs, schemas, status, updateStatus) - sp.Stop() - - logPath := filepath.Join(config.GlobalWorkspaceProfile.GetMigrationDir(), "migration.log") - // If cancellation arrived after doMigration returned, prefer the CANCELLED outcome + err = doMigration(ctx, matchedTableDirs, schemas, status) + // If cancellation arrived during migration, prefer the CANCELLED outcome and do not + // treat it as a failure (which would incorrectly move tailpipe.db to failed) if perr.IsContextCancelledError(ctx.Err()) { - status.Finish("CANCELLED") - _ = status.WriteStatusToFile() - perr.ShowWarning(fmt.Sprintf("Migration cancelled. It will automatically resume next time you run Tailpipe.\nFor details, see %s\n", logPath)) - cancelledHandled = true + statusMsg = getStatusMessage(ctx, MigrationFailed, logPath) return ctx.Err() } + if err != nil { + statusMsg = getStatusMessage(ctx, MigrationFailed, logPath) + return fmt.Errorf("migration failed: %w", err) + } + // Post-migration outcomes - if status.Failed > 0 { + + if status.FailedTables > 0 { if err := onFailed(status); err != nil { - return err + statusMsg = getStatusMessage(ctx, MigrationFailed, logPath) + return fmt.Errorf("failed to cleanup after failed migration: %w", err) } - perr.ShowWarning(fmt.Sprintf("Your data has been migrated to DuckLake with issues.\nFor details, see %s\n", logPath)) - } else { - if err := onSuccessful(status); err != nil { - return err - } - error_helpers.ShowInfo(fmt.Sprintf("Your data has been migrated to DuckLake.\nFor details, see %s\n", logPath)) + partialMigrated = true + statusMsg = getStatusMessage(ctx, PartialSuccess, logPath) + return err + } + // so we are successful - cleanup + if err := onSuccessful(status); err != nil { + statusMsg = getStatusMessage(ctx, CleanupAfterSuccess, logPath) + return fmt.Errorf("failed to cleanup after successful migration: %w", err) + } + + // all good! + statusMsg = getStatusMessage(ctx, Success, logPath) + return err } -// moveDirContents handles the initial migration move: copy data dir into migrating and move the legacy DB -// into migrated. If any step fails, it removes the migrating directory and shows a support warning. -func moveDirContents(ctx context.Context, dataDefaultDir, migratingDefaultDir string) (err error) { - migratedDir := config.GlobalWorkspaceProfile.GetMigratedDir() - defer func() { - if err != nil { - _ = os.RemoveAll(migratingDefaultDir) - perr.ShowWarning(fmt.Sprintf("Migration initialisation failed. Cleaned up '%s'. Please contact Turbot support.", migratingDefaultDir)) +// check if the data migration is supported, based on the current arguments +// if the output for this command is a machine readable format (csv/json) or progress is false, +// it is possible/likely that tailpipe is being used in a non interactive way - in this case, +// we should not prompt the user, instead return an error +// NOTE: set exit code to +func checkMigrationSupported() error { + if error_helpers.IsMachineReadableOutput() { + return &UnsupportedError{ + Reason: "--output " + viper.GetString(constants.ArgOutput), } - }() + } else if viper.IsSet(constants.ArgProgress) && !viper.GetBool(constants.ArgProgress) { + return &UnsupportedError{ + Reason: "--progress=false", + } + } + return nil +} - // 1) Ensure the destination for the DB exists first - // Reason: we will move tailpipe.db after copying data; guaranteeing the target avoids partial moves later. - if err = os.MkdirAll(migratedDir, 0755); err != nil { - return err +// moveDataToMigrating ensures the migration folder exists and handles any existing migrating folder +func moveDataToMigrating(ctx context.Context, dataDefaultDir, migratingDefaultDir string) error { + // Ensure the 'migrating' folder exists + migrationDir := config.GlobalWorkspaceProfile.GetMigratingDir() + if err := os.MkdirAll(migrationDir, 0755); err != nil { + return fmt.Errorf("failed to create migration directory: %w", err) } - // 2) Copy ALL data from data/default -> migration/migrating/default (do not delete source yet) - // Reason: copying first keeps the legacy data readable if the process crashes midway. - if err = utils.CopyDir(ctx, dataDefaultDir, migratingDefaultDir); err != nil { - return err + + // If the migrating folder exists, it can't have a db as we already checked - delete it + if _, err := os.Stat(migratingDefaultDir); err == nil { + // Directory exists, remove it since we already verified it doesn't contain a db + if err := os.RemoveAll(migratingDefaultDir); err != nil { + return fmt.Errorf("failed to remove existing migrating directory: %w", err) + } } - // 3) Move the DB file from data/default -> migration/migrated/default - // Reason: once data copy succeeded, moving tailpipe.db signals the backup exists and clarifies resume semantics. - if err = utils.MoveFile(filepath.Join(dataDefaultDir, "tailpipe.db"), filepath.Join(migratedDir, "tailpipe.db")); err != nil { - return err + + // Now move the data directory to the migrating directory + if err := os.Rename(dataDefaultDir, migratingDefaultDir); err != nil { + return fmt.Errorf("failed to move data to migration area: %w", err) } - // 4) Empty the original data directory last to emulate an atomic move - // Reason: only after successful copy+db move do we clear the source so we never strand users without their legacy data. - if err = utils.EmptyDir(dataDefaultDir); err != nil { - return err + + // now recreate the moved folder + if err := os.MkdirAll(dataDefaultDir, 0755); err != nil { + return fmt.Errorf("failed to recreate data directory after moving: %w", err) } return nil } +// promptUserForMigration prompts the user to confirm migration and returns true if they want to continue +func promptUserForMigration(ctx context.Context, dataDir string) (bool, error) { + // Check if context is already cancelled + if ctx.Err() != nil { + return false, ctx.Err() + } + + //nolint: forbidigo // UI output + fmt.Printf("This version of Tailpipe requires your data to be migrated to the new Ducklake format.\n\nThis operation is irreversible. If desired, back up your data folder (%s) before proceeding.\n\nContinue? [y/N]: ", dataDir) + + // Use goroutine to read input while allowing context cancellation + type result struct { + response string + err error + } + + resultChan := make(chan result, 1) + go func() { + reader := bufio.NewReader(os.Stdin) + response, err := reader.ReadString('\n') + resultChan <- result{response, err} + }() + + select { + case <-ctx.Done(): + return false, ctx.Err() + case res := <-resultChan: + if res.err != nil { + return false, fmt.Errorf("failed to read user input: %w", res.err) + } + + response := strings.TrimSpace(strings.ToLower(res.response)) + return response == "y" || response == "yes", nil + } +} + +// getStatusMessage returns the appropriate status message based on error type and context +// It handles cancellation checking internally and returns the appropriate message +func getStatusMessage(ctx context.Context, msgType StatusType, logPath string) string { + // Handle cancellation first + if perr.IsContextCancelledError(ctx.Err()) { + switch msgType { + case InitialisationFailed: + return "Migration cancelled. Migration data cleaned up and all original data files remain unchanged. Migration will automatically resume next time you run Tailpipe.\n" + default: + return "Migration cancelled. Migration will automatically resume next time you run Tailpipe.\n" + } + } + + // Handle non-cancellation cases + switch msgType { + case InitialisationFailed: + return "Migration initialisation failed.\nMigration data cleaned up and all original data files remain unchanged. Migration will automatically resume next time you run Tailpipe.\n" + case MigrationFailed: + return fmt.Sprintf("Migration failed.\nFor details, see %s\nPlease contact Turbot support on Slack (#tailpipe).", logPath) + case CleanupAfterSuccess: + return fmt.Sprintf("Migration succeeded but cleanup failed\nFor details, see %s\n", logPath) + case PartialSuccess: + return fmt.Sprintf("Your data has been migrated to DuckLake, but some files could not be migrated.\nFor details, see %s\nIf you need help, please contact Turbot support on Slack (#tailpipe).", logPath) + // success + default: + return fmt.Sprintf("Your data has been migrated to DuckLake format.\nFor details, see %s\n", logPath) + } +} + // discoverLegacyTablesAndSchemas enumerates legacy DuckDB views and, for each view, its schema. // It returns the list of view names and a map of view name to its schema (column->type). // If the legacy database contains no views, both return values are empty. @@ -284,8 +443,6 @@ func migrateTableDirectory(ctx context.Context, db *database.DuckDb, tableName s func migrateParquetFiles(ctx context.Context, db *database.DuckDb, tableName string, dirPath string, ts *schema.TableSchema, status *MigrationStatus, parquetFiles []string) error { filesInLeaf := len(parquetFiles) - // Placeholder: validate schema (from 'ts') against parquet files if needed - slog.Info("Found leaf node with parquet files", "table", tableName, "dir", dirPath, "files", filesInLeaf) // Begin transaction tx, err := db.BeginTx(ctx, nil) @@ -317,29 +474,13 @@ func migrateParquetFiles(ctx context.Context, db *database.DuckDb, tableName str slog.Info("Successfully committed transaction", "table", tableName, "dir", dirPath, "files", filesInLeaf) - // On success, move the entire leaf directory from migrating to migrated - migratingRoot := config.GlobalWorkspaceProfile.GetMigratingDir() - migratedRoot := config.GlobalWorkspaceProfile.GetMigratedDir() - rel, err := filepath.Rel(migratingRoot, dirPath) - if err != nil { - moveTableDirToFailed(ctx, dirPath) - status.OnFilesFailed(filesInLeaf) - return err - } - destDir := filepath.Join(migratedRoot, rel) - if err := os.MkdirAll(filepath.Dir(destDir), 0755); err != nil { - moveTableDirToFailed(ctx, dirPath) - status.OnFilesFailed(filesInLeaf) - return err - } - if err := utils.MoveDirContents(ctx, dirPath, destDir); err != nil { - moveTableDirToFailed(ctx, dirPath) - status.OnFilesFailed(filesInLeaf) - return err + // Clean up the now-empty source dir. If this fails (e.g., hidden files), log and continue; + // do NOT classify as a failed migration, since data has been committed successfully. + if err := os.RemoveAll(dirPath); err != nil { + slog.Warn("Cleanup: could not remove migrated leaf directory", "table", tableName, "dir", dirPath, "error", err) } - _ = os.Remove(dirPath) status.OnFilesMigrated(filesInLeaf) - slog.Info("Migrated leaf node", "table", tableName, "source", dirPath, "destination", destDir) + slog.Debug("Migrated leaf node", "table", tableName, "source", dirPath) return nil } @@ -374,10 +515,7 @@ func archiveUnmatchedDirs(ctx context.Context, unmatchedTableDirs []string) erro } // doMigration performs the migration of the matched table directories and updates status -func doMigration(ctx context.Context, matchedTableDirs []string, schemas map[string]*schema.TableSchema, status *MigrationStatus, onUpdate func(*MigrationStatus)) error { - if onUpdate == nil { - return fmt.Errorf("onUpdate callback is required") - } +func doMigration(ctx context.Context, matchedTableDirs []string, schemas map[string]*schema.TableSchema, status *MigrationStatus) error { ducklakeDb, err := database.NewDuckDb(database.WithDuckLake()) if err != nil { return err @@ -396,14 +534,16 @@ func doMigration(ctx context.Context, matchedTableDirs []string, schemas map[str } else { status.OnTableMigrated() } - // update our status - onUpdate(status) } return nil } // moveTableDirToFailed moves a table directory from migrating to failed, preserving relative path. func moveTableDirToFailed(ctx context.Context, dirPath string) { + // If the migration was cancelled, do not classify this table as failed + if perr.IsContextCancelledError(ctx.Err()) { + return + } migratingRoot := config.GlobalWorkspaceProfile.GetMigratingDir() failedRoot := config.GlobalWorkspaceProfile.GetMigrationFailedDir() rel, err := filepath.Rel(migratingRoot, dirPath) @@ -480,13 +620,15 @@ func insertFromParquetFiles(ctx context.Context, tx *sql.Tx, tableName string, c // onSuccessful handles success outcome: cleans migrating db, prunes empty dirs, prints summary func onSuccessful(status *MigrationStatus) error { // Remove any leftover db in migrating - _ = os.Remove(filepath.Join(config.GlobalWorkspaceProfile.GetMigratingDir(), "tailpipe.db")) + if err := os.Remove(filepath.Join(config.GlobalWorkspaceProfile.GetMigratingDir(), "tailpipe.db")); err != nil && !os.IsNotExist(err) { + return fmt.Errorf("failed to remove leftover migrating db: %w", err) + } + // Prune empty dirs in migrating if err := filepaths.PruneTree(config.GlobalWorkspaceProfile.GetMigratingDir()); err != nil { return fmt.Errorf("failed to prune empty directories in migrating: %w", err) } status.Finish("SUCCESS") - _ = status.WriteStatusToFile() return nil } @@ -495,12 +637,13 @@ func onCancelled(status *MigrationStatus) error { // Do not move db; just prune empties so tree is clean _ = filepaths.PruneTree(config.GlobalWorkspaceProfile.GetMigratingDir()) status.Finish("CANCELLED") - _ = status.WriteStatusToFile() return nil } // onFailed handles failure outcome: move db to failed, prune empties, print summary func onFailed(status *MigrationStatus) error { + status.Finish("INCOMPLETE") + failedDefaultDir := config.GlobalWorkspaceProfile.GetMigrationFailedDir() if err := os.MkdirAll(failedDefaultDir, 0755); err != nil { return err @@ -512,7 +655,5 @@ func onFailed(status *MigrationStatus) error { } } _ = filepaths.PruneTree(config.GlobalWorkspaceProfile.GetMigratingDir()) - status.Finish("INCOMPLETE") - _ = status.WriteStatusToFile() return nil } diff --git a/internal/migration/status.go b/internal/migration/status.go index 6d3c479b..1d18b96a 100644 --- a/internal/migration/status.go +++ b/internal/migration/status.go @@ -12,10 +12,10 @@ import ( type MigrationStatus struct { Status string `json:"status"` - Total int `json:"total"` - Migrated int `json:"migrated"` - Failed int `json:"failed"` - Remaining int `json:"remaining"` + TotalTables int `json:"totaltables"` + MigratedTables int `json:"migratedtables"` + FailedTables int `json:"failedtables"` + RemainingTables int `json:"remainingtables"` ProgressPercent float64 `json:"progress_percent"` TotalFiles int `json:"total_files"` @@ -23,25 +23,35 @@ type MigrationStatus struct { FailedFiles int `json:"failed_files"` RemainingFiles int `json:"remaining_files"` - FailedTables []string `json:"failed_tables,omitempty"` - StartTime time.Time `json:"start_time"` - Duration time.Duration `json:"duration"` + FailedTableNames []string `json:"failed_table_names,omitempty"` + StartTime time.Time `json:"start_time"` + Duration time.Duration `json:"duration"` Errors []string `json:"errors,omitempty"` + + // update func + updateFunc func(st *MigrationStatus) } -func NewMigrationStatus(total int) *MigrationStatus { - return &MigrationStatus{Total: total, Remaining: total, StartTime: time.Now()} +func NewMigrationStatus(totalFiles, totalTables int, updateFunc func(st *MigrationStatus)) *MigrationStatus { + return &MigrationStatus{ + TotalTables: totalTables, + RemainingTables: totalTables, + TotalFiles: totalFiles, + RemainingFiles: totalFiles, + StartTime: time.Now(), + updateFunc: updateFunc, + } } func (s *MigrationStatus) OnTableMigrated() { - s.Migrated++ + s.MigratedTables++ s.update() } func (s *MigrationStatus) OnTableFailed(tableName string) { - s.Failed++ - s.FailedTables = append(s.FailedTables, tableName) + s.FailedTables++ + s.FailedTableNames = append(s.FailedTableNames, tableName) s.update() } @@ -50,7 +60,7 @@ func (s *MigrationStatus) OnFilesMigrated(n int) { return } s.MigratedFiles += n - s.updateFiles() + s.update() } func (s *MigrationStatus) OnFilesFailed(n int) { @@ -58,7 +68,7 @@ func (s *MigrationStatus) OnFilesFailed(n int) { return } s.FailedFiles += n - s.updateFiles() + s.update() } func (s *MigrationStatus) AddError(err error) { @@ -68,17 +78,6 @@ func (s *MigrationStatus) AddError(err error) { s.Errors = append(s.Errors, err.Error()) } -func (s *MigrationStatus) update() { - s.Remaining = s.Total - s.Migrated - s.Failed - if s.Total > 0 { - s.ProgressPercent = float64(s.Migrated) * 100.0 / float64(s.Total) - } -} - -func (s *MigrationStatus) updateFiles() { - s.RemainingFiles = s.TotalFiles - s.MigratedFiles - s.FailedFiles -} - func (s *MigrationStatus) Finish(outcome string) { s.Status = outcome s.Duration = time.Since(s.StartTime) @@ -86,7 +85,6 @@ func (s *MigrationStatus) Finish(outcome string) { // StatusMessage returns a user-facing status message (with stats) based on current migration status func (s *MigrationStatus) StatusMessage() string { - migratedDir := config.GlobalWorkspaceProfile.GetMigratedDir() failedDir := config.GlobalWorkspaceProfile.GetMigrationFailedDir() migratingDir := config.GlobalWorkspaceProfile.GetMigratingDir() @@ -95,11 +93,9 @@ func (s *MigrationStatus) StatusMessage() string { return fmt.Sprintf( "DuckLake migration complete.\n"+ "- Tables: %d/%d migrated (failed: %d, remaining: %d)\n"+ - "- Parquet files: %d/%d migrated (failed: %d, remaining: %d)\n"+ - "- Backup of migrated legacy data: '%s'\n", - s.Migrated, s.Total, s.Failed, s.Remaining, + "- Parquet files: %d/%d migrated (failed: %d, remaining: %d)\n", + s.MigratedTables, s.TotalTables, s.FailedTables, s.RemainingTables, s.MigratedFiles, s.TotalFiles, s.FailedFiles, s.RemainingFiles, - migratedDir, ) case "CANCELLED": return fmt.Sprintf( @@ -108,27 +104,25 @@ func (s *MigrationStatus) StatusMessage() string { "- Parquet files: %d/%d migrated (failed: %d, remaining: %d)\n"+ "- Legacy DB preserved: '%s/tailpipe.db'\n\n"+ "Re-run Tailpipe to resume migrating your data.\n", - s.Migrated, s.Total, s.Failed, s.Remaining, + s.MigratedTables, s.TotalTables, s.FailedTables, s.RemainingTables, s.MigratedFiles, s.TotalFiles, s.FailedFiles, s.RemainingFiles, migratingDir, ) case "INCOMPLETE": failedList := "(none)" - if len(s.FailedTables) > 0 { - failedList = strings.Join(s.FailedTables, ", ") + if len(s.FailedTableNames) > 0 { + failedList = strings.Join(s.FailedTableNames, ", ") } base := fmt.Sprintf( "DuckLake migration completed with issues.\n"+ "- Tables: %d/%d migrated (failed: %d, remaining: %d)\n"+ "- Parquet files: %d/%d migrated (failed: %d, remaining: %d)\n"+ "- Failed tables (%d): %s\n"+ - "- Failed data and legacy DB: '%s'\n"+ - "- Backup of migrated legacy data: '%s'\n", - s.Migrated, s.Total, s.Failed, s.Remaining, + "- Failed data and legacy DB: '%s'\n", + s.MigratedTables, s.TotalTables, s.FailedTables, s.RemainingTables, s.MigratedFiles, s.TotalFiles, s.FailedFiles, s.RemainingFiles, - len(s.FailedTables), failedList, + len(s.FailedTableNames), failedList, failedDir, - migratedDir, ) if len(s.Errors) > 0 { base += fmt.Sprintf("\nErrors: %d error(s) occurred during migration\n", len(s.Errors)) @@ -155,3 +149,16 @@ func (s *MigrationStatus) WriteStatusToFile() error { } return os.WriteFile(statsFile, []byte(msg), 0600) } + +// update recalculates remaining counts and progress percent, and calls the update func if set +func (s *MigrationStatus) update() { + s.RemainingTables = s.TotalTables - s.MigratedTables - s.FailedTables + s.RemainingFiles = s.TotalFiles - s.MigratedFiles - s.FailedFiles + if s.TotalFiles > 0 { + s.ProgressPercent = float64(s.MigratedFiles+s.FailedFiles) * 100.0 / float64(s.TotalFiles) + } + // call our update func + if s.updateFunc != nil { + s.updateFunc(s) + } +} diff --git a/main.go b/main.go index dbd800f9..176917a0 100644 --- a/main.go +++ b/main.go @@ -11,7 +11,7 @@ import ( "github.com/turbot/tailpipe/cmd" "github.com/turbot/tailpipe/internal/cmdconfig" localconstants "github.com/turbot/tailpipe/internal/constants" - error_helpers "github.com/turbot/tailpipe/internal/error_helpers" + "github.com/turbot/tailpipe/internal/error_helpers" ) var exitCode int diff --git a/scripts/smoke_test.sh b/scripts/smoke_test.sh index 5b283a48..424e0622 100755 --- a/scripts/smoke_test.sh +++ b/scripts/smoke_test.sh @@ -16,7 +16,7 @@ jq --version /usr/local/bin/tailpipe query "SELECT 1 as smoke_test" # verify basic query works # Test connect functionality -DB_FILE=$(/usr/local/bin/tailpipe connect --output json | jq -r '.database_filepath') +DB_FILE=$(/usr/local/bin/tailpipe connect --output json | jq -r '.init_script_path') # Verify the database file exists if [ -f "$DB_FILE" ]; then