Go (AWS SDK v2) i HostArena S3: primeri koda

Go (AWS SDK v2) i HostArena S3: primeri koda

AWS SDK za Go (verzija 2) je zvanična biblioteka za rad sa S3 skladištem iz Go programa. Sa HostArena S3 storage je koristite isto kao sa Amazon S3, uz podešavanje adrese servera (BaseEndpoint), regiona garage i path-style adresa (UsePathStyle).

Šta vam je potrebno

PodatakVrednostGde ga nalazite
Endpointhttps://s3.hostarena.rsIsti za sve korisnike
RegiongarageObavezno tačno ovako
Bucketvas-bucketHostArena korisnički panel, kod S3 usluge
Access KeyVAS_ACCESS_KEYHostArena korisnički panel, kod S3 usluge
Secret KeyVAS_SECRET_KEYHostArena korisnički panel, kod S3 usluge

Potreban vam je Go, neka od aktuelnih verzija.

Koraci

  1. Napravite projekat:

    mkdir s3-primer
    cd s3-primer
    go mod init primer
  2. Ključeve ne upisujte direktno u kod. Postavite ih kao promenljive okruženja. Na Linux-u i macOS-u:

    export AWS_ACCESS_KEY_ID="VAS_ACCESS_KEY"
    export AWS_SECRET_ACCESS_KEY="VAS_SECRET_KEY"

    U Windows PowerShell-u:

    $env:AWS_ACCESS_KEY_ID="VAS_ACCESS_KEY"
    $env:AWS_SECRET_ACCESS_KEY="VAS_SECRET_KEY"

    Ove komande važe za otvoreni terminal. Na serveru ih obično upisujete u podešavanja servisa, u .env fajl koji se ne objavljuje ili u alat za tajne podatke.

    SDK ove promenljive čita sam, preko config.LoadDefaultConfig.

  3. Napravite fajl main.go. Primer otprema fajl, ispisuje spisak fajlova (i kada ih ima više od 1000) i preuzima fajl tako da ga stvarno snimi na disk. Svaka greška se proverava, a S3 kod greške se ispisuje čitljivo:

    package main
    
    import (
    	"context"
    	"errors"
    	"fmt"
    	"io"
    	"log"
    	"os"
    	"strings"
    
    	"github.com/aws/aws-sdk-go-v2/aws"
    	"github.com/aws/aws-sdk-go-v2/config"
    	"github.com/aws/aws-sdk-go-v2/service/s3"
    	"github.com/aws/smithy-go"
    )
    
    const bucket = "vas-bucket"
    
    func main() {
    	ctx := context.Background()
    
    	// Ključevi se čitaju iz promenljivih okruženja
    	// AWS_ACCESS_KEY_ID i AWS_SECRET_ACCESS_KEY.
    	cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion("garage"))
    	if err != nil {
    		log.Fatalf("greška u podešavanju: %v", err)
    	}
    
    	client := s3.NewFromConfig(cfg, func(o *s3.Options) {
    		o.BaseEndpoint = aws.String("https://s3.hostarena.rs")
    		o.UsePathStyle = true
    	})
    
    	if err := uploadFile(ctx, client, "bekap.zip", "bekapi/bekap.zip"); err != nil {
    		log.Fatal(err)
    	}
    	if err := listFiles(ctx, client); err != nil {
    		log.Fatal(err)
    	}
    	if err := downloadFile(ctx, client, "bekapi/bekap.zip", "vraceno.zip"); err != nil {
    		log.Fatal(err)
    	}
    }
    
    // Otpremanje lokalnog fajla u bucket.
    func uploadFile(ctx context.Context, client *s3.Client, localPath, key string) error {
    	file, err := os.Open(localPath)
    	if err != nil {
    		return fmt.Errorf("ne mogu da otvorim %s: %w", localPath, err)
    	}
    	defer file.Close()
    
    	_, err = client.PutObject(ctx, &s3.PutObjectInput{
    		Bucket: aws.String(bucket),
    		Key:    aws.String(key),
    		Body:   file,
    	})
    	if err != nil {
    		return fmt.Errorf("otpremanje nije uspelo: %w", describe(err))
    	}
    	fmt.Println("Otpremljeno:", key)
    	return nil
    }
    
    // Spisak fajlova u bucketu (radi i za više od 1000 fajlova).
    func listFiles(ctx context.Context, client *s3.Client) error {
    	p := s3.NewListObjectsV2Paginator(client, &s3.ListObjectsV2Input{
    		Bucket: aws.String(bucket),
    	})
    	for p.HasMorePages() {
    		page, err := p.NextPage(ctx)
    		if err != nil {
    			return fmt.Errorf("listanje nije uspelo: %w", describe(err))
    		}
    		for _, obj := range page.Contents {
    			fmt.Printf("%s  %d bajtovan", aws.ToString(obj.Key), aws.ToInt64(obj.Size))
    		}
    	}
    	return nil
    }
    
    // Preuzimanje fajla iz bucketa i snimanje na disk.
    func downloadFile(ctx context.Context, client *s3.Client, key, localPath string) error {
    	out, err := client.GetObject(ctx, &s3.GetObjectInput{
    		Bucket: aws.String(bucket),
    		Key:    aws.String(key),
    	})
    	if err != nil {
    		return fmt.Errorf("preuzimanje nije uspelo: %w", describe(err))
    	}
    	defer out.Body.Close()
    
    	f, err := os.Create(localPath)
    	if err != nil {
    		return fmt.Errorf("ne mogu da napravim %s: %w", localPath, err)
    	}
    	defer f.Close()
    
    	n, err := io.Copy(f, out.Body)
    	if err != nil {
    		return fmt.Errorf("snimanje nije uspelo: %w", err)
    	}
    	fmt.Printf("Snimljeno: %s (%d bajtova)n", localPath, n)
    	return nil
    }
    
    // Brisanje fajla iz bucketa.
    func deleteFile(ctx context.Context, client *s3.Client, key string) error {
    	_, err := client.DeleteObject(ctx, &s3.DeleteObjectInput{
    		Bucket: aws.String(bucket),
    		Key:    aws.String(key),
    	})
    	if err != nil {
    		return fmt.Errorf("brisanje nije uspelo: %w", describe(err))
    	}
    	return nil
    }
    
    // Izvlači S3 kod greške (npr. NoSuchKey, AccessDenied) radi lakšeg čitanja.
    func describe(err error) error {
    	var apiErr smithy.APIError
    	if errors.As(err, &apiErr) {
    		return fmt.Errorf("%s: %s", apiErr.ErrorCode(), strings.TrimSpace(apiErr.ErrorMessage()))
    	}
    	return err
    }
  4. Preuzmite potrebne biblioteke i pokrenite program (u istom terminalu u kom ste postavili ključeve):

    go mod tidy
    go run .

    go mod tidy sam preuzima sve pakete navedene u import delu.

Korisno je znati

  • Kod preuzimanja, out.Body je tok podataka. io.Copy ga upisuje u fajl, a defer out.Body.Close() zatvara vezu. Bez toga fajl ne bi bio snimljen.
  • Funkcija deleteFile je u primeru definisana, ali se ne poziva iz main. Pozovite je samo kada zaista želite da obrišete fajl.
  • Za velike fajlove postoji paket github.com/aws/aws-sdk-go-v2/feature/s3/manager, koji fajl deli na delove.

Pažnja: DeleteObject trajno briše fajl i ne pita za potvrdu. Obrisani fajl se ne može vratiti.

Provera da radi

Stavite mali fajl bekap.zip u folder projekta i pokrenite go run .. Ispravan ispis je „Otpremljeno”, spisak fajlova i „Snimljeno: vraceno.zip”. Ako program stane sa porukom o grešci, na početku poruke je S3 kod greške.

Najčešći problemi

AuthorizationHeaderMalformed

Region nije dobar. Mora biti tačno garage. Sa us-east-1 ili bilo kojim drugim AWS regionom server odbija zahtev.

InvalidAccessKeyId, AccessDenied ili SignatureDoesNotMatch

Proverite da li su promenljive okruženja postavljene u istom terminalu ili servisu u kom pokrećete program i da ključevi nemaju razmak na početku ili kraju. Ključeve proverite u HostArena korisničkom panelu, kod S3 usluge.

NoSuchBucket

Naziv bucketa nije tačan. Prepišite ga iz HostArena korisničkog panela. Endpoint ostaje https://s3.hostarena.rs, bez naziva bucketa u adresi.

Greška vezana za checksum pri otpremanju

Novije verzije AWS SDK-a same dodaju kontrolni zbir (checksum) svakom otpremanju. Ako dobijete grešku koja pominje checksum, dodajte u podešavanja klijenta sledeće dve opcije:

client := s3.NewFromConfig(cfg, func(o *s3.Options) {
	o.BaseEndpoint = aws.String("https://s3.hostarena.rs")
	o.UsePathStyle = true
	o.RequestChecksumCalculation = aws.RequestChecksumCalculationWhenRequired
	o.ResponseChecksumValidation = aws.ResponseChecksumValidationWhenRequired
})

Zaključak

Isti princip važi i za druge jezike: PHP, Python (boto3) i Node.js. Tehnički detalji o adresama i potpisivanju zahteva nalaze se u uputstvu HostArena S3 API: adrese, potpisivanje i kompatibilnost. Nemate S3 uslugu? Pogledajte pakete na stranici HostArena S3 storage. Ako zapne, otvorite tiket u korisničkom panelu.

Tags: , , ,