From 490d1687b7dceb016aa4d6f38bacd89f9f34e82a Mon Sep 17 00:00:00 2001 From: whosonfirst Date: Wed, 12 Oct 2022 10:40:00 -0700 Subject: [PATCH] return errors instead of triggering fatal errors in library code --- main.go | 27 ++++++++++++--- pmtiles/convert.go | 60 ++++++++++++++++++--------------- pmtiles/convert_test.go | 6 ++-- pmtiles/directory_test.go | 68 +++++++++++++++++++------------------- pmtiles/loop.go | 27 ++++++++++----- pmtiles/readerv2_test.go | 16 ++++----- pmtiles/show.go | 10 +++--- pmtiles/subpyramid_test.go | 20 +++++------ pmtiles/upload.go | 22 ++++++------ 9 files changed, 147 insertions(+), 109 deletions(-) diff --git a/main.go b/main.go index 80974de..a09dae4 100644 --- a/main.go +++ b/main.go @@ -25,7 +25,11 @@ func main() { switch os.Args[1] { case "show": - pmtiles.Show(logger, os.Args[2:]) + err := pmtiles.Show(logger, os.Args[2:]) + + if err != nil { + logger.Fatalf("Failed to show database, %v", err) + } case "serve": serveCmd := flag.NewFlagSet("serve", flag.ExitOnError) port := serveCmd.String("p", "8080", "port to serve on") @@ -37,7 +41,12 @@ func main() { logger.Println("USAGE: serve [-p PORT] [-cors VALUE] LOCAL_PATH or https://BUCKET") os.Exit(1) } - loop := pmtiles.NewLoop(path, logger, *cacheSize, *cors) + loop, err := pmtiles.NewLoop(path, logger, *cacheSize, *cors) + + if err != nil { + logger.Fatalf("Failed to create new loop, %v", err) + } + loop.Start() http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { @@ -83,9 +92,19 @@ func main() { convertCmd.Parse(os.Args[2:]) path := convertCmd.Arg(0) output := convertCmd.Arg(1) - pmtiles.Convert(logger, path, output) + err := pmtiles.Convert(logger, path, output) + + if err != nil { + logger.Fatalf("Failed to convert %s, %v", path, err) + } + case "upload": - pmtiles.Upload(logger, os.Args[2:]) + err := pmtiles.Upload(logger, os.Args[2:]) + + if err != nil { + logger.Fatalf("Failed to upload file, %v", err) + } + case "validate": // pmtiles.Validate() default: diff --git a/pmtiles/convert.go b/pmtiles/convert.go index 9e2cf80..505d658 100644 --- a/pmtiles/convert.go +++ b/pmtiles/convert.go @@ -5,6 +5,7 @@ import ( "compress/gzip" "encoding/json" "errors" + "fmt" "github.com/RoaringBitmap/roaring/roaring64" "github.com/schollz/progressbar/v3" "hash" @@ -85,11 +86,11 @@ func NewResolver() *Resolver { return &r } -func Convert(logger *log.Logger, input string, output string) { +func Convert(logger *log.Logger, input string, output string) error { if strings.HasSuffix(input, ".pmtiles") { - ConvertPmtilesV2(logger, input, output) + return ConvertPmtilesV2(logger, input, output) } else { - ConvertMbtiles(logger, input, output) + return ConvertMbtiles(logger, input, output) } } @@ -108,17 +109,17 @@ func add_directoryv2_entries(dir DirectoryV2, entries *[]EntryV3, f *os.File) { } } -func ConvertPmtilesV2(logger *log.Logger, input string, output string) { +func ConvertPmtilesV2(logger *log.Logger, input string, output string) error { start := time.Now() f, err := os.Open(input) if err != nil { - log.Fatalf("Failed to open file: %s", err) + return fmt.Errorf("Failed to open file: %w", err) } defer f.Close() buffer := make([]byte, 512000) io.ReadFull(f, buffer) if string(buffer[0:7]) == "PMTiles" && buffer[7] == 3 { - logger.Fatal("Archive is already the latest PMTiles version (3).") + return fmt.Errorf("Archive is already the latest PMTiles version (3).") } v2_json_bytes, dir := ParseHeaderV2(bytes.NewReader(buffer)) @@ -132,13 +133,13 @@ func ConvertPmtilesV2(logger *log.Logger, input string, output string) { f.Seek(512000, 0) n, err := f.Read(first4) if n != 4 || err != nil { - panic(err) + return fmt.Errorf("Failed to read first 4, %w", err) } header, json_metadata, err := v2_to_header_json(v2_metadata, first4) if err != nil { - logger.Fatal(err) + return fmt.Errorf("Failed to convert v2 to header JSON, %w", err) } entries := make([]EntryV3, 0) @@ -151,7 +152,7 @@ func ConvertPmtilesV2(logger *log.Logger, input string, output string) { tmpfile, err := ioutil.TempFile("", "") if err != nil { - logger.Fatal(err) + return fmt.Errorf("Failed to create temp file, %w", err) } defer os.Remove(tmpfile.Name()) @@ -164,13 +165,13 @@ func ConvertPmtilesV2(logger *log.Logger, input string, output string) { } _, err := f.Seek(int64(entry.Offset), 0) if err != nil { - panic(err) + return fmt.Errorf("Failed to seek at offset %d, %w", entry.Offset, err) } buf := make([]byte, entry.Length) _, err = f.Read(buf) if err != nil { if err != io.EOF { - panic(err) + return fmt.Errorf("Failed to read buffer, %w", err) } } if is_new, new_data := resolver.AddTileIsNew(entry.TileId, buf); is_new { @@ -180,13 +181,15 @@ func ConvertPmtilesV2(logger *log.Logger, input string, output string) { finalize(logger, resolver, header, tmpfile, output, json_metadata) logger.Println("Finished in ", time.Since(start)) + + return nil } -func ConvertMbtiles(logger *log.Logger, input string, output string) { +func ConvertMbtiles(logger *log.Logger, input string, output string) error { start := time.Now() conn, err := sqlite.OpenConn(input, sqlite.OpenReadOnly) if err != nil { - logger.Fatal(err) + return fmt.Errorf("Failed to create database connection, %w", err) } defer conn.Close() @@ -194,14 +197,14 @@ func ConvertMbtiles(logger *log.Logger, input string, output string) { { stmt, _, err := conn.PrepareTransient("SELECT name, value FROM metadata") if err != nil { - logger.Fatal(err) + return fmt.Errorf("Failed to create SQL statement, %w", err) } defer stmt.Finalize() for { row, err := stmt.Step() if err != nil { - logger.Fatal(err) + return fmt.Errorf("Failed to step statement, %w", err) } if !row { break @@ -213,7 +216,7 @@ func ConvertMbtiles(logger *log.Logger, input string, output string) { header, json_metadata, err := mbtiles_to_header_json(mbtiles_metadata) if err != nil { - logger.Fatal(err) + return fmt.Errorf("Failed to convert MBTiles to header JSON, %w", err) } logger.Println("Querying total tile count...") @@ -222,12 +225,12 @@ func ConvertMbtiles(logger *log.Logger, input string, output string) { { stmt, _, err := conn.PrepareTransient("SELECT count(*) FROM tiles") if err != nil { - logger.Fatal(err) + return fmt.Errorf("Failed to create statement, %w", err) } defer stmt.Finalize() row, err := stmt.Step() if err != nil || !row { - logger.Fatal(err) + return fmt.Errorf("Failed to step row, %w", err) } total_tiles = stmt.ColumnInt64(0) } @@ -238,7 +241,7 @@ func ConvertMbtiles(logger *log.Logger, input string, output string) { { stmt, _, err := conn.PrepareTransient("SELECT zoom_level, tile_column, tile_row FROM tiles") if err != nil { - logger.Fatal(err) + return fmt.Errorf("Failed to create statement, %w", err) } defer stmt.Finalize() @@ -247,7 +250,7 @@ func ConvertMbtiles(logger *log.Logger, input string, output string) { for { row, err := stmt.Step() if err != nil { - logger.Fatal(err) + return fmt.Errorf("Failed to step statement, %w", err) } if !row { break @@ -264,7 +267,7 @@ func ConvertMbtiles(logger *log.Logger, input string, output string) { tmpfile, err := ioutil.TempFile("", "") if err != nil { - logger.Fatal(err) + return fmt.Errorf("Failed to create temporary file, %w", err) } defer os.Remove(tmpfile.Name()) @@ -289,10 +292,10 @@ func ConvertMbtiles(logger *log.Logger, input string, output string) { has_row, err := stmt.Step() if err != nil { - logger.Fatal(err) + return fmt.Errorf("Failed to step statement, %w", err) } if !has_row { - logger.Fatal("Missing row") + return fmt.Errorf("Missing row") } reader := stmt.ColumnReader(0) @@ -313,9 +316,10 @@ func ConvertMbtiles(logger *log.Logger, input string, output string) { } finalize(logger, resolver, header, tmpfile, output, json_metadata) logger.Println("Finished in ", time.Since(start)) + return nil } -func finalize(logger *log.Logger, resolver *Resolver, header HeaderV3, tmpfile *os.File, output string, json_metadata map[string]interface{}) { +func finalize(logger *log.Logger, resolver *Resolver, header HeaderV3, tmpfile *os.File, output string, json_metadata map[string]interface{}) error { logger.Println("# of addressed tiles: ", resolver.AddressedTiles) logger.Println("# of tile entries (after RLE): ", len(resolver.Entries)) logger.Println("# of tile contents: ", len(resolver.OffsetMap)) @@ -327,7 +331,7 @@ func finalize(logger *log.Logger, resolver *Resolver, header HeaderV3, tmpfile * // assemble the final file outfile, err := os.Create(output) if err != nil { - logger.Fatal(err) + return fmt.Errorf("Failed to create %s, %w", output, err) } root_bytes, leaves_bytes, num_leaves := optimize_directories(resolver.Entries, 16384-HEADERV3_LEN_BYTES) @@ -348,7 +352,7 @@ func finalize(logger *log.Logger, resolver *Resolver, header HeaderV3, tmpfile * { metadata_bytes_uncompressed, err := json.Marshal(json_metadata) if err != nil { - logger.Fatal(err) + return fmt.Errorf("Failed to marshal metadata, %w", err) } var b bytes.Buffer w, _ := gzip.NewWriterLevel(&b, gzip.BestCompression) @@ -379,8 +383,10 @@ func finalize(logger *log.Logger, resolver *Resolver, header HeaderV3, tmpfile * tmpfile.Seek(0, 0) _, err = io.Copy(outfile, tmpfile) if err != nil { - logger.Fatal(err) + return fmt.Errorf("Failed to copy data to outfile, %w", err) } + + return nil } func v2_to_header_json(v2_json_metadata map[string]interface{}, first4 []byte) (HeaderV3, map[string]interface{}, error) { diff --git a/pmtiles/convert_test.go b/pmtiles/convert_test.go index 636e998..ea3c13f 100644 --- a/pmtiles/convert_test.go +++ b/pmtiles/convert_test.go @@ -90,9 +90,9 @@ func TestV2UpgradeBarebones(t *testing.T) { func TestV2UpgradeStrings(t *testing.T) { header, _, err := v2_to_header_json(map[string]interface{}{ - "minzoom": "0", - "maxzoom": "14", - "bounds": "-180.0,-85,178,83", + "minzoom": "0", + "maxzoom": "14", + "bounds": "-180.0,-85,178,83", }, []byte{0x1f, 0x8b, 0x0, 0x0}) if err != nil { t.Fatalf("parsing error %s", err) diff --git a/pmtiles/directory_test.go b/pmtiles/directory_test.go index 74f0710..1a927c3 100644 --- a/pmtiles/directory_test.go +++ b/pmtiles/directory_test.go @@ -2,9 +2,9 @@ package pmtiles import ( "bytes" + "github.com/stretchr/testify/assert" "math/rand" "testing" - "github.com/stretchr/testify/assert" ) func TestDirectoryRoundtrip(t *testing.T) { @@ -188,58 +188,58 @@ func TestOptimizeDirectories(t *testing.T) { } func TestFindTileMissing(t *testing.T) { - entries := make([]EntryV3,0) - _, ok := find_tile(entries,0) + entries := make([]EntryV3, 0) + _, ok := find_tile(entries, 0) if ok { t.Fatalf("Expected not ok") } } func TestFindTileFirstEntry(t *testing.T) { - entries := []EntryV3{{TileId:100, Offset: 1, Length: 1, RunLength:1}} - entry, ok := find_tile(entries,100) - assert.Equal(t,true,ok) - assert.Equal(t,uint64(1),entry.Offset) - assert.Equal(t,uint32(1),entry.Length) - _, ok = find_tile(entries,101) - assert.Equal(t,false,ok) + entries := []EntryV3{{TileId: 100, Offset: 1, Length: 1, RunLength: 1}} + entry, ok := find_tile(entries, 100) + assert.Equal(t, true, ok) + assert.Equal(t, uint64(1), entry.Offset) + assert.Equal(t, uint32(1), entry.Length) + _, ok = find_tile(entries, 101) + assert.Equal(t, false, ok) } func TestFindTileMultipleEntries(t *testing.T) { entries := []EntryV3{ - {TileId:100, Offset: 1, Length: 1, RunLength:2}, + {TileId: 100, Offset: 1, Length: 1, RunLength: 2}, } - entry, ok := find_tile(entries,101) - assert.Equal(t,true,ok) - assert.Equal(t,uint64(1),entry.Offset) - assert.Equal(t,uint32(1),entry.Length) + entry, ok := find_tile(entries, 101) + assert.Equal(t, true, ok) + assert.Equal(t, uint64(1), entry.Offset) + assert.Equal(t, uint32(1), entry.Length) entries = []EntryV3{ - {TileId:100, Offset: 1, Length: 1, RunLength:1}, - {TileId:150, Offset: 2, Length: 2, RunLength:2}, + {TileId: 100, Offset: 1, Length: 1, RunLength: 1}, + {TileId: 150, Offset: 2, Length: 2, RunLength: 2}, } - entry, ok = find_tile(entries,151) - assert.Equal(t,true,ok) - assert.Equal(t,uint64(2),entry.Offset) - assert.Equal(t,uint32(2),entry.Length) + entry, ok = find_tile(entries, 151) + assert.Equal(t, true, ok) + assert.Equal(t, uint64(2), entry.Offset) + assert.Equal(t, uint32(2), entry.Length) entries = []EntryV3{ - {TileId:50, Offset: 1, Length: 1, RunLength:2}, - {TileId:100, Offset: 2, Length: 2, RunLength:1}, - {TileId:150, Offset: 3, Length: 3, RunLength:1}, - } - entry, ok = find_tile(entries,51) - assert.Equal(t,true,ok) - assert.Equal(t,uint64(1),entry.Offset) - assert.Equal(t,uint32(1),entry.Length) + {TileId: 50, Offset: 1, Length: 1, RunLength: 2}, + {TileId: 100, Offset: 2, Length: 2, RunLength: 1}, + {TileId: 150, Offset: 3, Length: 3, RunLength: 1}, + } + entry, ok = find_tile(entries, 51) + assert.Equal(t, true, ok) + assert.Equal(t, uint64(1), entry.Offset) + assert.Equal(t, uint32(1), entry.Length) } func TestFindTileLeafSearch(t *testing.T) { entries := []EntryV3{ - {TileId:100, Offset: 1, Length: 1, RunLength:0}, + {TileId: 100, Offset: 1, Length: 1, RunLength: 0}, } - entry, ok := find_tile(entries,150) - assert.Equal(t,true,ok) - assert.Equal(t,uint64(1),entry.Offset) - assert.Equal(t,uint32(1),entry.Length) + entry, ok := find_tile(entries, 150) + assert.Equal(t, true, ok) + assert.Equal(t, uint64(1), entry.Offset) + assert.Equal(t, uint32(1), entry.Length) } diff --git a/pmtiles/loop.go b/pmtiles/loop.go index 0abc40b..9afec03 100644 --- a/pmtiles/loop.go +++ b/pmtiles/loop.go @@ -4,6 +4,7 @@ import ( "bytes" "container/list" "context" + "fmt" "gocloud.dev/blob" "io" "log" @@ -64,20 +65,28 @@ type Loop struct { cors string } -func NewLoop(path string, logger *log.Logger, cacheSize int, cors string) Loop { +func NewLoop(path string, logger *log.Logger, cacheSize int, cors string) (*Loop, error) { reqs := make(chan Request, 8) ctx := context.Background() bucket, err := blob.OpenBucket(ctx, path) if err != nil { - logger.Fatal(err) + return nil, fmt.Errorf("Failed to open bucket for %s, %w", path, err) } - return Loop{reqs: reqs, bucket: bucket, logger: logger, cacheSize: cacheSize, cors: cors} + l := &Loop{ + reqs: reqs, + bucket: bucket, + logger: logger, + cacheSize: cacheSize, + cors: cors, + } + + return l, nil } -func (loop Loop) Start() { +func (loop *Loop) Start() { go func() { cache := make(map[CacheKey]*list.Element) inflight := make(map[CacheKey][]Request) @@ -116,7 +125,7 @@ func (loop Loop) Start() { if err != nil { ok = false resps <- Response{key: key, value: result} - loop.logger.Printf("failed to fetch %s %d-%d", key.name, key.offset, key.length) + loop.logger.Printf("failed to fetch %s %d-%d, %v", key.name, key.offset, key.length, err) return } defer r.Close() @@ -124,7 +133,7 @@ func (loop Loop) Start() { if err != nil { ok = false resps <- Response{key: key, value: result} - loop.logger.Printf("failed to fetch %s %d-%d", key.name, key.offset, key.length) + loop.logger.Printf("failed to fetch %s %d-%d, %v", key.name, key.offset, key.length, err) return } @@ -181,7 +190,7 @@ func (loop Loop) Start() { }() } -func (loop Loop) get_metadata(ctx context.Context, http_headers map[string]string, name string) (int, map[string]string, []byte) { +func (loop *Loop) get_metadata(ctx context.Context, http_headers map[string]string, name string) (int, map[string]string, []byte) { root_req := Request{key: CacheKey{name: name, offset: 0, length: 0}, value: make(chan CachedValue, 1)} loop.reqs <- root_req root_value := <-root_req.value @@ -209,7 +218,7 @@ func (loop Loop) get_metadata(ctx context.Context, http_headers map[string]strin return 200, http_headers, b } -func (loop Loop) get_tile(ctx context.Context, http_headers map[string]string, name string, z uint8, x uint32, y uint32, ext string) (int, map[string]string, []byte) { +func (loop *Loop) get_tile(ctx context.Context, http_headers map[string]string, name string, z uint8, x uint32, y uint32, ext string) (int, map[string]string, []byte) { root_req := Request{key: CacheKey{name: name, offset: 0, length: 0}, value: make(chan CachedValue, 1)} loop.reqs <- root_req @@ -286,7 +295,7 @@ func (loop Loop) get_tile(ctx context.Context, http_headers map[string]string, n var tilePattern = regexp.MustCompile(`^\/([-A-Za-z0-9_]+)\/(\d+)\/(\d+)\/(\d+)\.([a-z]+)$`) var metadataPattern = regexp.MustCompile(`^\/([-A-Za-z0-9_]+)\/metadata$`) -func (loop Loop) Get(ctx context.Context, path string) (int, map[string]string, []byte) { +func (loop *Loop) Get(ctx context.Context, path string) (int, map[string]string, []byte) { http_headers := make(map[string]string) if len(loop.cors) > 0 { http_headers["Access-Control-Allow-Origin"] = loop.cors diff --git a/pmtiles/readerv2_test.go b/pmtiles/readerv2_test.go index 0074f99..1bb6ad9 100644 --- a/pmtiles/readerv2_test.go +++ b/pmtiles/readerv2_test.go @@ -5,12 +5,12 @@ import ( ) func TestUint24(t *testing.T) { - b := []byte{255,255,255} + b := []byte{255, 255, 255} result := readUint24(b) if result != 16777215 { t.Errorf("result did not match, was %d", result) } - b = []byte{255,0,0} + b = []byte{255, 0, 0} result = readUint24(b) if result != 255 { t.Errorf("result did not match, was %d", result) @@ -18,12 +18,12 @@ func TestUint24(t *testing.T) { } func TestUint48(t *testing.T) { - b := []byte{255,255,255,255,255,255} + b := []byte{255, 255, 255, 255, 255, 255} result := readUint48(b) if result != 281474976710655 { t.Errorf("result did not match, was %d", result) } - b = []byte{255,0,0,0,0,0} + b = []byte{255, 0, 0, 0, 0, 0} result = readUint48(b) if result != 255 { t.Errorf("result did not match, was %d", result) @@ -31,9 +31,9 @@ func TestUint48(t *testing.T) { } func TestGetParentTile(t *testing.T) { - a := Zxy{Z:8,X:125,Y:69} - result := GetParentTile(a,7) - if (result != Zxy{Z:7,X:62,Y:34}) { + a := Zxy{Z: 8, X: 125, Y: 69} + result := GetParentTile(a, 7) + if (result != Zxy{Z: 7, X: 62, Y: 34}) { t.Errorf("result did not match, was %d", result) } -} \ No newline at end of file +} diff --git a/pmtiles/show.go b/pmtiles/show.go index 9abf777..00aff33 100644 --- a/pmtiles/show.go +++ b/pmtiles/show.go @@ -10,7 +10,7 @@ import ( "log" ) -func Show(logger *log.Logger, args []string) { +func Show(logger *log.Logger, args []string) error { cmd := flag.NewFlagSet("upload", flag.ExitOnError) cmd.Parse(args) bucketURL := cmd.Arg(0) @@ -19,18 +19,18 @@ func Show(logger *log.Logger, args []string) { ctx := context.Background() bucket, err := blob.OpenBucket(ctx, bucketURL) if err != nil { - logger.Fatal(err) + return fmt.Errorf("Failed to open bucket for %s, %w", bucketURL, err) } defer bucket.Close() r, err := bucket.NewRangeReader(ctx, file, 0, 16384, nil) if err != nil { - logger.Fatal(err) + return fmt.Errorf("Failed to create range reader for %s, %w", file, err) } b, err := io.ReadAll(r) if err != nil { - logger.Fatal(err) + return fmt.Errorf("Failed to read %s, %w", file, err) } r.Close() @@ -64,4 +64,6 @@ func Show(logger *log.Logger, args []string) { // for _, entry := range entries { // fmt.Println(entry) // } + + return nil } diff --git a/pmtiles/subpyramid_test.go b/pmtiles/subpyramid_test.go index 594354b..ccfd645 100644 --- a/pmtiles/subpyramid_test.go +++ b/pmtiles/subpyramid_test.go @@ -5,24 +5,24 @@ import ( ) func TestPointToTile(t *testing.T) { - result := PointToTile(4,0,0) - if (result != Zxy{4,8,8}) { + result := PointToTile(4, 0, 0) + if (result != Zxy{4, 8, 8}) { t.Errorf("result did not match, was %d", result) } - result = PointToTile(4,-180,-85) - if (result != Zxy{4,0,15}) { + result = PointToTile(4, -180, -85) + if (result != Zxy{4, 0, 15}) { t.Errorf("result did not match, was %d", result) } - result = PointToTile(4,-180,85) - if (result != Zxy{4,0,0}) { + result = PointToTile(4, -180, 85) + if (result != Zxy{4, 0, 0}) { t.Errorf("result did not match, was %d", result) } - result = PointToTile(4,179.999,-85) - if (result != Zxy{4,15,15}) { + result = PointToTile(4, 179.999, -85) + if (result != Zxy{4, 15, 15}) { t.Errorf("result did not match, was %d", result) } - result = PointToTile(4,179.999,85) - if (result != Zxy{4,15,0}) { + result = PointToTile(4, 179.999, 85) + if (result != Zxy{4, 15, 0}) { t.Errorf("result did not match, was %d", result) } } diff --git a/pmtiles/upload.go b/pmtiles/upload.go index 8c943d3..51f44fc 100644 --- a/pmtiles/upload.go +++ b/pmtiles/upload.go @@ -3,6 +3,7 @@ package pmtiles import ( "context" "flag" + "fmt" "github.com/schollz/progressbar/v3" "gocloud.dev/blob" "io" @@ -10,7 +11,7 @@ import ( "os" ) -func Upload(logger *log.Logger, args []string) { +func Upload(logger *log.Logger, args []string) error { cmd := flag.NewFlagSet("upload", flag.ExitOnError) buffer_size := cmd.Int("buffer-size", 8, "Upload chunk size in megabytes") max_concurrency := cmd.Int("max-concurrency", 5, "Number of upload threads") @@ -20,26 +21,25 @@ func Upload(logger *log.Logger, args []string) { bucketURL := cmd.Arg(1) if file == "" || bucketURL == "" { - logger.Println("USAGE: upload [-buffer-size B] [-max-concurrency M] INPUT s3://BUCKET?region=region") - os.Exit(1) + return fmt.Errorf("USAGE: upload [-buffer-size B] [-max-concurrency M] INPUT s3://BUCKET?region=region") } logger.Println(file, bucketURL) ctx := context.Background() b, err := blob.OpenBucket(ctx, bucketURL) if err != nil { - log.Fatalf("Failed to setup bucket: %s", err) + return fmt.Errorf("Failed to setup bucket: %w", err) } defer b.Close() f, err := os.Open(file) if err != nil { - log.Fatalf("Failed to open file: %s", err) + return fmt.Errorf("Failed to open file: %w", err) } defer f.Close() filestat, err := f.Stat() if err != nil { - log.Fatalf("Failed to open file: %s", err) + return fmt.Errorf("Failed to open file: %w", err) } bar := progressbar.Default(filestat.Size()) @@ -53,7 +53,7 @@ func Upload(logger *log.Logger, args []string) { w, err := b.NewWriter(ctx, file, opts) if err != nil { - log.Fatalf("Failed to obtain writer: %s", err) + return fmt.Errorf("Failed to obtain writer: %w", err) } for { @@ -73,16 +73,18 @@ func Upload(logger *log.Logger, args []string) { _, err = w.Write(buffer[:n]) if err != nil { - log.Fatalf("Failed to write to bucket: %s", err) + return fmt.Errorf("Failed to write to bucket: %w", err) } bar.Add(n) if err != nil && err != io.EOF { - logger.Fatal(err) + return fmt.Errorf("Failed to write data, %w", err) } } if err := w.Close(); err != nil { - log.Fatalf("Failed to close: %s", err) + return fmt.Errorf("Failed to close: %w", err) } + + return nil }