Obsah
1. Projekt MinIO – jedna z nejužitečnějších aplikací naprogramovaných v Go
3. Instalace služby MinIO i ovládací konzole mc
4. Spuštění služby MinIO na lokálním počítači
5. Ovládání Minia z webového prohlížeče
6. Ovládání Minia z příkazového řádku přes konzoli
7. Instalace knihovny s rozhraním pro Minio (MinIO Client SDK)
8. Kostra aplikace, která provede inicializaci klienta služby Minio
9. Úprava příkladu – zadání parametrů připojení z příkazového řádku, výpis bucketů
10. Typické chyby, které mohou nastat
11. Výpis všech objektů ve zvoleném bucketu
12. Podrobnější informace o objektech ve zvoleném bucketu
13. Uložení objektu z bucketu do lokálního souboru
14. Přečtení obsahu objektu s textem
15. Alternativní způsob přístupu k obsahu objektu
16. Poslání dat do Minia s jejich uložením do objektu
17. Kopie objektu v rámci Minia bez jejich přesunu na lokální počítač
18. Obsah následující části seriálu
19. Repositář s demonstračními příklady
1. Projekt MinIO – jedna z nejužitečnějších aplikací naprogramovaných v Go
V dnešním článku se ve stručnosti seznámíme s projektem nazvaným MinIO. Jedná se o sadu několika služeb a nástrojů, které uživatelům poskytují distribuované datové úložiště určené pro ukládání obecných (nestrukturovaných) dat. Typicky se jedná o soubory používané v oblasti AI (Artifical Intelligence) a ML (Machine Learning), ovšem kromě těchto populárních (a vlastně do značné míry i módních) oblastí IT je pochopitelně možné službu MinIO použít i pro ukládání logů, souborů, k nimž je zapotřebí rychle přistupovat z mnoha různých, mnohdy vzájemně vzdálených oblastí (zde využijeme možnost distribuovaného systému), jako centrální úložiště dokumentů, obrázků, videí, pochopitelně i obrazů souborových systémů pro Docker apod. MinIO dosahuje velmi slušné rychlosti přístupu k datům (při vhodně nadimenzované síti, která je většinou limitujícím faktorem) a mj. i díky velmi dobré stabilitě ukazuje přednosti programovacího jazyka Go, v němž je celý systém naprogramován.
Obrázek 1: Logo používané projektem MinIO.
Dnes se s projektem MinIO seznámíme především z pohledu vývojáře. Nebudeme se tedy do všech podrobností zabývat tím, jak MinIO nasadit do rozsáhlého systému s geograficky vzdálenými serverovnami, jak nastavit distribuci dat atd. Zajímat nás budou především způsoby ukládání a načítání dat, a to jak s využitím konzole ovládané z příkazového řádku, tak i přes webové rozhraní. Ovšem prakticky vždy je nutné k datům přistupovat i programově, což je obsahem druhé části dnešního článku, kde si ukážeme několik příkladů používajících poměrně snadno ovladatelnou knihovnu nazvanou MinIO Client SDK.
Obrázek 2: Porovnání používání projektu MinIO a dalších podobných technologií. Tato statistika je ovšem založena na počtu stažení, nikoli na celkovém počtu instalací.
2. MinIO a AWS S3
Jedním z nejdůležitějších a v důsledku i nejpraktičtějších vlastností projektu MinIO je fakt, že se pro přístup k datům používá stejná technologie, jaká je implementována i v populární službě Amazon S3 či možná přesněji AWS S3. To mj. znamená, že dodávaný MinIO Client SDK popsaný v navazujících kapitolách může sloužit jak pro přístup k datům uloženým v Miniu, tak i k datům uloženým ve cloudu na S3. Díky tomu lze například snadněji nastavit konfiguraci pro vývoj, konfiguraci CI, zajistit si možnost využití veřejného cloudu (S3) nebo naopak privátního cloudu (založeného na Miniu) atd. Navíc je MinIO Client SDK určen jen pro přístup k datům a nikoli pro ovládání dalších služeb, takže je jeho zahrnutí do vyvíjené aplikace méně náročné na systémové prostředky. Musíme si totiž uvědomit, že přístup k datům je mnohdy zapotřebí i z relativně málo výkonných zařízení IoT atd. (mj. i z tohoto důvodu se MinIO co do snadnosti integrace porovnává s Redisem, i když oblasti nasazení těchto dvou technologií jsou mnohdy značně odlišné).
3. Instalace služby MinIO i ovládací konzole mc
Instalace služby (přesněji řečeno serverové části) projektu MinIO je snadná a přímočará. Jelikož se jedná o aplikaci naprogramovanou v jazyce Go, je služba dodávána ve formě jediného (i když relativně objemného) spustitelného souboru. K dispozici je ovšem i obraz pro Docker, překlad lze provést ze zdrojových souborů atd. Dnes se zaměříme na první způsob, tedy na stažení již připravených souborů projektu MinIO. Musíme si pouze vybrat soubor pro právě používaný operační systém a procesorovou architekturu. Pro testování budu používat Linux a architekturu x86–64. Službu MinIO, přesněji řečeno spustitelný binární soubor, který po svém spuštění službu nabídne, získáme jednoduše jediným příkazem:
$ wget https://dl.min.io/server/minio/release/linux-amd64/minio
Následně je nutné nastavit příznak „x“ pro stažený soubor, aby bylo možné službu spustit přímo z příkazového řádku:
$ chmod +x minio
Dále pro jistotu otestujeme, zda je stažený soubor skutečně spustitelný:
$ ./minio version Version: 2019-10-12T01:39:57Z Release-Tag: RELEASE.2019-10-12T01-39-57Z Commit-ID: bd106408462ecef70debf51f1e6179de950c5812
Podobným způsobem lokálně nainstalujeme i konzoli projektu MinIO. Ta se jmenuje mc. Nejdříve stáhneme příslušný spustitelný soubor pro zvolený operační systém a architekturu mikroprocesoru:
$ wget https://dl.min.io/client/mc/release/linux-amd64/mc
Následně, podobně jako v předchozích krocích, nastavíme příznak „x“, aby byla konzole spustitelná:
$ chmod +x mc
A ověříme si, že tomu tak skutečně je:
$ ./mc version Version: 2019-10-09T22:54:57Z Release-tag: RELEASE.2019-10-09T22-54-57Z Commit-id: f93fe1330a3647b1afaff0ed8c188d2897bf391e
$ mc $ ./mc
4. Spuštění služby MinIO na lokálním počítači
Pokud již máme připravený spustitelný soubor nazvaný minio, je inicializace a následné spuštění služby MinIO na lokálním počítači otázkou jediného příkazu. Musíme pouze specifikovat, že se má spustit server a na jakém disku a adresáři budou umístěny soubory spravované službou MinIO:
$ ./minio server /tmp/minio Endpoint: http://10.0.0.29:9000 http://127.0.0.1:9000 AccessKey: WDGGENVCJDQVFM3TBM88 SecretKey: 8YxAW5qxYKBzo7qLGuqxuVDwK5NekY2k7v9ZIZ9C Browser Access: http://10.0.0.29:9000 http://127.0.0.1:9000 Command-line Access: https://docs.min.io/docs/minio-client-quickstart-guide $ mc config host add myminio http://10.0.0.29:9000 WDGGENVCJDQVFM3TBM88 8YxAW5qxYKBzo7qLGuqxuVDwK5NekY2k7v9ZIZ9C Object API (Amazon S3 compatible): Go: https://docs.min.io/docs/golang-client-quickstart-guide Java: https://docs.min.io/docs/java-client-quickstart-guide Python: https://docs.min.io/docs/python-client-quickstart-guide JavaScript: https://docs.min.io/docs/javascript-client-quickstart-guide .NET: https://docs.min.io/docs/dotnet-client-quickstart-guide
Povšimněte si, že se po spuštění zobrazily všechny informace nutné pro spuštění webového rozhraní, pro použití konzole ovládané z příkazového řádku i pro instalaci SDK pro podporované programovací jazyky. Dále jsme získali i dvojici klíčů, které použijeme v následujících kapitolách, a to jak při přístupu přes webovou konzoli, tak i v demonstračních příkladech založených na SDK Minia.
Jestliže se spuštění služby Minio nepovedlo, je vhodné zkontrolovat, zda adresář předaný při spuštění neobsahuje podadresáře mapované (přesněji řečeno připojené) z jiného disku či souborového systému. Celý adresář by měl (z pohledu Minia) ležet v jediném diskoFGetObject, popř. ve svazku spravovaném vlastním operačním systémem.
5. Ovládání Minia z webového prohlížeče
Minio je možné částečně ovládat z webového prohlížeče, konkrétně ze stránky, která byla zobrazena při spouštění služby:
$ ./minio server /tmp/minio ... ... ... Browser Access: http://10.0.0.29:9000 http://127.0.0.1:9000 ... ... ...
Po otevření adresy (v našem případě lokální 127.0.0.1:9000) je nutné zadat AccessKey (20 znaků) a SecretKey (40 znaků), které taktéž známe, protože se opět zobrazily při spuštění služby:
$ ./minio server /tmp/minio ... AccessKey: WDGGENVCJDQVFM3TBM88 SecretKey: 8YxAW5qxYKBzo7qLGuqxuVDwK5NekY2k7v9ZIZ9C ... ... ...
Obrázek 3: Přihlašovací obrazovka, do které je nutné přenést AccessKey a SecretKey.
Obrázek 4: Běžná copy&paste procedura :-)
Obrázek 5: Data jsou v Miniu představována objekty, které jsou ukládány do bucketů. Zpočátku je úložiště zcela prázdné.
Obrázek 6: Přes ikonu + lze vytvořit nový bucket. V příkladech budeme používat buckety „foo“ a „bar“.
Obrázek 7: V pravém dolním rohu je patrné kontextové menu získané po rozkliku ikony +.
Obrázek 8: Uložení objektu do úložiště. Jedná se o obrázek dostupný na adrese https://blog.golang.org/go-brand/logos.jpg.
6. Ovládání Minia z příkazového řádku přes konzoli
Pro ovládání Minia, tj. především pro práci s buckety a objekty, lze použít i nástroj pojmenovaný mc, který jsme nainstalovali v rámci třetí kapitoly. Tento nástroj po svém spuštění bez parametrů vypíše všechny podporované příkazy:
ls list buckets and objects tree list buckets and objects in a tree format mb make a bucket rb remove a bucket cat display object contents head display first 'n' lines of an object pipe stream STDIN to an object share generate URL for temporary access to an object cp copy objects mirror synchronize objects to a remote site find search for objects sql run sql queries on objects stat stat contents of objects diff list differences in object name, size, and date between buckets rm remove objects event manage object notifications watch watch for object events policy manage anonymous access to objects admin manage MinIO servers session manage saved sessions for cp command config manage mc configuration file update check for a new software update version print version info
Před použitím většiny operací, které ovládají službu Minio je nutné se přihlásit, a to následujícím způsobem:
$ ./mc config host add myminio http://10.0.0.29:9000 WDGGENVCJDQVFM3TBM88 8YxAW5qxYKBzo7qLGuqxuVDwK5NekY2k7v9ZIZ9C Added `myminio` successfully.
Další příkazy budou používat jméno „myminio“. Díky pojmenování „sezení“ je možné z jediného počítače v danou chvíli ovládat i několik instancí Minia.
7. Instalace knihovny s rozhraním pro Minio (Minio Client SDK)
V této kapitole si ve stručnosti ukážeme, jakým způsobem lze nainstalovat Minio Client SDK, což je poněkud dlouhý název pro knihovnu, jenž programátorům zajišťuje rozhraní mezi programovacím jazykem a aplikací v něm vyvíjené na jedné straně a službou Minio na straně druhé. Zaměříme se na použití klienta v programovacím jazyku Go, i když je pochopitelně možné Minio ovládat i z aplikací naprogramovaných v jiných programovacích jazycích. Touto problematikou se však budeme zabývat až příště.
SDK pro Go je postaveno na použití modulů, takže instalaci budeme muset provést z projektu, který má systém modulů povolen. Vytvoření takového projektu je ve skutečnosti velmi snadné, protože nám bude postačovat vytvoření adresáře s prázdným projektem a inicializace systému modulů v tomto adresáři:
$ mkdir minio1 $ cd minio1 $ go mod init minio1 go: creating new go.mod: module minio1
Po zadání předchozích tří příkazů by měl v novém adresáři „minio1“ vzniknout soubor s názvem „go.mod“. V následujícím kroku již nainstalujeme celé SDK:
$ go get github.com/minio/minio-go/v6 go: finding github.com/minio/minio-go/v6 v6.0.44 go: finding github.com/minio/minio-go v6.0.14+incompatible go: downloading github.com/minio/minio-go/v6 v6.0.44 go: downloading github.com/minio/minio-go v6.0.14+incompatible go: extracting github.com/minio/minio-go v6.0.14+incompatible go: extracting github.com/minio/minio-go/v6 v6.0.44 go: downloading golang.org/x/net v0.0.0-20190522155817-f3200d17e092 go: downloading golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f go: downloading github.com/minio/sha256-simd v0.1.1 go: downloading gopkg.in/ini.v1 v1.42.0 go: extracting github.com/minio/sha256-simd v0.1.1 go: extracting gopkg.in/ini.v1 v1.42.0 go: extracting golang.org/x/net v0.0.0-20190522155817-f3200d17e092 go: extracting golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f go: downloading golang.org/x/sys v0.0.0-20190422165155-953cdadca894 go: extracting golang.org/x/sys v0.0.0-20190422165155-953cdadca894 go: finding github.com/minio/sha256-simd v0.1.1 go: finding golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f go: finding gopkg.in/ini.v1 v1.42.0 go: finding golang.org/x/sys v0.0.0-20190422165155-953cdadca894
8. Kostra aplikace, která provede inicializaci klienta služby Minio
Nyní, když již máme SDK pro službu Minio nainstalovaný, si můžeme vytvořit kostru aplikace, po jejímž spuštění se provede inicializace klienta. Pro úspěšné připojení je nutné znát minimálně tři údaje: URL, na němž je služba Minio spuštěna, dále Access Key ID, což je řetězec o délce dvaceti znaků a nakonec i Secret Access Key představovaný řetězcem o délce čtyřiceti znaků. Všechny tři údaje již známe, protože je služba Minio vypsala při svém spuštění (viz též třetí kapitolu):
Endpoint: http://10.0.0.29:9000 http://127.0.0.1:9000 AccessKey: WDGGENVCJDQVFM3TBM88 SecretKey: 8YxAW5qxYKBzo7qLGuqxuVDwK5NekY2k7v9ZIZ9C
V prvním demonstračním příkladu tyto údaje pro jednoduchost přímo zadáme do zdrojového kódu (což pochopitelně neznamená, že se jedná o doporučení, kam podobné údaje ukládat :-):
endpoint := "127.0.0.1:9000" accessKeyID := "WDGGENVCJDQVFM3TBM88" secretAccessKey := "8YxAW5qxYKBzo7qLGuqxuVDwK5NekY2k7v9ZIZ9C"
Pro inicializaci klienta je nutné zavolat funkci New z balíčku minio-go. Této funkci se předají všechny tři výše zmíněné údaje a navíc i příznak oznamující, zda se má pro komunikaci použít SSL/TLS či nikoli:
minioClient, err := minio.New(endpoint, accessKeyID, secretAccessKey, useSSL)
Funkce New vrací referenci na vytvořeného klienta, popř. strukturu s informací o chybě, která při inicializaci klienta nastala. Prozatím si tuto strukturu, popř. chybu pouze vypíšeme, což je ostatně patrné i při pohledu na úplný zdrojový kód příkladu:
package main import ( "github.com/minio/minio-go/v6" "log" ) func main() { endpoint := "127.0.0.1:9000" // it is needed to change the following two keys accessKeyID := "WDGGENVCJDQVFM3TBM88" secretAccessKey := "8YxAW5qxYKBzo7qLGuqxuVDwK5NekY2k7v9ZIZ9C" useSSL := true // initialize minio client object minioClient, err := minio.New(endpoint, accessKeyID, secretAccessKey, useSSL) if err != nil { log.Fatalln(err) } // everything seems to be ok log.Printf("%#v\n", minioClient) }
Po překladu (go build) a spuštění příkladu by se měly vypsat informace o inicializovaném klientovi služby Minio:
2019/12/14 11:05:58 &minio.Client{endpointURL:(*url.URL)(0xc000174000), credsProvider:(*credentials.Credentials)(0xc0000ae780), overrideSignerType:0, appInfo:struct { appName string; appVersion string }{appName:"", appVersion:""}, secure:true, httpClient:(*http.Client)(0xc000192c90), bucketLocCache:(*minio.bucketLocationCache)(0xc000284100), isTraceEnabled:false, traceErrorsOnly:false, traceOutput:io.Writer(nil), s3AccelerateEndpoint:"", region:"", random:(*rand.Rand)(0xc000192cf0), lookup:0}
9. Úprava příkladu – zadání parametrů připojení z příkazového řádku, výpis bucketů
Předchozí demonstrační příklad byl sice jednoduchý, ovšem používali jsme v něm údaje o připojení zapsané přímo do zdrojového kódu, což není praktické. Proto příklad upravíme, a to takovým způsobem, aby se údaje nutné pro připojení do služby Minio získávaly z příkazového řádku. Pro tento účel použijeme standardní balíček flag:
var endpoint = flag.String("endpoint", "127.0.0.1:9000", "MinIO service endpoint") var accessKeyID = flag.String("accessKeyID", "", "Access key ID for MinIO") var secretAccessKey = flag.String("secretAccessKey", "", "Secret access key for MinIO") var useSSL = flag.Bool("useSSL", false, "Use SSL for communication with MinIO") flag.Parse()
Inicializace klienta je snadná, jen si musíme uvědomit, že příznaky získané z příkazové řádky jsou ukazateli na řetězce a nikoli přímo řetězce:
minioClient, err := minio.New(*endpoint, *accessKeyID, *secretAccessKey, *useSSL) if err != nil { log.Fatalln(err) }
Dále demonstrační příklad rozšíříme takovým způsobem, aby se vypsaly všechny buckety, které jsou v Miniu uloženy a jsou dostupné pro zvoleného uživatele (specifikovaného klíčem). Použijeme funkci ListBuckets, která vrátí buď chybu nebo seznam bucketů:
func listBuckets(minioClient *minio.Client) { fmt.Println("List of buckets:") buckets, err := minioClient.ListBuckets() if err != nil { log.Fatalln(err) return } for i, bucket := range buckets { fmt.Printf("%d\t%+v\n", i, bucket) } }
Úplný zdrojový kód dnešního druhého demonstračního příkladu vypadá následovně:
package main import ( "flag" "fmt" "github.com/minio/minio-go/v6" "log" ) func listBuckets(minioClient *minio.Client) { fmt.Println("List of buckets:") buckets, err := minioClient.ListBuckets() if err != nil { log.Fatalln(err) return } for i, bucket := range buckets { fmt.Printf("%d\t%+v\n", i, bucket) } } func main() { var endpoint = flag.String("endpoint", "127.0.0.1:9000", "MinIO service endpoint") var accessKeyID = flag.String("accessKeyID", "", "Access key ID for MinIO") var secretAccessKey = flag.String("secretAccessKey", "", "Secret access key for MinIO") var useSSL = flag.Bool("useSSL", false, "Use SSL for communication with MinIO") flag.Parse() // initialize minio client object minioClient, err := minio.New(*endpoint, *accessKeyID, *secretAccessKey, *useSSL) if err != nil { log.Fatalln(err) } // everything seems to be ok log.Printf("%#v\n", minioClient) listBuckets(minioClient) }
10. Typické chyby, které mohou nastat
Nyní, když můžeme parametry připojení do Minia specifikovat na příkazovém řádku, je možné se seznámit s typickými chybami, které mohou nastat.
Pokus o připojení s nekorektním AccessKey:
$ ./minio3 2019/12/14 11:52:53 &minio.Client{endpointURL:(*url.URL)(0xc000158000), credsProvider:(*credentials.Credentials)(0xc00006c840), overrideSignerType:0, appInfo:struct { appName string; appVersion string }{appName:"", appVersion:""}, secure:false, httpClient:(*http.Client)(0xc00008f860), bucketLocCache:(*minio.bucketLocationCache)(0xc00000e7c0), isTraceEnabled:false, traceErrorsOnly:false, traceOutput:io.Writer(nil), s3AccelerateEndpoint:"", region:"", random:(*rand.Rand)(0xc00008f8c0), lookup:0} List of buckets: 2019/12/14 11:52:53 Access Denied.
Chybný SecretKey:
$ ./minio3 -accessKeyID=3V8WMANF061SGOIVR7AA -secretAccessKey=AHTM6+74n1Z8DZRZ4V7o83QcnYRnTEVblVb8sIl 2019/12/14 13:02:11 &minio.Client{endpointURL:(*url.URL)(0xc000178000), credsProvider:(*credentials.Credentials)(0xc0000ae7e0), overrideSignerType:0, appInfo:struct { appName string; appVersion string }{appName:"", appVersion:""}, secure:false, httpClient:(*http.Client)(0xc0000938c0), bucketLocCache:(*minio.bucketLocationCache)(0xc0000a0780), isTraceEnabled:false, traceErrorsOnly:false, traceOutput:io.Writer(nil), s3AccelerateEndpoint:"", region:"", random:(*rand.Rand)(0xc000093920), lookup:0} List of buckets: 2019/12/14 13:02:11 The request signature we calculated does not match the signature you provided. Check your key and signing method.
Pokus o použití SSL/TLS u služby běžící přes HTTP:
$ ./minio3 -accessKeyID=3V8WMANF061SGOIVR7AA -secretAccessKey=AHTM6+74n1Z8DZRZ4V7o83QcnYRnTEVblVb8sIlE -useSSL=true 2019/12/14 13:02:27 &minio.Client{endpointURL:(*url.URL)(0xc000158000), credsProvider:(*credentials.Credentials)(0xc00006c840), overrideSignerType:0, appInfo:struct { appName string; appVersion string }{appName:"", appVersion:""}, secure:true, httpClient:(*http.Client)(0xc00012db60), bucketLocCache:(*minio.bucketLocationCache)(0xc000239ae0), isTraceEnabled:false, traceErrorsOnly:false, traceOutput:io.Writer(nil), s3AccelerateEndpoint:"", region:"", random:(*rand.Rand)(0xc00012dbc0), lookup:0} List of buckets: 2019/12/14 13:02:27 Get https://127.0.0.1:9000/: http: server gave HTTP response to HTTPS client
Úspěšné připojení s výpisem bucketů (existují dva):
$ ./minio3 -accessKeyID=3V8WMANF061SGOIVR7AA -secretAccessKey=AHTM6+74n1Z8DZRZ4V7o83QcnYRnTEVblVb8sIlE 2019/12/14 13:01:54 &minio.Client{endpointURL:(*url.URL)(0xc000172000), credsProvider:(*credentials.Credentials)(0xc0000ac7e0), overrideSignerType:0, appInfo:struct { appName string; appVersion string }{appName:"", appVersion:""}, secure:false, httpClient:(*http.Client)(0xc0000938c0), bucketLocCache:(*minio.bucketLocationCache)(0xc00009e780), isTraceEnabled:false, traceErrorsOnly:false, traceOutput:io.Writer(nil), s3AccelerateEndpoint:"", region:"", random:(*rand.Rand)(0xc000093920), lookup:0} List of buckets: 0 {Name:bar CreationDate:2019-12-14 10:30:57.918 +0000 UTC} 1 {Name:foo CreationDate:2019-12-14 12:01:10.533 +0000 UTC}
11. Výpis všech objektů ve zvoleném bucketu
Ve třetím demonstračním příkladu si ukážeme, jakým způsobem je možné získat seznam všech objektů, které jsou uloženy ve zvoleném bucketu. Tuto funkcionalitu nabízí metoda listObjects, které je nutné předat jméno bucketu, prefix (lze ho použít pro rychlou filtraci na straně služby), příznak, zda se mají procházet i objekty v podadresářích a posledním parametrem je kanál, který lze použít pro synchronizaci gorutiny, která by objekty načítala. V našem jednoduchém příkladu však kanál na konci funkce listObjects pouze uzavřeme a nebudeme se starat o to, zda do něj byla zapsána hodnota či nikoli:
func listObjects(minioClient *minio.Client, bucket string) { fmt.Println("List of objects for bucket:", bucket) done := make(chan struct{}) defer close(done) objects := minioClient.ListObjects(bucket, "", false, done) for object := range objects { if object.Err != nil { log.Println(object.Err) return } fmt.Println(object) } }
Výpis bude proveden v tomto (prozatím nijak neupraveném) formátu:
{"f2042bf5780d07253480fb8c64c60850-1" t.go 2019-12-14 16:10:38.411 +0000 UTC 56 0001-01-01 00:00:00 +0000 UTC map[] map[] { 02d6176db174dc93cb1b899f7c6078f08654445fe8cf1b6ce98d8855f66bdbf4} [] STANDARD <nil<}
Úplný zdrojový kód tohoto demonstračního příkladu vypadá následovně:
package main import ( "flag" "fmt" "github.com/minio/minio-go/v6" "log" ) func listBuckets(minioClient *minio.Client) { fmt.Println("List of buckets:") buckets, err := minioClient.ListBuckets() if err != nil { log.Fatalln(err) return } for i, bucket := range buckets { fmt.Printf("%d\t%+v\n", i, bucket) } } func listObjects(minioClient *minio.Client, bucket string) { fmt.Println("List of objects for bucket:", bucket) done := make(chan struct{}) defer close(done) objects := minioClient.ListObjects(bucket, "", false, done) for object := range objects { if object.Err != nil { log.Println(object.Err) return } fmt.Println(object) } } func main() { var endpoint = flag.String("endpoint", "127.0.0.1:9000", "MinIO service endpoint") var accessKeyID = flag.String("accessKeyID", "", "Access key ID for MinIO") var secretAccessKey = flag.String("secretAccessKey", "", "Secret access key for MinIO") var useSSL = flag.Bool("useSSL", false, "Use SSL for communication with MinIO") flag.Parse() // initialize minio client object minioClient, err := minio.New(*endpoint, *accessKeyID, *secretAccessKey, *useSSL) if err != nil { log.Fatalln(err) } // everything seems to be ok log.Printf("%#v\n", minioClient) listBuckets(minioClient) listObjects(minioClient, "foo") }
Po spuštění tohoto příkladu by se nejdříve měly vypsat všechny buckety a následně i objekty z bucketu „foo“ (ty jsme přidali přes webové rozhraní):
09:17 $ ./minio4 -accessKeyID=3V8WMANF061SGOIVR7AA -secretAccessKey=AHTM6+74n1Z8DZRZ4V7o83QcnYRnTEVblVb8sIlE 2019/12/16 09:17:45 &minio.Client{endpointURL:(*url.URL)(0xc00015a000), credsProvider:(*credentials.Credentials)(0xc00006e840), overrideSignerType:0, appInfo:struct { appName string; appVersion string }{appName:"", appVersion:""}, secure:false, httpClient:(*http.Client)(0xc0000138f0), bucketLocCache:(*minio.bucketLocationCache)(0xc00000e7a0), isTraceEnabled:false, traceErrorsOnly:false, traceOutput:io.Writer(nil), s3AccelerateEndpoint:"", region:"", random:(*rand.Rand)(0xc000013950), lookup:0} List of buckets: 0 {Name:bar CreationDate:2019-12-14 10:30:57.918 +0000 UTC} 1 {Name:foo CreationDate:2019-12-14 17:13:38.282 +0000 UTC} List of objects for bucket: foo {"f2042bf5780d07253480fb8c64c60850-1" t.go 2019-12-14 16:10:38.411 +0000 UTC 56 0001-01-01 00:00:00 +0000 UTC map[] map[] { 02d6176db174dc93cb1b899f7c6078f08654445fe8cf1b6ce98d8855f66bdbf4} [] STANDARD <nil<}
12. Podrobnější informace o objektech ve zvoleném bucketu
O objektech, které jsou uloženy ve vybraném bucketu, je možné získat i podrobnější informace. Ve struktuře popisující každý objekt, nalezneme mj. i tyto atributy:
# | Atribut | Význam |
---|---|---|
1 | Key | klíč (jednoznačný v rámci bucketu) |
2 | Size | velikost v bajtech (nikoli ve znacích, i když jde o JSON, XML atd.) |
3 | LastModified | čas poslední modifikace |
4 | ETag | MD5 heš (128 bitů reprezentovaných třiceti dvěma hexadecimálními číslicemi) |
Z tohoto důvodu je možné upravit funkci pro výpis informací o objektech tak, aby se vypsaly potřebné atributy (viz zvýrazněný řádek):
func listObjects(minioClient *minio.Client, bucket string, prefix string) { fmt.Println("List of objects for bucket:", bucket) done := make(chan struct{}) defer close(done) objects := minioClient.ListObjects(bucket, prefix, false, done) for object := range objects { if object.Err != nil { log.Println(object.Err) return } fmt.Printf("Key: %s, Size: %d, Tag: %s\n", object.Key, object.Size, object.ETag) } }
Takto upravený příklad zobrazí podrobnější informace o objektu s klíčem „logos.jpg“, délkou 48913 bajtů a tagem nastaveným na „f95e4a85dafc56313883f8571cfc8143“:
$ ./minio5 -accessKeyID=3V8WMANF061SGOIVR7AA -secretAccessKey=AHTM6+74n1Z8DZRZ4V7o83QcnYRnTEVblVb8sIlE 2019/12/14 16:43:51 &minio.Client{endpointURL:(*url.URL)(0xc00015a000), credsProvider:(*credentials.Credentials)(0xc00006e840), overrideSignerType:0, appInfo:struct { appName string; appVersion string }{appName:"", appVersion:""}, secure:false, httpClient:(*http.Client)(0xc0000138f0), bucketLocCache:(*minio.bucketLocationCache)(0xc00000e7c0), isTraceEnabled:false, traceErrorsOnly:false, traceOutput:io.Writer(nil), s3AccelerateEndpoint:"", region:"", random:(*rand.Rand)(0xc000013950), lookup:0} List of buckets: 0 {Name:bar CreationDate:2019-12-14 10:30:57.918 +0000 UTC} 1 {Name:foo CreationDate:2019-12-14 12:01:10.533 +0000 UTC} List of objects for bucket: foo Key: logos.jpg, Size: 48913, Tag: "f95e4a85dafc56313883f8571cfc8143-1"
Úplný zdrojový kód takto upraveného demonstračního příkladu vypadá následovně:
package main import ( "flag" "fmt" "github.com/minio/minio-go/v6" "log" ) func listBuckets(minioClient *minio.Client) { fmt.Println("List of buckets:") buckets, err := minioClient.ListBuckets() if err != nil { log.Fatalln(err) return } for i, bucket := range buckets { fmt.Printf("%d\t%+v\n", i, bucket) } } func listObjects(minioClient *minio.Client, bucket string, prefix string) { fmt.Println("List of objects for bucket:", bucket) done := make(chan struct{}) defer close(done) objects := minioClient.ListObjects(bucket, prefix, false, done) for object := range objects { if object.Err != nil { log.Println(object.Err) return } fmt.Printf("Key: %s, Size: %d, Tag: %s\n", object.Key, object.Size, object.ETag) } } func main() { var endpoint = flag.String("endpoint", "127.0.0.1:9000", "MinIO service endpoint") var accessKeyID = flag.String("accessKeyID", "", "Access key ID for MinIO") var secretAccessKey = flag.String("secretAccessKey", "", "Secret access key for MinIO") var useSSL = flag.Bool("useSSL", false, "Use SSL for communication with MinIO") var objectPrefix = flag.String("prefix", "", "Prefix for objects to be listed") flag.Parse() // initialize minio client object minioClient, err := minio.New(*endpoint, *accessKeyID, *secretAccessKey, *useSSL) if err != nil { log.Fatalln(err) } // everything seems to be ok log.Printf("%#v\n", minioClient) listBuckets(minioClient) listObjects(minioClient, "foo", *objectPrefix) }
13. Uložení objektu z bucketu do lokálního souboru
Velmi často se setkáme s požadavkem, aby se nějaký objekt zkopíroval z Minia do lokálního souboru. K tomuto problému lze přistoupit dvěma způsoby – buď se použijí obecné funkce pro přístup k obsahu objektu, nebo se zavolá specializovaná metoda nazvaná FGetObject, která se o přečtení obsahu objektu a jeho uložení do lokálního souboru automaticky postará. Této metodě je nutné předat název bucketu, klíč objektu, jméno lokálního souboru a popř. další parametry (ty jsou uloženy ve struktuře, která může být prázdná):
err = minioClient.FGetObject("foo", "logos.jpg", "logos.jpg", minio.GetObjectOptions{}) if err != nil { log.Fatalln(err) }
Po spuštění této metody je vhodné se přesvědčit, zda se stažení skutečně podařilo:
$ file logos.jpg logos.jpg: JPEG image data, JFIF standard 1.01, aspect ratio, density 1x1, segment length 16, baseline, precision 8, 1600x878, frames 3
Úplný zdrojový kód demonstračního příkladu, který kopii objektu do lokálního souboru provede, může vypadat následovně:
package main import ( "flag" "github.com/minio/minio-go/v6" "log" ) func main() { var endpoint = flag.String("endpoint", "127.0.0.1:9000", "MinIO service endpoint") var accessKeyID = flag.String("accessKeyID", "", "Access key ID for MinIO") var secretAccessKey = flag.String("secretAccessKey", "", "Secret access key for MinIO") var useSSL = flag.Bool("useSSL", false, "Use SSL for communication with MinIO") flag.Parse() // initialize minio client object minioClient, err := minio.New(*endpoint, *accessKeyID, *secretAccessKey, *useSSL) if err != nil { log.Fatalln(err) } // everything seems to be ok log.Printf("%#v\n", minioClient) err = minioClient.FGetObject("foo", "logos.jpg", "logos.jpg", minio.GetObjectOptions{}) if err != nil { log.Fatalln(err) } }
14. Přečtení obsahu objektu s textem
Mnohdy potřebujeme obsah objektu programově přečíst z úložiště Minia a ihned zpracovat. V takovém případě ovšem není vhodné použít výše zmíněnou metodu FGetObject a posléze soubor znovu načíst do aplikace. Jak z paměťového, tak i výkonnostního hlediska je lepší obsah objektu přečíst přímo (bez meziuložení do souboru), k čemuž slouží metoda nazvaná GetObject s prakticky stejnými parametry, jaké má metoda FGetObject (pochopitelně chybí jméno souboru):
object, err := minioClient.GetObject(bucket, objectName, minio.GetObjectOptions{}) if err != nil { log.Fatalln(err) } defer object.Close()
Výše uvedeným postupem získáme strukturu implementující standardní rozhraní Reader, což nám například umožňuje zpracovávat obsah (textového) objektu řádek po řádku:
scanner := bufio.NewScanner(object) scanner.Split(bufio.ScanLines) for scanner.Scan() { fmt.Println(scanner.Text()) }
Obrázek 9: Založení nového objektu pod klíčem „t.go“, jehož obsahem je text. Tento soubor použijeme v příkladu popsaném v této kapitole
Opět se podívejme na úplný výpis demonstračního příkladu, který načte nově vytvořený objekt s obsahem získaným ze zdrojového (textového) souboru:
package main import ( "bufio" "flag" "fmt" "github.com/minio/minio-go/v6" "log" ) func printObject(minioClient *minio.Client, bucket string, objectName string) { object, err := minioClient.GetObject(bucket, objectName, minio.GetObjectOptions{}) if err != nil { log.Fatalln(err) } defer object.Close() scanner := bufio.NewScanner(object) scanner.Split(bufio.ScanLines) for scanner.Scan() { fmt.Println(scanner.Text()) } } func main() { var endpoint = flag.String("endpoint", "127.0.0.1:9000", "MinIO service endpoint") var accessKeyID = flag.String("accessKeyID", "", "Access key ID for MinIO") var secretAccessKey = flag.String("secretAccessKey", "", "Secret access key for MinIO") var useSSL = flag.Bool("useSSL", false, "Use SSL for communication with MinIO") flag.Parse() // initialize minio client object minioClient, err := minio.New(*endpoint, *accessKeyID, *secretAccessKey, *useSSL) if err != nil { log.Fatalln(err) } // everything seems to be ok log.Printf("%#v\n", minioClient) printObject(minioClient, "foo", "t.go") }
17:28 $ ./minio7 -accessKeyID=3V8WMANF061SGOIVR7AA -secretAccessKey=AHTM6+74n1Z8DZRZ4V7o83QcnYRnTEVblVb8sIlE 2019/12/14 17:28:06 &minio.Client{endpointURL:(*url.URL)(0xc000172000), credsProvider:(*credentials.Credentials)(0xc0000ac7e0), overrideSignerType:0, appInfo:struct { appName string; appVersion string }{appName:"", appVersion:""}, secure:false, httpClient:(*http.Client)(0xc0000938c0), bucketLocCache:(*minio.bucketLocationCache)(0xc00009e780), isTraceEnabled:false, traceErrorsOnly:false, traceOutput:io.Writer(nil), s3AccelerateEndpoint:"", region:"", random:(*rand.Rand)(0xc000093920), lookup:0} package main func main() { println(`foo "bar" baz`) }
15. Alternativní způsob přístupu k obsahu objektu
Obsah zvoleného objektu, který získáme z Minia, lze zpracovat různými způsoby. Stačí si uvědomit, že stále pracujeme s objektem implementujícím rozhraní Reader, takže lze použít všechny funkce a metody, které s tímto rozhraním dokáží spolupracovat. V dalším demonstračním příkladu je ukázáno použití funkce ioutil.ReadAll, která nám umožní načíst všechna data poskytovaná Readerem a vrátit je jako řez (slice) bajtů:
$ go doc ioutil.ReadAll func ReadAll(r io.Reader) ([]byte, error) ReadAll reads from r until an error or EOF and returns the data it read. A successful call returns err == nil, not err == EOF. Because ReadAll is defined to read from src until EOF, it does not treat an EOF from Read as an error to be reported.
Celý řez bajtů posléze snadno převedeme na řetězec a vytiskneme:
object, err := minioClient.GetObject(bucket, objectName, minio.GetObjectOptions{}) if err != nil { log.Fatalln(err) } defer object.Close() bytes, err := ioutil.ReadAll(object) if err != nil { log.Fatalln(err) } fmt.Println(string(bytes))
Opět si ukažme úplný zdrojový kód tohoto demonstračního příkladu:
package main import ( "flag" "fmt" "github.com/minio/minio-go/v6" "io/ioutil" "log" ) func printObject(minioClient *minio.Client, bucket string, objectName string) { object, err := minioClient.GetObject(bucket, objectName, minio.GetObjectOptions{}) if err != nil { log.Fatalln(err) } defer object.Close() bytes, err := ioutil.ReadAll(object) if err != nil { log.Fatalln(err) } fmt.Println(string(bytes)) } func main() { var endpoint = flag.String("endpoint", "127.0.0.1:9000", "MinIO service endpoint") var accessKeyID = flag.String("accessKeyID", "", "Access key ID for MinIO") var secretAccessKey = flag.String("secretAccessKey", "", "Secret access key for MinIO") var useSSL = flag.Bool("useSSL", false, "Use SSL for communication with MinIO") flag.Parse() // initialize minio client object minioClient, err := minio.New(*endpoint, *accessKeyID, *secretAccessKey, *useSSL) if err != nil { log.Fatalln(err) } // everything seems to be ok log.Printf("%#v\n", minioClient) printObject(minioClient, "foo", "t.go") }
16. Poslání dat do Minia s jejich uložením do objektu
V předchozích kapitolách jsme si ukázali, jak lze získat obsah objektu z úložiště Minia. Víme, jak se obsah objektu uloží do souboru metodou FGetObject popř. získá ve formě sekvence bajtů metodou GetObject. Existuje pochopitelně i opačný postup, tj. poslání dat do Minia s jejich uložením. Pokud jsou data uložená v lokálním souboru, je situace jednoduchá, neboť lze použít metodu FPutObject:
length, err := minioClient.FPutObject("foo", "minio9.go", "minio9.go", minio.PutObjectOptions{ ContentType: "text/plain;charset=UTF-8", }) if err != nil { fmt.Println(err) return } fmt.Println("Successfully uploaded bytes: ", length)
V dalším demonstračním příkladu se nejdříve do úložiště Minia pošle textový soubor se zdrojovým textem a následně se zpětně přečte a vypíše jeho obsah:
package main import ( "flag" "fmt" "github.com/minio/minio-go/v6" "io/ioutil" "log" ) func printObject(minioClient *minio.Client, bucket string, objectName string) { object, err := minioClient.GetObject(bucket, objectName, minio.GetObjectOptions{}) if err != nil { log.Fatalln(err) } defer object.Close() bytes, err := ioutil.ReadAll(object) if err != nil { log.Fatalln(err) } fmt.Println(string(bytes)) } func main() { var endpoint = flag.String("endpoint", "127.0.0.1:9000", "MinIO service endpoint") var accessKeyID = flag.String("accessKeyID", "", "Access key ID for MinIO") var secretAccessKey = flag.String("secretAccessKey", "", "Secret access key for MinIO") var useSSL = flag.Bool("useSSL", false, "Use SSL for communication with MinIO") flag.Parse() // initialize minio client object minioClient, err := minio.New(*endpoint, *accessKeyID, *secretAccessKey, *useSSL) if err != nil { log.Fatalln(err) } // everything seems to be ok log.Printf("%#v\n", minioClient) length, err := minioClient.FPutObject("foo", "minio9.go", "minio9.go", minio.PutObjectOptions{ ContentType: "text/plain;charset=UTF-8", }) if err != nil { fmt.Println(err) return } fmt.Println("Successfully uploaded bytes: ", length) printObject(minioClient, "foo", "minio9.go") }
Výsledek může vypadat takto (je zkrácený):
17:42 $ ./minio9 -accessKeyID=3V8WMANF061SGOIVR7AA -secretAccessKey=AHTM6+74n1Z8DZRZ4V7o83QcnYRnTEVblVb8sIlE 2019/12/14 17:47:29 &minio.Client{endpointURL:(*url.URL)(0xc00015a000), credsProvider:(*credentials.Credentials)(0xc00006e840), overrideSignerType:0, appInfo:struct { appName string; appVersion string }{appName:"", appVersion:""}, secure:false, httpClient:(*http.Client)(0xc0000138f0), bucketLocCache:(*minio.bucketLocationCache)(0xc00000e7a0), isTraceEnabled:false, traceErrorsOnly:false, traceOutput:io.Writer(nil), s3AccelerateEndpoint:"", region:"", random:(*rand.Rand)(0xc000013950), lookup:0} Successfully uploaded bytes: 1299 ... ... ...
Obrázek 10: V úložišti byl vytvořen nový objekt uložený pod klíčem „minio9.go“.
17. Kopie objektu v rámci Minia bez jejich přesunu na lokální počítač
Poslední operací, s níž se dnes seznámíme, je kopie objektu v rámci Minia, bez nutnosti přesunu dat na lokální počítač (což je obecně velmi pomalá operace, protože pro Minio je úzkým hrdlem síťové rozhraní). Samotnou kopii objektu zajišťuje metoda CopyObject:
err = minioClient.CopyObject(dst, src) if err != nil { log.Fatalln(err) }
Této metodě je nutné předat dvě struktury reprezentující cílový a zdrojový objekt. Pro vytvoření těchto struktur existují funkce–konstruktory nazvané NewSourceInfo a NewDestinationInfo, kterým je nutné předat minimálně jméno bucketu a jméno objektu:
src := minio.NewSourceInfo(bucket, from, nil) dst, err := minio.NewDestinationInfo(bucket, to, nil, nil)
Úplný zdrojový kód dnešního posledního demonstračního příkladu, který zkopíruje objekt s klíčem „minio9.go“ do objektu „minio10.go“, vypadá následovně:
package main import ( "flag" "fmt" "github.com/minio/minio-go/v6" "io/ioutil" "log" ) func printObject(minioClient *minio.Client, bucket string, objectName string) { object, err := minioClient.GetObject(bucket, objectName, minio.GetObjectOptions{}) if err != nil { log.Fatalln(err) } defer object.Close() bytes, err := ioutil.ReadAll(object) if err != nil { log.Fatalln(err) } fmt.Println(string(bytes)) } func copyObject(minioClient *minio.Client, bucket string, from string, to string) { src := minio.NewSourceInfo(bucket, from, nil) dst, err := minio.NewDestinationInfo(bucket, to, nil, nil) if err != nil { log.Fatalln(err) } err = minioClient.CopyObject(dst, src) if err != nil { log.Fatalln(err) } } func main() { var endpoint = flag.String("endpoint", "127.0.0.1:9000", "MinIO service endpoint") var accessKeyID = flag.String("accessKeyID", "", "Access key ID for MinIO") var secretAccessKey = flag.String("secretAccessKey", "", "Secret access key for MinIO") var useSSL = flag.Bool("useSSL", false, "Use SSL for communication with MinIO") flag.Parse() // initialize minio client object minioClient, err := minio.New(*endpoint, *accessKeyID, *secretAccessKey, *useSSL) if err != nil { log.Fatalln(err) } // everything seems to be ok log.Printf("%#v\n", minioClient) printObject(minioClient, "foo", "minio9.go") copyObject(minioClient, "foo", "minio9.go", "minio10.go") printObject(minioClient, "foo", "minio10.go") }
Obrázek 11: Objekty „minio9.go“ a „minio10.go“ by měly mít totožný obsah.
18. Obsah následující části seriálu
V navazující části tohoto seriálu si ukážeme další (již poněkud komplikovanější) operace nabízené SDK, ovšem nezapomeneme ani na použití nástroje mc a na ukázku využití služby Minia z dalších programovacích jazyků, zejména z Pythonu, jenž se v souvislosti s Miniem i s AWS S3 používá velmi často.
Obrázek 12: Vybraný objekt či objekty je možné z úložiště smazat. Prozatím jsme nenastavovali uživatelská práva, takže smazání je proveditelné.
19. Repositář s demonstračními příklady
Zdrojové kódy všech dnes použitých demonstračních příkladů byly uloženy do Git repositáře, který je dostupný na adrese https://github.com/tisnik/go-root (stále na GitHubu :-). V případě, že nebudete chtít klonovat celý repositář (ten je ovšem – alespoň prozatím – velmi malý, dnes má přibližně pět až šest megabajtů), můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:
20. Odkazy na Internetu
- Stránky projektu MinIO
https://min.io/ - MinIO Quickstart Guide
https://docs.min.io/docs/minio-quickstart-guide.html - MinIO Go Client API Reference
https://docs.min.io/docs/golang-client-api-reference - Performance at Scale: MinIO Pushes Past 1.4 terabits per second with 256 NVMe Drives
https://blog.min.io/performance-at-scale-minio-pushes-past-1–3-terabits-per-second-with-256-nvme-drives/ - Benchmarking MinIO vs. AWS S3 for Apache Spark
https://blog.min.io/benchmarking-apache-spark-vs-aws-s3/ - MinIO Client Quickstart Guide
https://docs.min.io/docs/minio-client-quickstart-guide.html - Analýza kvality zdrojových kódů Minia
https://goreportcard.com/report/github.com/minio/minio - This is MinIO
https://www.youtube.com/watch?v=vF0lQh0XOCs - Running MinIO Standalone
https://www.youtube.com/watch?v=dIQsPCHvHoM - „Amazon S3 Compatible Storage in Kubernetes“ – Rob Girard, Principal Tech Marketing Engineer, Minio
https://www.youtube.com/watch?v=wlpn8K0jJ4U - Ginkgo
http://onsi.github.io/ginkgo/ - Gomega
https://onsi.github.io/gomega/ - Ginkgo's Preferred Matcher Library na GitHubu
https://github.com/onsi/gomega/ - Provided Matchers
http://onsi.github.io/gomega/#provided-matchers - Dokumentace k balíčku goexpect
https://godoc.org/github.com/google/goexpect - Balíček goexpect
https://github.com/google/goexpect - Balíček go-expect
https://github.com/Netflix/go-expect - Balíček gexpect
https://github.com/ThomasRooney/gexpect - Expect (originál naprogramovaný v TCL)
https://core.tcl-lang.org/expect/index - Expect (Wikipedia)
https://en.wikipedia.org/wiki/Expect - Pexpect
https://pexpect.readthedocs.io/en/stable/ - Golang SSH Client: Multiple Commands, Crypto & Goexpect Examples
http://networkbit.ch/golang-ssh-client/ - goblin na GitHubu
https://github.com/franela/goblin - Mocha framework
https://mochajs.org/ - frisby na GitHubu
https://github.com/verdverm/frisby - package frisby
https://godoc.org/github.com/verdverm/frisby - Frisby alternatives and similar packages (generováno)
https://go.libhunt.com/frisby-alternatives - Cucumber for golang
https://github.com/DATA-DOG/godog - How to Use Godog for Behavior-driven Development in Go
https://semaphoreci.com/community/tutorials/how-to-use-godog-for-behavior-driven-development-in-go - Comparative Analysis Of GoLang Testing Frameworks
https://www.slideshare.net/DushyantBhalgami/comparative-analysis-of-golang-testing-frameworks - A Quick Guide to Testing in Golang
https://caitiem.com/2016/08/18/a-quick-guide-to-testing-in-golang/ - Tom's Obvious, Minimal Language.
https://github.com/toml-lang/toml - xml.org
http://www.xml.org/ - Soubory .properties
https://en.wikipedia.org/wiki/.properties - Soubory INI
https://en.wikipedia.org/wiki/INI_file - JSON to YAML
https://www.json2yaml.com/ - Data Format Converter
https://toolkit.site/format.html - Viper na GitHubu
https://github.com/spf13/viper - GoDotEnv na GitHubu
https://github.com/joho/godotenv - The fantastic ORM library for Golang
http://gorm.io/ - Dokumentace k balíčku gorilla/mux
https://godoc.org/github.com/gorilla/mux - Gorilla web toolkitk
http://www.gorillatoolkit.org/ - Metric types
https://prometheus.io/docs/concepts/metric_types/ - Histograms with Prometheus: A Tale of Woe
http://linuxczar.net/blog/2017/06/15/prometheus-histogram-2/ - Why are Prometheus histograms cumulative?
https://www.robustperception.io/why-are-prometheus-histograms-cumulative - Histograms and summaries
https://prometheus.io/docs/practices/histograms/ - Instrumenting Golang server in 5 min
https://medium.com/@gsisimogang/instrumenting-golang-server-in-5-min-c1c32489add3 - Semantic Import Versioning in Go
https://www.aaronzhuo.com/semantic-import-versioning-in-go/ - Sémantické verzování
https://semver.org/ - Getting started with Go modules
https://medium.com/@fonseka.live/getting-started-with-go-modules-b3dac652066d - Create projects independent of $GOPATH using Go Modules
https://medium.com/mindorks/create-projects-independent-of-gopath-using-go-modules-802260cdfb51o - Anatomy of Modules in Go
https://medium.com/rungo/anatomy-of-modules-in-go-c8274d215c16 - Modules
https://github.com/golang/go/wiki/Modules - Go Modules Tutorial
https://tutorialedge.net/golang/go-modules-tutorial/ - Module support
https://golang.org/cmd/go/#hdr-Module_support - Go Lang: Memory Management and Garbage Collection
https://vikash1976.wordpress.com/2017/03/26/go-lang-memory-management-and-garbage-collection/ - Golang Internals, Part 4: Object Files and Function Metadata
https://blog.altoros.com/golang-part-4-object-files-and-function-metadata.html - What is REPL?
https://pythonprogramminglanguage.com/repl/ - What is a REPL?
https://codewith.mu/en/tutorials/1.0/repl - Programming at the REPL: Introduction
https://clojure.org/guides/repl/introduction - What is REPL? (Quora)
https://www.quora.com/What-is-REPL - Gorilla REPL: interaktivní prostředí pro programovací jazyk Clojure
https://www.root.cz/clanky/gorilla-repl-interaktivni-prostredi-pro-programovaci-jazyk-clojure/ - Read-eval-print loop (Wikipedia)
https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop - Vim as a Go (Golang) IDE using LSP and vim-go
https://octetz.com/posts/vim-as-go-ide - gopls
https://github.com/golang/go/wiki/gopls - IDE Integration Guide
https://github.com/stamblerre/gocode/blob/master/docs/IDE_integration.md - How to instrument Go code with custom expvar metrics
https://sysdig.com/blog/golang-expvar-custom-metrics/ - Golang expvar metricset (Metricbeat Reference)
https://www.elastic.co/guide/en/beats/metricbeat/7.x/metricbeat-metricset-golang-expvar.html - Package expvar
https://golang.org/pkg/expvar/#NewInt - Java Platform Debugger Architecture: Overview
https://docs.oracle.com/en/java/javase/11/docs/specs/jpda/jpda.html - The JVM Tool Interface (JVM TI): How VM Agents Work
https://www.oracle.com/technetwork/articles/javase/index-140680.html - JVM Tool Interface Version 11.0
https://docs.oracle.com/en/java/javase/11/docs/specs/jvmti.html - Creating a Debugging and Profiling Agent with JVMTI
http://www.oracle.com/technetwork/articles/javase/jvmti-136367.html - JVM TI (Wikipedia)
http://en.wikipedia.org/wiki/JVM_TI - IBM JVMTI extensions
http://publib.boulder.ibm.com/infocenter/realtime/v2r0/index.jsp?topic=%2Fcom.ibm.softrt.doc%2Fdiag%2Ftools%2Fjvmti_extensions.html - Go & cgo: integrating existing C code with Go
http://akrennmair.github.io/golang-cgo-slides/#1 - Using cgo to call C code from within Go code
https://wenzr.wordpress.com/2018/06/07/using-cgo-to-call-c-code-from-within-go-code/ - Package trace
https://golang.org/pkg/runtime/trace/ - Introducing HTTP Tracing
https://blog.golang.org/http-tracing - Command trace
https://golang.org/cmd/trace/ - A StreamLike, Immutable, Lazy Loading and smart Golang Library to deal with slices
https://github.com/wesovilabs/koazee - Funkce vyššího řádu v knihovně Underscore
https://www.root.cz/clanky/funkce-vyssiho-radu-v-knihovne-underscore/ - Delve: a debugger for the Go programming language.
https://github.com/go-delve/delve - Příkazy debuggeru Delve
https://github.com/go-delve/delve/tree/master/Documentation/cli - Debuggery a jejich nadstavby v Linuxu
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu/ - Debuggery a jejich nadstavby v Linuxu (2. část)
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-2-cast/ - Debuggery a jejich nadstavby v Linuxu (3): Nemiver
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-3-nemiver/ - Debuggery a jejich nadstavby v Linuxu (4): KDbg
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-4-kdbg/ - Debuggery a jejich nadstavby v Linuxu (5): ladění aplikací v editorech Emacs a Vim
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-5-ladeni-aplikaci-v-editorech-emacs-a-vim/ - Debugging Go Code with GDB
https://golang.org/doc/gdb - Debugging Go (golang) programs with gdb
https://thornydev.blogspot.com/2014/01/debugging-go-golang-programs-with-gdb.html - GDB – Dokumentace
http://sourceware.org/gdb/current/onlinedocs/gdb/ - GDB – Supported Languages
http://sourceware.org/gdb/current/onlinedocs/gdb/Supported-Languages.html#Supported-Languages - GNU Debugger (Wikipedia)
https://en.wikipedia.org/wiki/GNU_Debugger - The LLDB Debugger
http://lldb.llvm.org/ - Debugger (Wikipedia)
https://en.wikipedia.org/wiki/Debugger - 13 Linux Debuggers for C++ Reviewed
http://www.drdobbs.com/testing/13-linux-debuggers-for-c-reviewed/240156817 - Go is on a Trajectory to Become the Next Enterprise Programming Language
https://hackernoon.com/go-is-on-a-trajectory-to-become-the-next-enterprise-programming-language-3b75d70544e - Go Proverbs: Simple, Poetic, Pithy
https://go-proverbs.github.io/ - Handling Sparse Files on Linux
https://www.systutorials.com/136652/handling-sparse-files-on-linux/ - Gzip (Wikipedia)
https://en.wikipedia.org/wiki/Gzip - Deflate
https://en.wikipedia.org/wiki/DEFLATE - 10 tools written in Go that every developer needs to know
https://gustavohenrique.net/en/2019/01/10-tools-written-in-go-that-every-dev-needs-to-know/ - Hexadecimální prohlížeče a editory s textovým uživatelským rozhraním
https://www.root.cz/clanky/hexadecimalni-prohlizece-a-editory-s-textovym-uzivatelskym-rozhranim/ - Hex dump
https://en.wikipedia.org/wiki/Hex_dump - Rozhraní io.ByteReader
https://golang.org/pkg/io/#ByteReader - Rozhraní io.RuneReader
https://golang.org/pkg/io/#RuneReader - Rozhraní io.ByteScanner
https://golang.org/pkg/io/#ByteScanner - Rozhraní io.RuneScanner
https://golang.org/pkg/io/#RuneScanner - Rozhraní io.Closer
https://golang.org/pkg/io/#Closer - Rozhraní io.Reader
https://golang.org/pkg/io/#Reader - Rozhraní io.Writer
https://golang.org/pkg/io/#Writer - Typ Strings.Reader
https://golang.org/pkg/strings/#Reader - VACUUM (SQL)
https://www.sqlite.org/lang_vacuum.html - VACUUM (Postgres)
https://www.postgresql.org/docs/8.4/sql-vacuum.html - go-cron
https://github.com/rk/go-cron - gocron
https://github.com/jasonlvhit/gocron - clockwork
https://github.com/whiteShtef/clockwork - clockwerk
https://github.com/onatm/clockwerk - JobRunner
https://github.com/bamzi/jobrunner - Rethinking Cron
https://adam.herokuapp.com/past/2010/4/13/rethinking_cron/ - In the Beginning was the Command Line
https://web.archive.org/web/20180218045352/http://www.cryptonomicon.com/beginning.html - repl.it (REPL pro různé jazyky)
https://repl.it/languages - GOCUI – Go Console User Interface (celé uživatelské prostředí, nejenom input box)
https://github.com/jroimartin/gocui - Read–eval–print loop
https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop - go-prompt
https://github.com/c-bata/go-prompt - readline
https://github.com/chzyer/readline - A pure golang implementation for GNU-Readline kind library
https://golangexample.com/a-pure-golang-implementation-for-gnu-readline-kind-library/ - go-readline
https://github.com/fiorix/go-readline - 4 Python libraries for building great command-line user interfaces
https://opensource.com/article/17/5/4-practical-python-libraries - prompt_toolkit 2.0.3 na PyPi
https://pypi.org/project/prompt_toolkit/ - python-prompt-toolkit na GitHubu
https://github.com/jonathanslenders/python-prompt-toolkit - The GNU Readline Library
https://tiswww.case.edu/php/chet/readline/rltop.html - GNU Readline (Wikipedia)
https://en.wikipedia.org/wiki/GNU_Readline - readline — GNU readline interface (Python 3.x)
https://docs.python.org/3/library/readline.html - readline — GNU readline interface (Python 2.x)
https://docs.python.org/2/library/readline.html - GNU Readline Library – command line editing
https://tiswww.cwru.edu/php/chet/readline/readline.html - gnureadline 6.3.8 na PyPi
https://pypi.org/project/gnureadline/ - Editline Library (libedit)
http://thrysoee.dk/editline/ - Comparing Python Command-Line Parsing Libraries – Argparse, Docopt, and Click
https://realpython.com/comparing-python-command-line-parsing-libraries-argparse-docopt-click/ - libedit or editline
http://www.cs.utah.edu/~bigler/code/libedit.html - WinEditLine
http://mingweditline.sourceforge.net/ - rlcompleter — Completion function for GNU readline
https://docs.python.org/3/library/rlcompleter.html - rlwrap na GitHubu
https://github.com/hanslub42/rlwrap - rlwrap(1) – Linux man page
https://linux.die.net/man/1/rlwrap - readline(3) – Linux man page
https://linux.die.net/man/3/readline - history(3) – Linux man page
https://linux.die.net/man/3/history - Dokumentace k balíčku oglematchers
https://godoc.org/github.com/jacobsa/oglematchers - Balíček oglematchers
https://github.com/jacobsa/oglematchers - Dokumentace k balíčku ogletest
https://godoc.org/github.com/jacobsa/ogletest - Balíček ogletest
https://github.com/jacobsa/ogletest - Dokumentace k balíčku assert
https://godoc.org/github.com/stretchr/testify/assert - Testify – Thou Shalt Write Tests
https://github.com/stretchr/testify/ - package testing
https://golang.org/pkg/testing/ - Golang basics – writing unit tests
https://blog.alexellis.io/golang-writing-unit-tests/ - An Introduction to Programming in Go / Testing
https://www.golang-book.com/books/intro/12 - An Introduction to Testing in Go
https://tutorialedge.net/golang/intro-testing-in-go/ - Advanced Go Testing Tutorial
https://tutorialedge.net/golang/advanced-go-testing-tutorial/ - GoConvey
http://goconvey.co/ - Testing Techniques
https://talks.golang.org/2014/testing.slide - 5 simple tips and tricks for writing unit tests in #golang
https://medium.com/@matryer/5-simple-tips-and-tricks-for-writing-unit-tests-in-golang-619653f90742 - Afinní transformace
https://cs.wikibooks.org/wiki/Geometrie/Afinn%C3%AD_transformace_sou%C5%99adnic - package gg
https://godoc.org/github.com/fogleman/gg - Generate an animated GIF with Golang
http://tech.nitoyon.com/en/blog/2016/01/07/go-animated-gif-gen/ - Generate an image programmatically with Golang
http://tech.nitoyon.com/en/blog/2015/12/31/go-image-gen/ - The Go image package
https://blog.golang.org/go-image-package - Balíček draw2D: 2D rendering for different output (raster, pdf, svg)
https://github.com/llgcode/draw2d - Draw a rectangle in Golang?
https://stackoverflow.com/questions/28992396/draw-a-rectangle-in-golang - YAML
https://yaml.org/ - edn
https://github.com/edn-format/edn - Smile
https://github.com/FasterXML/smile-format-specification - Protocol-Buffers
https://developers.google.com/protocol-buffers/ - Marshalling (computer science)
https://en.wikipedia.org/wiki/Marshalling_(computer_science) - Unmarshalling
https://en.wikipedia.org/wiki/Unmarshalling - Introducing JSON
http://json.org/ - Package json
https://golang.org/pkg/encoding/json/ - The Go Blog: JSON and Go
https://blog.golang.org/json-and-go - Go by Example: JSON
https://gobyexample.com/json - Writing Web Applications
https://golang.org/doc/articles/wiki/ - Golang Web Apps
https://www.reinbach.com/blog/golang-webapps-1/ - Build web application with Golang
https://legacy.gitbook.com/book/astaxie/build-web-application-with-golang/details - Golang Templates – Golang Web Pages
https://www.youtube.com/watch?v=TkNIETmF-RU - Simple Golang HTTPS/TLS Examples
https://github.com/denji/golang-tls - Playing with images in HTTP response in golang
https://www.sanarias.com/blog/1214PlayingwithimagesinHTTPresponseingolang - MIME Types List
https://www.freeformatter.com/mime-types-list.html - Go Mutex Tutorial
https://tutorialedge.net/golang/go-mutex-tutorial/ - Creating A Simple Web Server With Golang
https://tutorialedge.net/golang/creating-simple-web-server-with-golang/ - Building a Web Server in Go
https://thenewstack.io/building-a-web-server-in-go/ - How big is the pipe buffer?
https://unix.stackexchange.com/questions/11946/how-big-is-the-pipe-buffer - How to turn off buffering of stdout in C
https://stackoverflow.com/questions/7876660/how-to-turn-off-buffering-of-stdout-in-c - setbuf(3) – Linux man page
https://linux.die.net/man/3/setbuf - setvbuf(3) – Linux man page (stejný obsah jako předchozí stránka)
https://linux.die.net/man/3/setvbuf - Select waits on a group of channels
https://yourbasic.org/golang/select-explained/ - Rob Pike: Simplicity is Complicated (video)
http://www.golang.to/posts/dotgo-2015-rob-pike-simplicity-is-complicated-youtube-16893 - Algorithms to Go
https://yourbasic.org/ - Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů
https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu/ - Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů: vlastní filtry a lexery
https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu-vlastni-filtry-a-lexery/ - Go Defer Simplified with Practical Visuals
https://blog.learngoprogramming.com/golang-defer-simplified-77d3b2b817ff - 5 More Gotchas of Defer in Go — Part II
https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-ii-cc550f6ad9aa - The Go Blog: Defer, Panic, and Recover
https://blog.golang.org/defer-panic-and-recover - The defer keyword in Swift 2: try/finally done right
https://www.hackingwithswift.com/new-syntax-swift-2-defer - Swift Defer Statement
https://andybargh.com/swift-defer-statement/ - Modulo operation (Wikipedia)
https://en.wikipedia.org/wiki/Modulo_operation - Node.js vs Golang: Battle of the Next-Gen Languages
https://www.hostingadvice.com/blog/nodejs-vs-golang/ - The Go Programming Language (home page)
https://golang.org/ - GoDoc
https://godoc.org/ - Go (programming language), Wikipedia
https://en.wikipedia.org/wiki/Go_(programming_language) - Go Books (kniha o jazyku Go)
https://github.com/dariubs/GoBooks - The Go Programming Language Specification
https://golang.org/ref/spec - Go: the Good, the Bad and the Ugly
https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/ - Package builtin
https://golang.org/pkg/builtin/ - Package fmt
https://golang.org/pkg/fmt/ - The Little Go Book (další kniha)
https://github.com/dariubs/GoBooks - The Go Programming Language by Brian W. Kernighan, Alan A. A. Donovan
https://www.safaribooksonline.com/library/view/the-go-programming/9780134190570/ebook_split010.html - Learning Go
https://www.miek.nl/go/ - Go Bootcamp
http://www.golangbootcamp.com/ - Programming in Go: Creating Applications for the 21st Century (další kniha o jazyku Go)
http://www.informit.com/store/programming-in-go-creating-applications-for-the-21st-9780321774637 - Introducing Go (Build Reliable, Scalable Programs)
http://shop.oreilly.com/product/0636920046516.do - Learning Go Programming
https://www.packtpub.com/application-development/learning-go-programming - The Go Blog
https://blog.golang.org/ - Getting to Go: The Journey of Go's Garbage Collector
https://blog.golang.org/ismmkeynote - Go (programovací jazyk, Wikipedia)
https://cs.wikipedia.org/wiki/Go_(programovac%C3%AD_jazyk) - Rychle, rychleji až úplně nejrychleji s jazykem Go
https://www.root.cz/clanky/rychle-rychleji-az-uplne-nejrychleji-s-jazykem-go/ - Installing Go on the Raspberry Pi
https://dave.cheney.net/2012/09/25/installing-go-on-the-raspberry-pi - How the Go runtime implements maps efficiently (without generics)
https://dave.cheney.net/2018/05/29/how-the-go-runtime-implements-maps-efficiently-without-generics - Niečo málo o Go – Golang (slovensky)
http://golangsk.logdown.com/ - How Many Go Developers Are There?
https://research.swtch.com/gophercount - Most Popular Technologies (Stack Overflow Survery 2018)
https://insights.stackoverflow.com/survey/2018/#most-popular-technologies - Most Popular Technologies (Stack Overflow Survery 2017)
https://insights.stackoverflow.com/survey/2017#technology - JavaScript vs. Golang for IoT: Is Gopher Winning?
https://www.iotforall.com/javascript-vs-golang-iot/ - The Go Programming Language: Release History
https://golang.org/doc/devel/release.html - Go 1.11 Release Notes
https://golang.org/doc/go1.11 - Go 1.10 Release Notes
https://golang.org/doc/go1.10 - Go 1.9 Release Notes (tato verze je stále používána)
https://golang.org/doc/go1.9 - Go 1.8 Release Notes (i tato verze je stále používána)
https://golang.org/doc/go1.8 - Go on Fedora
https://developer.fedoraproject.org/tech/languages/go/go-installation.html - Writing Go programs
https://developer.fedoraproject.org/tech/languages/go/go-programs.html - The GOPATH environment variable
https://tip.golang.org/doc/code.html#GOPATH - Command gofmt
https://tip.golang.org/cmd/gofmt/ - The Go Blog: go fmt your code
https://blog.golang.org/go-fmt-your-code - C? Go? Cgo!
https://blog.golang.org/c-go-cgo - Spaces vs. Tabs: A 20-Year Debate Reignited by Google’s Golang
https://thenewstack.io/spaces-vs-tabs-a-20-year-debate-and-now-this-what-the-hell-is-wrong-with-go/ - 400,000 GitHub repositories, 1 billion files, 14 terabytes of code: Spaces or Tabs?
https://medium.com/@hoffa/400–000-github-repositories-1-billion-files-14-terabytes-of-code-spaces-or-tabs-7cfe0b5dd7fd - Gofmt No Longer Allows Spaces. Tabs Only
https://news.ycombinator.com/item?id=7914523 - Why does Go „go fmt“ uses tabs instead of whitespaces?
https://www.quora.com/Why-does-Go-go-fmt-uses-tabs-instead-of-whitespaces - Interactive: The Top Programming Languages 2018
https://spectrum.ieee.org/static/interactive-the-top-programming-languages-2018 - Go vs. Python
https://www.peterbe.com/plog/govspy - PackageManagementTools
https://github.com/golang/go/wiki/PackageManagementTools - A Tour of Go: Type inference
https://tour.golang.org/basics/14 - Go Slices: usage and internals
https://blog.golang.org/go-slices-usage-and-internals - Go by Example: Slices
https://gobyexample.com/slices - What is the point of slice type in Go?
https://stackoverflow.com/questions/2098874/what-is-the-point-of-slice-type-in-go - The curious case of Golang array and slices
https://medium.com/@hackintoshrao/the-curious-case-of-golang-array-and-slices-2565491d4335 - Introduction to Slices in Golang
https://www.callicoder.com/golang-slices/ - Golang: Understanding ‚null‘ and nil
https://newfivefour.com/golang-null-nil.html - What does nil mean in golang?
https://stackoverflow.com/questions/35983118/what-does-nil-mean-in-golang - nils In Go
https://go101.org/article/nil.html - Go slices are not dynamic arrays
https://appliedgo.net/slices/ - Go-is-no-good (nelze brát doslova)
https://github.com/ksimka/go-is-not-good - Rust vs. Go
https://news.ycombinator.com/item?id=13430108 - Seriál Programovací jazyk Rust
https://www.root.cz/serialy/programovaci-jazyk-rust/ - Modern garbage collection: A look at the Go GC strategy
https://blog.plan99.net/modern-garbage-collection-911ef4f8bd8e - Go GC: Prioritizing low latency and simplicity
https://blog.golang.org/go15gc - Is Golang a good language for embedded systems?
https://www.quora.com/Is-Golang-a-good-language-for-embedded-systems - Running GoLang on an STM32 MCU. A quick tutorial.
https://www.mickmake.com/post/running-golang-on-an-mcu-a-quick-tutorial - Go, Robot, Go! Golang Powered Robotics
https://gobot.io/ - Emgo: Bare metal Go (language for programming embedded systems)
https://github.com/ziutek/emgo - UTF-8 history
https://www.cl.cam.ac.uk/~mgk25/ucs/utf-8-history.txt - Less is exponentially more
https://commandcenter.blogspot.com/2012/06/less-is-exponentially-more.html - Should I Rust, or Should I Go
https://codeburst.io/should-i-rust-or-should-i-go-59a298e00ea9 - Setting up and using gccgo
https://golang.org/doc/install/gccgo - Elastic Tabstops
http://nickgravgaard.com/elastic-tabstops/ - Strings, bytes, runes and characters in Go
https://blog.golang.org/strings - Datový typ
https://cs.wikipedia.org/wiki/Datov%C3%BD_typ - Seriál o programovacím jazyku Rust: Základní (primitivní) datové typy
https://www.root.cz/clanky/programovaci-jazyk-rust-nahrada-c-nebo-slepa-cesta/#k09 - Seriál o programovacím jazyku Rust: Vytvoření „řezu“ z pole
https://www.root.cz/clanky/prace-s-poli-v-programovacim-jazyku-rust/#k06 - Seriál o programovacím jazyku Rust: Řezy (slice) vektoru
https://www.root.cz/clanky/prace-s-vektory-v-programovacim-jazyku-rust/#k05 - Printf Format Strings
https://www.cprogramming.com/tutorial/printf-format-strings.html - Java: String.format
https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#format-java.lang.String-java.lang.Object…- - Java: format string syntax
https://docs.oracle.com/javase/8/docs/api/java/util/Formatter.html#syntax - Selectors
https://golang.org/ref/spec#Selectors - Calling Go code from Python code
http://savorywatt.com/2015/09/18/calling-go-code-from-python-code/ - Go Data Structures: Interfaces
https://research.swtch.com/interfaces - How to use interfaces in Go
http://jordanorelli.com/post/32665860244/how-to-use-interfaces-in-go - Interfaces in Go (part I)
https://medium.com/golangspec/interfaces-in-go-part-i-4ae53a97479c - Part 21: Goroutines
https://golangbot.com/goroutines/ - Part 22: Channels
https://golangbot.com/channels/ - [Go] Lightweight eventbus with async compatibility for Go
https://github.com/asaskevich/EventBus - What about Trait support in Golang?
https://www.reddit.com/r/golang/comments/8mfykl/what_about_trait_support_in_golang/ - Don't Get Bitten by Pointer vs Non-Pointer Method Receivers in Golang
https://nathanleclaire.com/blog/2014/08/09/dont-get-bitten-by-pointer-vs-non-pointer-method-receivers-in-golang/ - Control Flow
https://en.wikipedia.org/wiki/Control_flow - Structured programming
https://en.wikipedia.org/wiki/Structured_programming - Control Structures
https://www.golang-book.com/books/intro/5 - Control structures – Go if else statement
http://golangtutorials.blogspot.com/2011/06/control-structures-if-else-statement.html - Control structures – Go switch case statement
http://golangtutorials.blogspot.com/2011/06/control-structures-go-switch-case.html - Control structures – Go for loop, break, continue, range
http://golangtutorials.blogspot.com/2011/06/control-structures-go-for-loop-break.html - Goroutine IDs
https://blog.sgmansfield.com/2015/12/goroutine-ids/ - Different ways to pass channels as arguments in function in go (golang)
https://stackoverflow.com/questions/24868859/different-ways-to-pass-channels-as-arguments-in-function-in-go-golang - justforfunc #22: using the Go execution tracer
https://www.youtube.com/watch?v=ySy3sR1LFCQ - Single Function Exit Point
http://wiki.c2.com/?SingleFunctionExitPoint - Entry point
https://en.wikipedia.org/wiki/Entry_point - Why does Go have a GOTO statement?!
https://www.reddit.com/r/golang/comments/kag5q/why_does_go_have_a_goto_statement/ - Effective Go
https://golang.org/doc/effective_go.html - GoClipse: an Eclipse IDE for the Go programming language
http://goclipse.github.io/ - GoClipse Installation
https://github.com/GoClipse/goclipse/blob/latest/documentation/Installation.md#installation - The zero value of a slice is not nil
https://stackoverflow.com/questions/30806931/the-zero-value-of-a-slice-is-not-nil - Go-tcha: When nil != nil
https://dev.to/pauljlucas/go-tcha-when-nil–nil-hic - Nils in Go
https://www.doxsey.net/blog/nils-in-go