diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 74eabee8..53788020 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -75,7 +75,8 @@ jobs: id: "goreleaser" with: distribution: "goreleaser-pro" - version: "latest" + # Pinning to version 2.2.0 to get around a regression in 2.3.0. + version: "2.2.0" args: "release -f .goreleaser.docker.yml --clean --split --snapshot" env: GORELEASER_KEY: "${{ secrets.GORELEASER_KEY }}" diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 840e999b..77b4f1a9 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -21,7 +21,8 @@ jobs: - uses: "goreleaser/goreleaser-action@v6" with: distribution: "goreleaser-pro" - version: "latest" + # Pinning to v2.2.0 to work around a regression in 2.3.0 + version: &goreleaser_version "2.2.0" args: "release --clean" env: GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" @@ -46,7 +47,7 @@ jobs: - uses: "goreleaser/goreleaser-action@v6" with: distribution: "goreleaser-pro" - version: "latest" + version: *goreleaser_version args: "release --config=.goreleaser.docker.yml --clean" env: GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.goreleaser.yml b/.goreleaser.yml index fd98d0a6..be308a08 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -158,7 +158,7 @@ checksum: name_template: "checksums.txt" snapshot: - name_template: "{{ incpatch .Version }}-next" + version_template: "{{ incpatch .Version }}-next" changelog: use: "github-native" diff --git a/Dockerfile b/Dockerfile index eb4cbf4f..4e693d95 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.22-alpine3.20 AS zed-builder +FROM golang:1.23-alpine3.20 AS zed-builder WORKDIR /go/src/app RUN apk update && apk add --no-cache git COPY . . diff --git a/go.mod b/go.mod index e5b2d6d7..44d1997b 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,8 @@ module github.com/authzed/zed -go 1.22.4 +go 1.22.7 -toolchain go1.22.5 +toolchain go1.23.1 require ( github.com/99designs/keyring v1.2.2 @@ -11,8 +11,10 @@ require ( github.com/authzed/grpcutil v0.0.0-20240123194739-2ea1e3d2d98b github.com/authzed/spicedb v1.35.3 github.com/brianvoe/gofakeit/v6 v6.28.0 + github.com/ccoveille/go-safecast v1.1.0 github.com/cenkalti/backoff/v4 v4.3.0 github.com/charmbracelet/lipgloss v0.12.1 + github.com/charmbracelet/x/term v0.2.0 github.com/google/uuid v1.6.0 github.com/gookit/color v1.5.4 github.com/hamba/avro/v2 v2.22.1 @@ -230,7 +232,7 @@ require ( golang.org/x/crypto v0.25.0 // indirect golang.org/x/net v0.27.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect - golang.org/x/sys v0.22.0 // indirect + golang.org/x/sys v0.24.0 // indirect golang.org/x/text v0.16.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect diff --git a/go.sum b/go.sum index 4c277764..2caa71e5 100644 --- a/go.sum +++ b/go.sum @@ -720,6 +720,8 @@ github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/ccoveille/go-safecast v1.1.0 h1:iHKNWaZm+OznO7Eh6EljXPjGfGQsSfa6/sxPlIEKO+g= +github.com/ccoveille/go-safecast v1.1.0/go.mod h1:QqwNjxQ7DAqY0C721OIO9InMk9zCwcsO7tnRuHytad8= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -738,6 +740,8 @@ github.com/charmbracelet/lipgloss v0.12.1 h1:/gmzszl+pedQpjCOH+wFkZr/N90Snz40J/N github.com/charmbracelet/lipgloss v0.12.1/go.mod h1:V2CiwIuhx9S1S1ZlADfOj9HmxeMAORuz5izHb0zGbB8= github.com/charmbracelet/x/ansi v0.1.4 h1:IEU3D6+dWwPSgZ6HBH+v6oUuZ/nVawMiWj5831KfiLM= github.com/charmbracelet/x/ansi v0.1.4/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= +github.com/charmbracelet/x/term v0.2.0 h1:cNB9Ot9q8I711MyZ7myUR5HFWL/lc3OpU8jZ4hwm0x0= +github.com/charmbracelet/x/term v0.2.0/go.mod h1:GVxgxAbjUrmpvIINHIQnJJKpMlHiZ4cktEQCN6GWyF0= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -1707,8 +1711,9 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= diff --git a/internal/cmd/backup.go b/internal/cmd/backup.go index 6b2586fb..95bf910e 100644 --- a/internal/cmd/backup.go +++ b/internal/cmd/backup.go @@ -126,7 +126,7 @@ func registerBackupCmd(rootCmd *cobra.Command) { } func registerBackupRestoreFlags(cmd *cobra.Command) { - cmd.Flags().Int("batch-size", 1_000, "restore relationship write batch size") + cmd.Flags().Uint("batch-size", 1_000, "restore relationship write batch size") cmd.Flags().Uint("batches-per-transaction", 10, "number of batches per transaction") cmd.Flags().String("conflict-strategy", "fail", "strategy used when a conflicting relationship is found. Possible values: fail, skip, touch") cmd.Flags().Bool("disable-retries", false, "retries when an errors is determined to be retryable (e.g. serialization errors)") @@ -397,7 +397,7 @@ func backupRestoreCmdFunc(cmd *cobra.Command, args []string) error { return fmt.Errorf("unable to initialize client: %w", err) } - batchSize := cobrautil.MustGetInt(cmd, "batch-size") + batchSize := cobrautil.MustGetUint(cmd, "batch-size") batchesPerTransaction := cobrautil.MustGetUint(cmd, "batches-per-transaction") strategy, err := GetEnum[ConflictStrategy](cmd, "conflict-strategy", conflictStrategyMapping) diff --git a/internal/cmd/backup_test.go b/internal/cmd/backup_test.go index ee4b2ca8..41cc77b0 100644 --- a/internal/cmd/backup_test.go +++ b/internal/cmd/backup_test.go @@ -330,7 +330,7 @@ func TestBackupRestoreCmdFunc(t *testing.T) { zedtesting.BoolFlag{FlagName: "rewrite-legacy"}, zedtesting.StringFlag{FlagName: "conflict-strategy", FlagValue: "fail"}, zedtesting.BoolFlag{FlagName: "disable-retries"}, - zedtesting.IntFlag{FlagName: "batch-size", FlagValue: 100}, + zedtesting.UintFlag{FlagName: "batch-size", FlagValue: 100}, zedtesting.UintFlag{FlagName: "batches-per-transaction", FlagValue: 10}, zedtesting.DurationFlag{FlagName: "request-timeout"}, ) diff --git a/internal/cmd/restorer.go b/internal/cmd/restorer.go index 8b852086..d5ad1a9a 100644 --- a/internal/cmd/restorer.go +++ b/internal/cmd/restorer.go @@ -9,6 +9,8 @@ import ( "time" v1 "github.com/authzed/authzed-go/proto/authzed/api/v1" + "github.com/authzed/spicedb/pkg/spiceerrors" + "github.com/ccoveille/go-safecast" "github.com/cenkalti/backoff/v4" "github.com/mattn/go-isatty" "github.com/rs/zerolog/log" @@ -57,25 +59,25 @@ type restorer struct { decoder *backupformat.Decoder client client.Client prefixFilter string - batchSize int + batchSize uint batchesPerTransaction uint conflictStrategy ConflictStrategy disableRetryErrors bool bar *progressbar.ProgressBar // stats - filteredOutRels int64 - writtenRels int64 - writtenBatches int64 - skippedRels int64 - skippedBatches int64 - duplicateRels int64 - duplicateBatches int64 - totalRetries int64 + filteredOutRels uint + writtenRels uint + writtenBatches uint + skippedRels uint + skippedBatches uint + duplicateRels uint + duplicateBatches uint + totalRetries uint requestTimeout time.Duration } -func newRestorer(schema string, decoder *backupformat.Decoder, client client.Client, prefixFilter string, batchSize int, +func newRestorer(schema string, decoder *backupformat.Decoder, client client.Client, prefixFilter string, batchSize uint, batchesPerTransaction uint, conflictStrategy ConflictStrategy, disableRetryErrors bool, requestTimeout time.Duration, ) *restorer { @@ -129,7 +131,7 @@ func (r *restorer) restoreFromDecoder(ctx context.Context) error { batch = append(batch, rel) - if len(batch)%r.batchSize == 0 { + if uint(len(batch))%r.batchSize == 0 { batchesToBeCommitted = append(batchesToBeCommitted, batch) err := relationshipWriter.Send(&v1.BulkImportRelationshipsRequest{ Relationships: batch, @@ -195,13 +197,13 @@ func (r *restorer) restoreFromDecoder(ctx context.Context) error { totalTime := time.Since(relationshipWriteStart) log.Info(). - Int64("batches", r.writtenBatches). - Int64("relationships_loaded", r.writtenRels). - Int64("relationships_skipped", r.skippedRels). - Int64("duplicate_relationships", r.duplicateRels). - Int64("relationships_filtered_out", r.filteredOutRels). - Int64("retried_errors", r.totalRetries). - Uint64("perSecond", perSec(uint64(r.writtenRels+r.skippedRels), totalTime)). + Uint("batches", r.writtenBatches). + Uint("relationships_loaded", r.writtenRels). + Uint("relationships_skipped", r.skippedRels). + Uint("duplicate_relationships", r.duplicateRels). + Uint("relationships_filtered_out", r.filteredOutRels). + Uint("retried_errors", r.totalRetries). + Uint64("perSecond", perSec(uint64(r.writtenRels), totalTime)). Stringer("duration", totalTime). Msg("finished restore") return nil @@ -210,9 +212,9 @@ func (r *restorer) restoreFromDecoder(ctx context.Context) error { func (r *restorer) commitStream(ctx context.Context, bulkImportClient v1.ExperimentalService_BulkImportRelationshipsClient, batchesToBeCommitted [][]*v1.Relationship, ) error { - var numLoaded, expectedLoaded, retries uint64 + var numLoaded, expectedLoaded, retries uint for _, b := range batchesToBeCommitted { - expectedLoaded += uint64(len(b)) + expectedLoaded += uint(len(b)) } resp, err := bulkImportClient.CloseAndRecv() // transaction commit happens here @@ -225,6 +227,8 @@ func (r *restorer) commitStream(ctx context.Context, bulkImportClient v1.Experim canceled, cancelErr := isCanceledError(ctx.Err(), err) unknown := !retryable && !conflict && !canceled && err != nil + numBatches := uint(len(batchesToBeCommitted)) + switch { case canceled: r.bar.Describe("backup restore aborted") @@ -235,15 +239,15 @@ func (r *restorer) commitStream(ctx context.Context, bulkImportClient v1.Experim case retryable && r.disableRetryErrors: return err case conflict && r.conflictStrategy == Skip: - r.skippedRels += int64(expectedLoaded) - r.skippedBatches += int64(len(batchesToBeCommitted)) - r.duplicateBatches += int64(len(batchesToBeCommitted)) - r.duplicateRels += int64(expectedLoaded) + r.skippedRels += expectedLoaded + r.skippedBatches += numBatches + r.duplicateBatches += numBatches + r.duplicateRels += expectedLoaded r.bar.Describe("skipping conflicting batch") case conflict && r.conflictStrategy == Touch: r.bar.Describe("touching conflicting batch") - r.duplicateRels += int64(expectedLoaded) - r.duplicateBatches += int64(len(batchesToBeCommitted)) + r.duplicateRels += expectedLoaded + r.duplicateBatches += numBatches r.totalRetries++ numLoaded, retries, err = r.writeBatchesWithRetry(ctx, batchesToBeCommitted) if err != nil { @@ -251,8 +255,8 @@ func (r *restorer) commitStream(ctx context.Context, bulkImportClient v1.Experim } retries++ // account for the initial attempt - r.writtenBatches += int64(len(batchesToBeCommitted)) - r.writtenRels += int64(numLoaded) + r.writtenBatches += numBatches + r.writtenRels += numLoaded case conflict && r.conflictStrategy == Fail: r.bar.Describe("conflict detected, aborting restore") return fmt.Errorf("duplicate relationships found") @@ -265,34 +269,43 @@ func (r *restorer) commitStream(ctx context.Context, bulkImportClient v1.Experim } retries++ // account for the initial attempt - r.writtenBatches += int64(len(batchesToBeCommitted)) - r.writtenRels += int64(numLoaded) + r.writtenBatches += numBatches + r.writtenRels += numLoaded default: r.bar.Describe("restoring relationships from backup") - r.writtenBatches += int64(len(batchesToBeCommitted)) + r.writtenBatches += numBatches } // it was a successful transaction commit without duplicates if resp != nil { - r.writtenRels += int64(resp.NumLoaded) - if expectedLoaded != resp.NumLoaded { - log.Warn().Uint64("loaded", resp.NumLoaded).Uint64("expected", expectedLoaded).Msg("unexpected number of relationships loaded") + numLoaded, err := safecast.ToUint(resp.NumLoaded) + if err != nil { + return spiceerrors.MustBugf("could not cast numLoaded to uint") + } + r.writtenRels += numLoaded + if uint64(expectedLoaded) != resp.NumLoaded { + log.Warn().Uint64("loaded", resp.NumLoaded).Uint("expected", expectedLoaded).Msg("unexpected number of relationships loaded") } } - if err := r.bar.Set64(r.writtenRels + r.skippedRels); err != nil { + writtenAndSkipped, err := safecast.ToInt64(r.writtenRels + r.skippedRels) + if err != nil { + return fmt.Errorf("too many written and skipped rels for an int64") + } + + if err := r.bar.Set64(writtenAndSkipped); err != nil { return fmt.Errorf("error incrementing progress bar: %w", err) } if !isatty.IsTerminal(os.Stderr.Fd()) { log.Trace(). - Int64("batches_written", r.writtenBatches). - Int64("relationships_written", r.writtenRels). - Int64("duplicate_batches", r.duplicateBatches). - Int64("duplicate_relationships", r.duplicateRels). - Int64("skipped_batches", r.skippedBatches). - Int64("skipped_relationships", r.skippedRels). - Uint64("retries", retries). + Uint("batches_written", r.writtenBatches). + Uint("relationships_written", r.writtenRels). + Uint("duplicate_batches", r.duplicateBatches). + Uint("duplicate_relationships", r.duplicateRels). + Uint("skipped_batches", r.skippedBatches). + Uint("skipped_relationships", r.skippedRels). + Uint("retries", retries). Msg("restore progress") } @@ -301,14 +314,14 @@ func (r *restorer) commitStream(ctx context.Context, bulkImportClient v1.Experim // writeBatchesWithRetry writes a set of batches using touch semantics and without transactional guarantees - // each batch will be committed independently. If a batch fails, it will be retried up to 10 times with a backoff. -func (r *restorer) writeBatchesWithRetry(ctx context.Context, batches [][]*v1.Relationship) (uint64, uint64, error) { +func (r *restorer) writeBatchesWithRetry(ctx context.Context, batches [][]*v1.Relationship) (uint, uint, error) { backoffInterval := backoff.NewExponentialBackOff() backoffInterval.InitialInterval = defaultBackoff backoffInterval.MaxInterval = 2 * time.Second backoffInterval.MaxElapsedTime = 0 backoffInterval.Reset() - var currentRetries, totalRetries, loadedRels uint64 + var currentRetries, totalRetries, loadedRels uint for _, batch := range batches { updates := lo.Map[*v1.Relationship, *v1.RelationshipUpdate](batch, func(item *v1.Relationship, _ int) *v1.RelationshipUpdate { return &v1.RelationshipUpdate{ @@ -339,7 +352,7 @@ func (r *restorer) writeBatchesWithRetry(ctx context.Context, batches [][]*v1.Re currentRetries = 0 backoffInterval.Reset() - loadedRels += uint64(len(batch)) + loadedRels += uint(len(batch)) break } } diff --git a/internal/cmd/restorer_test.go b/internal/cmd/restorer_test.go index 4445ef4a..8d23be4d 100644 --- a/internal/cmd/restorer_test.go +++ b/internal/cmd/restorer_test.go @@ -8,6 +8,7 @@ import ( v1 "github.com/authzed/authzed-go/proto/authzed/api/v1" "github.com/authzed/spicedb/pkg/tuple" + "github.com/ccoveille/go-safecast" "github.com/stretchr/testify/require" "google.golang.org/grpc" "google.golang.org/grpc/codes" @@ -30,7 +31,7 @@ func TestRestorer(t *testing.T) { for _, tt := range []struct { name string prefixFilter string - batchSize int + batchSize uint batchesPerTransaction uint conflictStrategy ConflictStrategy disableRetryErrors bool @@ -60,12 +61,13 @@ func TestRestorer(t *testing.T) { } { tt := tt t.Run(tt.name, func(t *testing.T) { + require := require.New(t) backupFileName := createTestBackup(t, testSchema, tt.relationships) d, closer, err := decoderFromArgs(backupFileName) - require.NoError(t, err) + require.NoError(err) t.Cleanup(func() { - require.NoError(t, closer.Close()) - require.NoError(t, os.Remove(backupFileName)) + require.NoError(closer.Close()) + require.NoError(os.Remove(backupFileName)) }) expectedFilteredRels := make([]string, 0, len(tt.relationships)) @@ -77,11 +79,11 @@ func TestRestorer(t *testing.T) { expectedFilteredRels = append(expectedFilteredRels, rel) } - expectedBatches := len(expectedFilteredRels) / tt.batchSize + expectedBatches := uint(len(expectedFilteredRels)) / tt.batchSize // there is always one extra commit, regardless there is or not a remainder batch - expectedCommits := expectedBatches/int(tt.batchesPerTransaction) + 1 + expectedCommits := expectedBatches/tt.batchesPerTransaction + 1 remainderBatch := false - if len(expectedFilteredRels)%tt.batchSize != 0 { + if uint(len(expectedFilteredRels))%tt.batchSize != 0 { expectedBatches++ remainderBatch = true } @@ -99,8 +101,8 @@ func TestRestorer(t *testing.T) { sendErrors: tt.sendErrors, } - expectedConflicts := 0 - expectedRetries := 0 + expectedConflicts := uint(0) + expectedRetries := uint(0) var expectsError error for _, err := range tt.commitErrors { if isRetryableError(err) { @@ -126,12 +128,12 @@ func TestRestorer(t *testing.T) { } // if skip is enabled, there will be N less relationships written, where N is the number of conflicts - expectedWrittenRels := len(expectedFilteredRels) + expectedWrittenRels := uint(len(expectedFilteredRels)) if tt.conflictStrategy == Skip { expectedWrittenRels -= expectedConflicts * tt.batchSize } - expectedWrittenBatches := len(expectedFilteredRels) / tt.batchSize + expectedWrittenBatches := uint(len(expectedFilteredRels)) / tt.batchSize if tt.conflictStrategy == Skip { expectedWrittenBatches -= expectedConflicts } @@ -142,12 +144,12 @@ func TestRestorer(t *testing.T) { expectedTouchedBatches := expectedRetries expectedTouchedRels := expectedRetries * tt.batchSize if tt.conflictStrategy == Touch { - expectedTouchedBatches += expectedConflicts * int(tt.batchesPerTransaction) - expectedTouchedRels += expectedConflicts * int(tt.batchesPerTransaction) * tt.batchSize + expectedTouchedBatches += expectedConflicts * tt.batchesPerTransaction + expectedTouchedRels += expectedConflicts * tt.batchesPerTransaction * tt.batchSize } - expectedSkippedBatches := 0 - expectedSkippedRels := 0 + expectedSkippedBatches := uint(0) + expectedSkippedRels := uint(0) if tt.conflictStrategy == Skip { expectedSkippedBatches += expectedConflicts expectedSkippedRels += expectedConflicts * tt.batchSize @@ -156,28 +158,28 @@ func TestRestorer(t *testing.T) { r := newRestorer(testSchema, d, c, tt.prefixFilter, tt.batchSize, tt.batchesPerTransaction, tt.conflictStrategy, tt.disableRetryErrors, 0*time.Second) err = r.restoreFromDecoder(context.Background()) if expectsError != nil || (expectedConflicts > 0 && tt.conflictStrategy == Fail) { - require.ErrorIs(t, err, expectsError) + require.ErrorIs(err, expectsError) return } - require.NoError(t, err) + require.NoError(err) // assert on mock stats - require.Equal(t, expectedBatches, c.receivedBatches, "unexpected number of received batches") - require.Equal(t, expectedCommits, c.receivedCommits, "unexpected number of batch commits") - require.Equal(t, len(expectedFilteredRels), c.receivedRels, "unexpected number of received relationships") - require.Equal(t, expectedTouchedBatches, c.touchedBatches, "unexpected number of touched batches") - require.Equal(t, expectedTouchedRels, c.touchedRels, "unexpected number of touched commits") + require.Equal(expectedBatches, c.receivedBatches, "unexpected number of received batches") + require.Equal(expectedCommits, c.receivedCommits, "unexpected number of batch commits") + require.Equal(uint(len(expectedFilteredRels)), c.receivedRels, "unexpected number of received relationships") + require.Equal(expectedTouchedBatches, c.touchedBatches, "unexpected number of touched batches") + require.Equal(expectedTouchedRels, c.touchedRels, "unexpected number of touched commits") // assert on restorer stats - require.Equal(t, expectedWrittenRels, int(r.writtenRels), "unexpected number of written relationships") - require.Equal(t, expectedWrittenBatches, int(r.writtenBatches), "unexpected number of written relationships") - require.Equal(t, expectedSkippedBatches, int(r.skippedBatches), "unexpected number of conflicting batches skipped") - require.Equal(t, expectedSkippedRels, int(r.skippedRels), "unexpected number of conflicting relationships skipped") - require.Equal(t, uint(expectedConflicts)*tt.batchesPerTransaction, uint(r.duplicateBatches), "unexpected number of duplicate batches detected") - require.Equal(t, expectedConflicts*int(tt.batchesPerTransaction)*tt.batchSize, int(r.duplicateRels), "unexpected number of duplicate relationships detected") - require.Equal(t, int64(expectedRetries+expectedConflicts-expectedSkippedBatches), r.totalRetries, "unexpected number of retries") - require.Equal(t, len(tt.relationships)-len(expectedFilteredRels), int(r.filteredOutRels), "unexpected number of filtered out relationships") + require.Equal(expectedWrittenRels, r.writtenRels, "unexpected number of written relationships") + require.Equal(expectedWrittenBatches, r.writtenBatches, "unexpected number of written relationships") + require.Equal(expectedSkippedBatches, r.skippedBatches, "unexpected number of conflicting batches skipped") + require.Equal(expectedSkippedRels, r.skippedRels, "unexpected number of conflicting relationships skipped") + require.Equal(expectedConflicts*tt.batchesPerTransaction, r.duplicateBatches, "unexpected number of duplicate batches detected") + require.Equal(expectedConflicts*tt.batchesPerTransaction*tt.batchSize, r.duplicateRels, "unexpected number of duplicate relationships detected") + require.Equal(expectedRetries+expectedConflicts-expectedSkippedBatches, r.totalRetries, "unexpected number of retries") + require.Equal(uint(len(tt.relationships))-uint(len(expectedFilteredRels)), r.filteredOutRels, "unexpected number of filtered out relationships") }) } } @@ -189,14 +191,14 @@ type mockClient struct { schema string remainderBatch bool expectedRels []string - expectedBatches int - requestedBatchSize int + expectedBatches uint + requestedBatchSize uint requestedBatchesPerTransaction uint - receivedBatches int - receivedCommits int - receivedRels int - touchedBatches int - touchedRels int + receivedBatches uint + receivedCommits uint + receivedRels uint + touchedBatches uint + touchedRels uint lastReceivedBatch []*v1.Relationship sendErrors []error commitErrors []error @@ -209,20 +211,22 @@ func (m *mockClient) BulkImportRelationships(_ context.Context, _ ...grpc.CallOp func (m *mockClient) Send(req *v1.BulkImportRelationshipsRequest) error { m.receivedBatches++ - m.receivedRels += len(req.Relationships) + m.receivedRels += uint(len(req.Relationships)) m.lastReceivedBatch = req.Relationships - if m.receivedBatches <= len(m.sendErrors) { + if m.receivedBatches <= uint(len(m.sendErrors)) { return m.sendErrors[m.receivedBatches-1] } if m.receivedBatches == m.expectedBatches && m.remainderBatch { - require.Equal(m.t, len(m.expectedRels)%m.requestedBatchSize, len(req.Relationships)) + require.Equal(m.t, uint(len(m.expectedRels))%m.requestedBatchSize, uint(len(req.Relationships))) } else { - require.Equal(m.t, m.requestedBatchSize, len(req.Relationships)) + require.Equal(m.t, m.requestedBatchSize, uint(len(req.Relationships))) } for i, rel := range req.Relationships { - require.True(m.t, proto.Equal(rel, tuple.ParseRel(m.expectedRels[((m.receivedBatches-1)*m.requestedBatchSize)+i]))) + // This is a gosec115 false positive which should be fixed in a future version. + uinti, _ := safecast.ToUint(i) + require.True(m.t, proto.Equal(rel, tuple.ParseRel(m.expectedRels[((m.receivedBatches-1)*m.requestedBatchSize)+uinti]))) } return nil @@ -230,8 +234,8 @@ func (m *mockClient) Send(req *v1.BulkImportRelationshipsRequest) error { func (m *mockClient) WriteRelationships(_ context.Context, in *v1.WriteRelationshipsRequest, _ ...grpc.CallOption) (*v1.WriteRelationshipsResponse, error) { m.touchedBatches++ - m.touchedRels += len(in.Updates) - if m.touchedBatches <= len(m.touchErrors) { + m.touchedRels += uint(len(in.Updates)) + if m.touchedBatches <= uint(len(m.touchErrors)) { return nil, m.touchErrors[m.touchedBatches-1] } @@ -243,7 +247,7 @@ func (m *mockClient) CloseAndRecv() (*v1.BulkImportRelationshipsResponse, error) lastBatch := m.lastReceivedBatch defer func() { m.lastReceivedBatch = nil }() - if m.receivedCommits <= len(m.commitErrors) { + if m.receivedCommits <= uint(len(m.commitErrors)) { return nil, m.commitErrors[m.receivedCommits-1] } diff --git a/internal/cmd/schema.go b/internal/cmd/schema.go index 0529aa67..dc8439b3 100644 --- a/internal/cmd/schema.go +++ b/internal/cmd/schema.go @@ -13,6 +13,7 @@ import ( "github.com/authzed/spicedb/pkg/schemadsl/compiler" "github.com/authzed/spicedb/pkg/schemadsl/generator" "github.com/authzed/spicedb/pkg/schemadsl/input" + "github.com/ccoveille/go-safecast" "github.com/jzelinskie/cobrautil/v2" "github.com/jzelinskie/stringz" "github.com/rs/zerolog/log" @@ -119,7 +120,11 @@ func schemaCopyCmdFunc(cmd *cobra.Command, args []string) error { } func schemaWriteCmdFunc(cmd *cobra.Command, args []string) error { - if len(args) == 0 && term.IsTerminal(int(os.Stdin.Fd())) { + intFd, err := safecast.ToInt(uint(os.Stdout.Fd())) + if err != nil { + return err + } + if len(args) == 0 && term.IsTerminal(intFd) { return fmt.Errorf("must provide file path or contents via stdin") } diff --git a/internal/cmd/validate.go b/internal/cmd/validate.go index 3d3d3ef2..a291d6bc 100644 --- a/internal/cmd/validate.go +++ b/internal/cmd/validate.go @@ -7,6 +7,7 @@ import ( "os" "strings" + "github.com/ccoveille/go-safecast" "github.com/spf13/cobra" v1 "github.com/authzed/authzed-go/proto/authzed/api/v1" @@ -166,10 +167,13 @@ func ouputErrorWithSource(validateContents []byte, errWithSource spiceerrors.Err func outputForLine(validateContents []byte, oneIndexedLineNumber uint64, sourceCodeString string, oneIndexedColumnPosition uint64) { lines := strings.Split(string(validateContents), "\n") - errorLineNumber := int(oneIndexedLineNumber) - 1 + // These should be fine to be zero if the cast fails. + intLineNumber, _ := safecast.ToInt(oneIndexedLineNumber) + intColumnPosition, _ := safecast.ToInt(oneIndexedColumnPosition) + errorLineNumber := intLineNumber - 1 for i := errorLineNumber - 3; i < errorLineNumber+3; i++ { if i == errorLineNumber { - renderLine(lines, i, sourceCodeString, errorLineNumber, int(oneIndexedColumnPosition)-1) + renderLine(lines, i, sourceCodeString, errorLineNumber, intColumnPosition-1) } else { renderLine(lines, i, "", errorLineNumber, -1) } diff --git a/internal/cmd/version.go b/internal/cmd/version.go index 03a1e494..8c67b16d 100644 --- a/internal/cmd/version.go +++ b/internal/cmd/version.go @@ -8,9 +8,9 @@ import ( v1 "github.com/authzed/authzed-go/proto/authzed/api/v1" "github.com/gookit/color" "github.com/jzelinskie/cobrautil/v2" + "github.com/mattn/go-isatty" "github.com/rs/zerolog/log" "github.com/spf13/cobra" - "golang.org/x/term" "google.golang.org/grpc" "google.golang.org/grpc/metadata" @@ -20,7 +20,7 @@ import ( ) func versionCmdFunc(cmd *cobra.Command, _ []string) error { - if !term.IsTerminal(int(os.Stdout.Fd())) { + if !isatty.IsTerminal(os.Stdout.Fd()) { color.Disable() } diff --git a/internal/grpcutil/batch.go b/internal/grpcutil/batch.go index 98929015..640085c6 100644 --- a/internal/grpcutil/batch.go +++ b/internal/grpcutil/batch.go @@ -10,7 +10,7 @@ import ( "golang.org/x/sync/semaphore" ) -func min(a int, b int) int { +func minimum(a int, b int) int { if a <= b { return a } @@ -60,7 +60,7 @@ func ConcurrentBatch(ctx context.Context, n int, batchSize int, maxWorkers int, g.Go(func() error { defer sem.Release(1) start := batchNum * batchSize - end := min(start+batchSize, n) + end := minimum(start+batchSize, n) return each(ctx, batchNum, start, end) }) } diff --git a/internal/storage/secrets.go b/internal/storage/secrets.go index 0a93861f..d29d9a98 100644 --- a/internal/storage/secrets.go +++ b/internal/storage/secrets.go @@ -8,8 +8,8 @@ import ( "strings" "github.com/99designs/keyring" + "github.com/charmbracelet/x/term" "github.com/jzelinskie/stringz" - "golang.org/x/term" "github.com/authzed/zed/internal/console" ) @@ -27,7 +27,7 @@ type Token struct { } func (t Token) Certificate() (cert []byte, ok bool) { - if t.CACert != nil && len(t.CACert) > 0 { + if len(t.CACert) > 0 { return t.CACert, true } return nil, false @@ -146,7 +146,7 @@ func fileExists(path string) (bool, error) { func promptPassword(prompt string) (string, error) { console.Printf(prompt) - b, err := term.ReadPassword(int(os.Stdin.Fd())) + b, err := term.ReadPassword(os.Stdin.Fd()) if err != nil { return "", err }