Obsah
1. Další možnosti nabízené projektem MinIO
3. Nejdůležitější funkce a metody z SDK Minia pro Go
4. Přečtení informací o objektu uloženém v Miniu
5. Přístup do úložiště Minia z aplikací naprogramovaných v Pythonu
6. Vytvoření instance klienta pro připojení k Miniu
7. Nastavení údajů nutných pro připojení k Miniu z příkazového řádku
8. Vytištění všech bucketů, které jsou klientovi dostupné
9. Zjištění základních informací o objektech uložených do zvoleného bucketu
10. Přečtení objektu z Minia a uložení jeho obsahu do souboru
11. Programové přečtení obsahu vybraného objektu
12. Načtení textového obsahu z vybraného objektu
13. Uložení obsahu souboru do Minia
14. Přístupová práva k bucketu
15. Přečtení informací o přístupových právech k bucketu
16. Změna přístupových práv k bucketu
17. Přiřazení verze k objektům, uložení více verzí objektu se stejným jménem
18. Konfigurace životního cyklu objektů
19. Repositář s demonstračními příklady
1. Další možnosti nabízené projektem MinIO
V úvodním článku o projektu MinIO, který vyšel v úterý, jsme se seznámili se základními koncepty, na nichž je toto objektové úložiště postaveno. Taktéž jsme si řekli, že MinIO lze ovládat podobným způsobem jako známé cloudové úložiště AWS S3 (S3=Simple Storage Service) a je tak možné tento projekt nasadit do privátního clusteru, použít ho jako lokální úložiště, využít MinIO pro vývoj a testování aplikací, které v reálném nasazení použijí AWS S3 atd. atd.
Všechny demonstrační příklady ukázané minule byly vyvinuty v programovacím jazyku Go, což však v žádném případě neznamená, že se jedná o jediný jazyk, ve kterém mohou být psány aplikace, které k Miniu přistupují. K dispozici je šest oficiálních klientů pro šest platforem:
Obrázek 1: Služba MinIO aktivně upozorňuje administrátory ve chvíli, kdy se pokouší pracovat se starší verzí, což nastalo i v našem případě – verze, která byla ještě v době vydání první verze článku aktuální, je dnes již považována za zastaralou a je nabídnuta možnost reinstalace (která je mimochodem bezproblémová).
2. Spuštění Minia
Všechny dnes ukázané demonstrační příklady vyžadují, aby na systému běžela instance služby Minia s nastaveným úložištěm. Minio lze spustit z Dockeru (k dispozici je oficiální obraz) či přímo s využitím binárního spustitelného souboru – viz popis instalace uvedený minule:
$ ./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
3. Nejdůležitější funkce a metody z SDK Minia pro Go
V úvodním článku o Miniu jsme se seznámili s některými funkcemi a metodami nabízenými SDK určeným pro programovací jazyk Go. Připomeňme si ve stručnosti, o jaké funkce a metody se jednalo:
# | Funkce/metoda | Stručný popis |
---|---|---|
1 | minio.New | vytvoří novou instanci klienta, který reprezentuje připojení k Miniu (ostatní metody tuto instanci používají jako příjemce – receiver) |
2 | minio.Client.ListBuckets | získání seznamu všech dostupných bucketů |
3 | minio.Client.ListObjects | získání seznamu objektů v určitém bucketu |
4 | minio.Client.GetObject | přečtení obsahu vybraného objektu, obsah je reprezentován objektem Reader |
5 | minio.Client.FGetObject | uložení obsahu vybraného objektu z úložiště do lokálního souboru |
6 | minio.Client.FPutObject | kopie dat ze souboru do vybraného objektu v úložišti |
7 | minio.Client.CopyObject | kopie objektu v rámci úložiště (i mezi různými buckety) |
Funkce minio.New vytvoří novou instanci klienta, který reprezentuje připojení k Miniu (tedy ke službě obsluhující úložiště). Je možné, aby se v jedné aplikaci použilo větší množství klientů, protože se například může jednat o implementaci message brokera s více úložišti, synchronizační službu atd.:
minioClient, err := minio.New(endpoint, accessKeyID, secretAccessKey, useSSL) if err != nil { log.Fatalln(err) }
Dále jsme si popsali metodu nazvanou minio.Client.ListBucket, která vrátí seznam všech dostupných bucketů. Použití této metody je většinou snadné (i když je nutné kontrolovat, zda nedošlo k chybě):
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) }
Třetí důležitou metodou je metoda pojmenovaná minio.Client.ListObjects sloužící pro získání seznamu objektů v určitém bucketu. Ovšem tato metoda nabízí i další možnosti použití, protože lze specifikovat prefix klíče objektu a taktéž příznak, zda se má vyhledávání provádět rekurzivně. Navíc tato metoda používá i kanál (channel) pro oznámení, že již došlo k ukončení vyhledávání:
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) }
Další metoda se jmenuje minio.Client.FGetObject a slouží pro přečtení obsahu vybraného objektu a uložení tohoto obsahu do lokálního souboru:
err = minioClient.FGetObject("foo", "logos.jpg", "logos.jpg", minio.GetObjectOptions{}) if err != nil { log.Fatalln(err) }
Opakem metody minio.Client.FGetObject je metoda minio.Client.FPutObject, která naopak obsah lokálního souboru přenese do úložiště pod zadaným jménem bucketu a klíče (=jména objektu):
length, err := minioClient.FPutObject("foo", "minio9.go", "minio9.go", minio.PutObjectOptions{ ContentType: "text/plain;charset=UTF-8", }) if err != nil { fmt.Println(err) }
A nakonec nesmíme zapomenout na metodu minio.Client.GetObject, která slouží pro přečtení obsahu objektu. Z pohledu aplikace se vrací objekt implementující rozhraní Reader, takže lze použít prakticky všechny funkce, které s Readerem pracují (včetně komprimace streamu atd. atd.):
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) }
4. Přečtení informací o objektu uloženém v Miniu
V některých případech je nutné zjistit informace o objektu, který je v Miniu uložen. Připomeňme si, že objekt je určen jménem bucketu a klíčem (což je taktéž jméno), přičemž na jména bucketů jsou kladena podobná omezení, jako na URL či cesty v adresářové struktuře. Samotný objekt přitom není reprezentován pouze svým obsahem (sekvence bajtů), ale i dalšími metadaty, typicky příznaky přístupu, časovými razítky, určením typu dat (content-type) a v S3 i verzí (což je ovšem vlastnost, kterou Minio prozatím nepodporuje). Pro přečtení těchto informací slouží metoda minio.Client.StatObject:
# | Funkce/metoda | Stručný popis |
---|---|---|
1 | minio.Client.StatObject | získání podrobnějších informací o objektu |
Přečtení informací o objektu je jednoduché, pouze nesmíme zapomenout na správnou reakci na případný chybový stav:
info, err := minioClient.StatObject(bucket, name, minio.StatObjectOptions{}) if err != nil { log.Fatalln(err) }
Nejprve se podívejme na zdrojový kód demonstračního příkladu, posléze si ukážeme, jaké informace jsme získali:
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 getObjectInfo(minioClient *minio.Client, bucket string, name string) { fmt.Printf("Info for object %s in bucket %s\n", name, bucket) info, err := minioClient.StatObject(bucket, name, minio.StatObjectOptions{}) if err != nil { log.Fatalln(err) } fmt.Printf("Key: %s\nSize: %d\nTag: %s\nContent-type: %s\n", info.Key, info.Size, info.ETag, info.ContentType) } 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) fmt.Println() getObjectInfo(minioClient, "foo", "minio9.go") fmt.Println() getObjectInfo(minioClient, "foo", "logos.jpg") fmt.Println() getObjectInfo(minioClient, "foo", "something_else") }
Po spuštění tohoto příkladu by se měly zobrazit informace o dvou existujících objektech se jmény „minio9.go“ a „logos.jpg“ a o neexistujícím objektu „something_else“:
$ ./minio11 -accessKeyID=3V8WMANF061SGOIVR7AA -secretAccessKey=AHTM6+74n1Z8DZRZ4V7o83QcnYRnTEVblVb8sIlE 2019/12/17 10: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} 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} Info for object minio9.go in bucket foo Key: minio9.go Size: 1304 Tag: 34a37b73d9402b51201f42569f506d4b-1 Content-type: text/plain;charset=UTF-8 Info for object logos.jpg in bucket foo Key: logos.jpg Size: 48913 Tag: f95e4a85dafc56313883f8571cfc8143-1 Content-type: image/jpeg Info for object something_else in bucket foo 2019/12/17 10:28:06 The specified key does not exist.
5. Přístup do úložiště Minia z aplikací naprogramovaných v Pythonu
Ve druhé části dnešního článku si ukážeme, jakým způsobem lze přistupovat do úložiště Minia z aplikací, které jsou naprogramované v Pythonu. Uvidíme, že až na několik rozdílů je přístup z Pythonu prakticky totožný jako přístup z aplikací naprogramovaných v jazyku Go, pochopitelně s přihlédnutím k tomu, že v Pythonu se používají odlišné jmenné konvence, než je tomu v Go. Taktéž se liší způsob zpracování chyb – v Pythonu se používají klasické výjimky, zatímco Go se spoléhá na struktury typu error.
Před spuštěním demonstračních příkladů zmíněných v navazujících kapitolách je nutné mít nainstalován Pythonovský balíček nazvaný jednoduše minio. Tento balíček je dostupný na PyPi, takže je jeho instalace jednoduchá. Použijeme nástroj pip3 určený pro Python 3 (protože pro Python 2 se již odpočítává posledních několik dnů jeho oficiální podpory):
$ pip3 install --user minio Collecting minio Downloading https://files.pythonhosted.org/packages/14/46/60bff78df1b112cc50f95c5ffb2e14aaf9aa279a5219845b55c56f214383/minio-5.0.5-py2.py3-none-any.whl (62kB) 100% |████████████████████████████████| 71kB 1.7MB/s Requirement already satisfied: certifi in /usr/lib/python3.7/site-packages (from minio) Requirement already satisfied: urllib3 in /usr/lib/python3.7/site-packages (from minio) Requirement already satisfied: pytz in /usr/lib/python3.7/site-packages (from minio) Requirement already satisfied: python-dateutil in ./.local/lib/python3.7/site-packages (from minio) Requirement already satisfied: six>=1.5 in ./.local/lib/python3.7/site-packages (from python-dateutil->minio) Installing collected packages: minio Successfully installed minio-5.0.5
Většina funkcí a metod, které jsme si již popsali v souvislosti s SDK pro programovací jazyk Go, existuje i v Pythonu. Ovšem kvůli tomu, že jmenné konvence jsou v Pythonu odlišné a liší se i možnosti volání funkcí/metod (nepovinné parametry atd.), je ovládání Minia z Pythonu poněkud rozdílné. Ostatně se stačí podívat na následující tabulku s porovnáním jmen funkcí/metod v jazyku Go a jazyku Python:
# | Funkce/metoda v Go | Funkce/metoda v Pythonu |
---|---|---|
1 | minio.New (konstruktor) | minio.Minio (de facto konstruktor) |
2 | minio.Client.ListBuckets | minio.Minio.list_buckets |
3 | minio.Client.ListObjects | minio.Minio.list_objects |
4 | minio.Client.GetObject | minio.Minio.get_object |
5 | minio.Client.FGetObject | minio.Minio.fget_object |
6 | minio.Client.FPutObject | minio.Minio.fput_object |
7 | minio.Client.CopyObject | minio.Minio.copy_object |
8 | minio.Client.StatObject | minio.Minio.stat_object |
6. Vytvoření instance klienta pro připojení k Miniu
Nejprve si ukažme aplikaci, které pouze vytvoří instanci klienta sloužícího pro připojení k Miniu. Jedná se o obdobu prvního demonstračního příkladu z předchozího článku:
from minio import Minio from minio.error import ResponseError endpoint = "127.0.0.1:9000" accessKeyID = "3V8WMANF061SGOIVR7AA" secretAccessKey = "AHTM6+74n1Z8DZRZ4V7o83QcnYRnTEVblVb8sIlE" useSSL = True minioClient = Minio(endpoint, access_key=accessKeyID, secret_key=secretAccessKey, secure=useSSL) print(minioClient)
Po spuštění se pouze zobrazí základní informace o nově vytvořeném objektu, a to bez toho, aby se klient pokoušel o připojení k Miniu:
$ python3 minio1.py <minio.api.Minio object at 0x7fa8955c5550>
7. Nastavení údajů nutných pro připojení k Miniu z příkazového řádku
Předchozí demonstrační příklad náležitě 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 argparse, který se však používá odlišným způsobem než balíček flag v jazyce Go:
import argparse from minio import Minio from minio.error import ResponseError parser = argparse.ArgumentParser() parser.add_argument("--endpoint", default="127.0.0.1:9000", help="MinIO service endpoint") parser.add_argument("--accessKeyID", default="", help="Access key ID for MinIO") parser.add_argument("--secretAccessKey", default="", help="Secret access key for MinIO") parser.add_argument("--enable-ssl", dest="useSSL", action="store_true", help="Use SSL for communication with MinIO") parser.add_argument("--disable-ssl", dest="useSSL", action="store_false", help="Don't SSL for communication with MinIO") args = parser.parse_args() minioClient = Minio(args.endpoint, access_key=args.accessKeyID, secret_key=args.secretAccessKey, secure=args.useSSL) print(minioClient)
Opět si můžeme ukázat chování příkladu po jeho spuštění, tentokrát se specifikací všech potřebných údajů:
$ python3 minio2.py --accessKeyID=3V8WMANF061SGOIVR7AA --secretAccessKey=AHTM6+74n1Z8DZRZ4V7o83QcnYRnTEVblVb8sIlE --disable-ssl <minio.api.Minio object at 0x7fec0933c710>
8. Vytištění všech bucketů, které jsou klientovi dostupné
Třetí demonstrační příklad naprogramovaný v Pythonu 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 metodu list_buckets, která vrátí seznam bucketů (chyba je zde hlášena formou výjimky, na rozdíl od Go):
import argparse from minio import Minio from minio.error import ResponseError def list_buckets(minioClient): buckets = minioClient.list_buckets() for bucket in buckets: print(bucket.name, bucket.creation_date) parser = argparse.ArgumentParser() parser.add_argument("--endpoint", default="127.0.0.1:9000", help="MinIO service endpoint") parser.add_argument("--accessKeyID", default="", help="Access key ID for MinIO") parser.add_argument("--secretAccessKey", default="", help="Secret access key for MinIO") parser.add_argument("--enable-ssl", dest="useSSL", action="store_true", help="Use SSL for communication with MinIO") parser.add_argument("--disable-ssl", dest="useSSL", action="store_false", help="Don't SSL for communication with MinIO") args = parser.parse_args() minioClient = Minio(args.endpoint, access_key=args.accessKeyID, secret_key=args.secretAccessKey, secure=args.useSSL) list_buckets(minioClient)
Příklad výstupu pro platné klíče a existující úložiště:
$ python3 minio3.py --accessKeyID=3V8WMANF061SGOIVR7AA --secretAccessKey=AHTM6+74n1Z8DZRZ4V7o83QcnYRnTEVblVb8sIlE --disable-ssl bar 2019-12-14 10:30:57.918000+00:00 foo 2019-12-14 17:13:38.282000+00:00
Chyba v případě, že je zadán neplatný klíč:
$ python3 minio3.py --accessKeyID=3V8WMANF061SGOIVR7AA --secretAccessKey=AHTM6+74n1Z8DZRZ4V7o83QcnYRnTEVblVb8sIl --disable-ssl Traceback (most recent call last): File "minio3.py", line 30, in list_buckets(minioClient) File "minio3.py", line 8, in list_buckets buckets = minioClient.list_buckets() File "/home/ptisnovs/.local/lib/python3.7/site-packages/minio/api.py", line 385, in list_buckets raise ResponseError(response, method).get_exception() minio.error.SignatureDoesNotMatch: SignatureDoesNotMatch: message: The request signature we calculated does not match the signature you provided.
Chyba v případě, že vyžadujeme připojení přes SSL/TSL, ovšem služba je nakonfigurována odlišným způsobem:
$ python3 minio3.py --accessKeyID=3V8WMANF061SGOIVR7AA --secretAccessKey=AHTM6+74n1Z8DZRZ4V7o83QcnYRnTEVblVb8sIlE --enable-ssl Traceback (most recent call last): ... ... ... urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='127.0.0.1', port=9000): Max retries exceeded with url: / (Caused by SSLError(SSLError(1, '[SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:866)'),))
9. Zjištění základních informací o objektech uložených do zvoleného bucketu
Další skript je obdobou třetího příkladu z předchozího článku. Po jeho spuštění se vypíšou všechny objekty uložené ve zvoleném bucketu. Pro získání seznamu objektů se používá metoda list_objects objektu typu Minio:
import argparse from minio import Minio from minio.error import ResponseError def list_buckets(minioClient): buckets = minioClient.list_buckets() for bucket in buckets: print(bucket.name, bucket.creation_date) def list_objects(minioClient, bucket): print("List of objects for bucket:", bucket) objects = minioClient.list_objects(bucket, prefix="", recursive=False) for object in objects: print(object.bucket_name, object.last_modified, object.etag, object.size, object.object_name) parser = argparse.ArgumentParser() parser.add_argument("--endpoint", default="127.0.0.1:9000", help="MinIO service endpoint") parser.add_argument("--accessKeyID", default="", help="Access key ID for MinIO") parser.add_argument("--secretAccessKey", default="", help="Secret access key for MinIO") parser.add_argument("--enable-ssl", dest="useSSL", action="store_true", help="Use SSL for communication with MinIO") parser.add_argument("--disable-ssl", dest="useSSL", action="store_false", help="Don't SSL for communication with MinIO") args = parser.parse_args() minioClient = Minio(args.endpoint, access_key=args.accessKeyID, secret_key=args.secretAccessKey, secure=args.useSSL) list_buckets(minioClient) list_objects(minioClient, "foo")
Následuje ukázka, jakým způsobem se informace o objektech vypíšou. Nejprve je vypsáno jméno bucketu, potom čas modifikace objektu, MD5 heš, velikost objektu v bajtech a nakonec i jméno objektu:
$ python3 minio4.py --accessKeyID=3V8WMANF061SGOIVR7AA --secretAccessKey=AHTM6+74n1Z8DZRZ4V7o83QcnYRnTEVblVb8sIlE --disable-ssl bar 2019-12-14 10:30:57.918000+00:00 foo 2019-12-14 17:13:38.282000+00:00 List of objects for bucket: foo foo 2019-12-14 12:01:10.533000+00:00 f95e4a85dafc56313883f8571cfc8143-1 48913 logos.jpg foo 2019-12-14 17:13:38.282000+00:00 f3025fabac6f9648eb6408911379b595-1 1304 minio10.go foo 2019-12-14 17:09:38.517000+00:00 34a37b73d9402b51201f42569f506d4b-1 1304 minio9.go foo 2019-12-14 16:10:38.411000+00:00 f2042bf5780d07253480fb8c64c60850-1 56 t.go
10. Přečtení objektu z Minia a uložení jeho obsahu do souboru
V dalším demonstračním příkladu se setkáme s metodou nazvanou fget_object, která je určena pro přečtení obsahu objektu a jeho uložení do lokálního souboru. Této metodě, která je obdobou Go metody FGetObject, je nutné předat název bucketu, klíč objektu, jméno lokálního souboru a popř. další parametry předané v nepovinných argumentech:
import argparse from minio import Minio from minio.error import ResponseError parser = argparse.ArgumentParser() parser.add_argument("--endpoint", default="127.0.0.1:9000", help="MinIO service endpoint") parser.add_argument("--accessKeyID", default="", help="Access key ID for MinIO") parser.add_argument("--secretAccessKey", default="", help="Secret access key for MinIO") parser.add_argument("--enable-ssl", dest="useSSL", action="store_true", help="Use SSL for communication with MinIO") parser.add_argument("--disable-ssl", dest="useSSL", action="store_false", help="Don't SSL for communication with MinIO") args = parser.parse_args() minioClient = Minio(args.endpoint, access_key=args.accessKeyID, secret_key=args.secretAccessKey, secure=args.useSSL) minioClient.fget_object("foo", "logos.jpg", "logos.jpg")
Po spuštění tohoto příkladu by se měl v aktuálním adresáři objevit soubor se jménem „logos.go“ obsahující šestici log programovacího jazyka Go:
$ python3 minio6.py --accessKeyID=3V8WMANF061SGOIVR7AA --secretAccessKey=AHTM6+74n1Z8DZRZ4V7o83QcnYRnTEVblVb8sIlE --disable-ssl
Pro jistotu se o výsledku přesvědčíme:
$ 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
11. Programové přečtení obsahu vybraného objektu
Ve chvíli, kdy je nutné obsah objektu z úložiště přečíst a nějakým způsobem zpracovat, použijeme namísto metody fget_object metodu nazvanou get_object (tedy bez „f“ na začátku). Tato metoda umožňuje, aby se s její návratovou hodnotou pracovalo stejným způsobem, jakoby se jednalo o otevřený soubor:
import argparse from minio import Minio from minio.error import ResponseError def print_object(minioClient, bucket, object_name): try: data = minioClient.get_object(bucket, object_name) for line in data.readlines(): print(line) except ResponseError as err: print(err) parser = argparse.ArgumentParser() parser.add_argument("--endpoint", default="127.0.0.1:9000", help="MinIO service endpoint") parser.add_argument("--accessKeyID", default="", help="Access key ID for MinIO") parser.add_argument("--secretAccessKey", default="", help="Secret access key for MinIO") parser.add_argument("--enable-ssl", dest="useSSL", action="store_true", help="Use SSL for communication with MinIO") parser.add_argument("--disable-ssl", dest="useSSL", action="store_false", help="Don't SSL for communication with MinIO") args = parser.parse_args() minioClient = Minio(args.endpoint, access_key=args.accessKeyID, secret_key=args.secretAccessKey, secure=args.useSSL) print_object(minioClient, "foo", "t.go")
Po spuštění tohoto demonstračního příkladu ovšem zjistíme, že je obsah zpracováván jako pole bajtů, nikoli jako skutečné (Unicode) řetězce:
$ python3 minio7.py --accessKeyID=3V8WMANF061SGOIVR7AA --secretAccessKey=AHTM6+74n1Z8DZRZ4V7o83QcnYRnTEVblVb8sIlE --disable-ssl b'package main\n' b'\n' b'func main() {\n' b'\tprintln(`foo "bar" baz`)\n' b'}\n'
12. Načtení textového obsahu z vybraného objektu
Ve chvíli, kdy objekt obsahuje textová data, je nutné provést (alespoň v Pythonu 3) jejich dekódování do řetězce, a to konkrétně s využitím metody decode. V případě objektu obsahujícího zdrojové kódy v Go je situace jednoduchá, protože kódování je definitoricky nastaveno na UTF-8:
data = minioClient.get_object(bucket, object_name) for line in data.readlines(): print(line.decode('utf-8')[:-1])
Dekódování by mělo zajistit, že se obsah vybraného objektu skutečně zpracuje (zde jen vytiskne) jako sekvence textových řádků a nikoli sekvence polí bajtů:
$ python3 minio7B.py --accessKeyID=3V8WMANF061SGOIVR7AA --secretAccessKey=AHTM6+74n1Z8DZRZ4V7o83QcnYRnTEVblVb8sIlE --disable-ssl package main func main() { println(`foo "bar" baz`) }
Opět se podívejme na kompletní zdrojový kód demonstračního příkladu, který výše popsané dekódování používá:
import argparse from minio import Minio from minio.error import ResponseError def print_object(minioClient, bucket, object_name): try: data = minioClient.get_object(bucket, object_name) for line in data.readlines(): print(line.decode('utf-8')[:-1]) except ResponseError as err: print(err) parser = argparse.ArgumentParser() parser.add_argument("--endpoint", default="127.0.0.1:9000", help="MinIO service endpoint") parser.add_argument("--accessKeyID", default="", help="Access key ID for MinIO") parser.add_argument("--secretAccessKey", default="", help="Secret access key for MinIO") parser.add_argument("--enable-ssl", dest="useSSL", action="store_true", help="Use SSL for communication with MinIO") parser.add_argument("--disable-ssl", dest="useSSL", action="store_false", help="Don't SSL for communication with MinIO") args = parser.parse_args() minioClient = Minio(args.endpoint, access_key=args.accessKeyID, secret_key=args.secretAccessKey, secure=args.useSSL) print_object(minioClient, "foo", "t.go")
13. Uložení obsahu souboru do Minia
V posledním příkladu naprogramovaném v Pythonu použijeme metodu fput_object pro uložení obsahu lokálního souboru do úložiště Minia. V tomto konkrétním případě do Minia pošleme přímo zdrojové kódy spuštěného skriptu:
import argparse from minio import Minio from minio.error import ResponseError parser = argparse.ArgumentParser() parser.add_argument("--endpoint", default="127.0.0.1:9000", help="MinIO service endpoint") parser.add_argument("--accessKeyID", default="", help="Access key ID for MinIO") parser.add_argument("--secretAccessKey", default="", help="Secret access key for MinIO") parser.add_argument("--enable-ssl", dest="useSSL", action="store_true", help="Use SSL for communication with MinIO") parser.add_argument("--disable-ssl", dest="useSSL", action="store_false", help="Don't SSL for communication with MinIO") args = parser.parse_args() minioClient = Minio(args.endpoint, access_key=args.accessKeyID, secret_key=args.secretAccessKey, secure=args.useSSL) minioClient.fput_object("foo", "minio9.py", "minio9.py", content_type="text/plain")
14. Přístupová práva k bucketu
Z webového rozhraní lze nastavit základní přístupová práva k vybranému bucketu:
Obrázek 2: Dvojice bucketů vytvořených v rámci předchozího článku.
Obrázek 3: Dialog pro nastavení přístupových práv k bucketu může být poněkud matoucí. Poslední řádek není ukládán, slouží pro nastavení nových práv.
Obrázek 4: Takto vypadají konkrétně nastavená práva.
Ve skutečnosti jsou ovšem přístupová práva přiřazována pro jednotlivé operace:
- PutObject
- DeleteObject
- GetBucketLocation
- ListBucketMultipartUploads
- AbortMultipartUpload
atd. Tato práva lze nastavit metodou SetBucketPolicy a přečíst metodou GetBucketPolicy, což je téma, kterému se budeme věnovat v navazujících dvou kapitolách.
15. Přečtení informací o přístupových právech k bucketu
Ve třetí části článku se budeme věnovat některým dalším užitečným funkcím a metodám, tentokrát opět při použití klienta resp. aplikace vytvářené v programovacím jazyce Go. Použijeme zde metodu nazvanou GetBucketPolicy, která vrátí informace o přístupových právech k objektům v zadaném bucketu:
package main import ( "flag" "fmt" "github.com/minio/minio-go/v6" "log" ) func printBucketPolicy(minioClient *minio.Client, bucket string) { fmt.Printf("Policy for bucket: %s\n", bucket) policy, err := minioClient.GetBucketPolicy(bucket) if err != nil { log.Fatalln(err) } fmt.Println(policy) fmt.Println() } 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) printBucketPolicy(minioClient, "foo") printBucketPolicy(minioClient, "readonly") printBucketPolicy(minioClient, "writeonly") printBucketPolicy(minioClient, "readwrite") }
Po spuštění příkladu se zobrazí informace o bucketem, které jsme vytvořili a nastavili v rámci předchozí kapitoly:
$ ./minio13 -accessKeyID=3V8WMANF061SGOIVR7AA -secretAccessKey=AHTM6+74n1Z8DZRZ4V7o83QcnYRnTEVblVb8sIlE 2019/12/18 09:44:27 &minio.Client{endpointURL:(*url.URL)(0xc000176000), credsProvider:(*credentials.Credentials)(0xc0000b47e0), overrideSignerType:0, appInfo:struct { appName string; appVersion string }{appName:"", appVersion:""}, secure:false, httpClient:(*http.Client)(0xc00009b8c0), bucketLocCache:(*minio.bucketLocationCache)(0xc0000a6780), isTraceEnabled:false, traceErrorsOnly:false, traceOutput:io.Writer(nil), s3AccelerateEndpoint:"", region:"", random:(*rand.Rand)(0xc00009b920), lookup:0} Policy for bucket: foo Policy for bucket: readonly {"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":["*"]},"Action":["s3:GetBucketLocation","s3:ListBucket"],"Resource":["arn:aws:s3:::readonly"]},{"Effect":"Allow","Principal":{"AWS":["*"]},"Action":["s3:GetObject"],"Resource":["arn:aws:s3:::readonly/*"]}]} Policy for bucket: writeonly {"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":["*"]},"Action":["s3:GetBucketLocation","s3:ListBucketMultipartUploads"],"Resource":["arn:aws:s3:::writeonly"]},{"Effect":"Allow","Principal":{"AWS":["*"]},"Action":["s3:AbortMultipartUpload","s3:DeleteObject","s3:ListMultipartUploadParts","s3:PutObject"],"Resource":["arn:aws:s3:::writeonly/*"]}]} Policy for bucket: readwrite {"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":["*"]},"Action":["s3:GetBucketLocation","s3:ListBucket","s3:ListBucketMultipartUploads"],"Resource":["arn:aws:s3:::readwrite"]},{"Effect":"Allow","Principal":{"AWS":["*"]},"Action":["s3:DeleteObject","s3:GetObject","s3:ListMultipartUploadParts","s3:PutObject","s3:AbortMultipartUpload"],"Resource":["arn:aws:s3:::readwrite/*"]}]}
Vidíme, že práva jsou vrácena ve formě řetězce obsahujícího strukturu reprezentovanou v JSONu. Tuto strukturu je výhodné poslat do filtru pro pretty-printing JSONu, aby byly výsledky čitelnější.
Bucket „readonly“:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": [ "*" ] }, "Action": [ "s3:GetBucketLocation", "s3:ListBucket" ], "Resource": [ "arn:aws:s3:::readonly" ] }, { "Effect": "Allow", "Principal": { "AWS": [ "*" ] }, "Action": [ "s3:GetObject" ], "Resource": [ "arn:aws:s3:::readonly/*" ] } ] }
Bucket „writeonly“:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": [ "*" ] }, "Action": [ "s3:GetBucketLocation", "s3:ListBucketMultipartUploads" ], "Resource": [ "arn:aws:s3:::writeonly" ] }, { "Effect": "Allow", "Principal": { "AWS": [ "*" ] }, "Action": [ "s3:AbortMultipartUpload", "s3:DeleteObject", "s3:ListMultipartUploadParts", "s3:PutObject" ], "Resource": [ "arn:aws:s3:::writeonly/*" ] } ] }
Bucket „readwrite“:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": [ "*" ] }, "Action": [ "s3:GetBucketLocation", "s3:ListBucket", "s3:ListBucketMultipartUploads" ], "Resource": [ "arn:aws:s3:::readwrite" ] }, { "Effect": "Allow", "Principal": { "AWS": [ "*" ] }, "Action": [ "s3:DeleteObject", "s3:GetObject", "s3:ListMultipartUploadParts", "s3:PutObject", "s3:AbortMultipartUpload" ], "Resource": [ "arn:aws:s3:::readwrite/*" ] } ] }
16. Změna přístupových práv k bucketu
Změnu přístupových práv k bucketu na úrovni jednotlivých operací lze realizovat metodou SetBucketPolicy. Této metodě je nutné předat řetězec obsahující validní JSON se specifikací povolených operací. Pro jednoduchost tento JSON vytvoříme skutečně v řetězci, ovšem v praxi je lepší použít marshalling (zajímavé je, že tato operace není přímo podporována v SDK pro Minio v Go):
policyReadOnly := `{"Version": "2012-10-17","Statement": [{"Effect":"Allow","Principal":{"AWS": ["*"]},"Resource": ["arn:aws:s3:::readonly/*"],"Sid": "", "Action":["s3:GetObject","s3:PutObject"]}]}` err = minioClient.SetBucketPolicy("readonly", policyReadOnly) if err != nil { fmt.Println(err) return }
V dalším příkladu je tato možnost ukázána – nastavíme v něm nová práva pro bucket „readonly“, který byl původně určen skutečně pouze pro čtení:
package main import ( "flag" "fmt" "github.com/minio/minio-go/v6" "log" ) func printBucketPolicy(minioClient *minio.Client, bucket string) { fmt.Printf("Policy for bucket: %s\n", bucket) policy, err := minioClient.GetBucketPolicy(bucket) if err != nil { log.Fatalln(err) } fmt.Println(policy) fmt.Println() } 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) policyFoo := `{"Version": "2012-10-17","Statement": [{"Effect":"Allow","Principal":{"AWS": ["*"]},"Resource": ["arn:aws:s3:::foo/*"],"Sid": "", "Action":["s3:PutObject"]}]}` err = minioClient.SetBucketPolicy("foo", policyFoo) if err != nil { fmt.Println(err) return } policyReadOnly := `{"Version": "2012-10-17","Statement": [{"Effect":"Allow","Principal":{"AWS": ["*"]},"Resource": ["arn:aws:s3:::readonly/*"],"Sid": "", "Action":["s3:GetObject","s3:PutObject"]}]}` err = minioClient.SetBucketPolicy("readonly", policyReadOnly) if err != nil { fmt.Println(err) return } printBucketPolicy(minioClient, "foo") printBucketPolicy(minioClient, "readonly") printBucketPolicy(minioClient, "writeonly") printBucketPolicy(minioClient, "readwrite") }
Spuštění příkladu:
$ ./minio14 -accessKeyID=3V8WMANF061SGOIVR7AA -secretAccessKey=AHTM6+74n1Z8DZRZ4V7o83QcnYRnTEVblVb8sIlE 2019/12/18 10:09:31 &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} Policy for bucket: foo {"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":["*"]},"Action":["s3:PutObject"],"Resource":["arn:aws:s3:::foo/*"]}]} Policy for bucket: readonly {"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":["*"]},"Action":["s3:PutObject","s3:GetObject"],"Resource":["arn:aws:s3:::readonly/*"]}]} Policy for bucket: writeonly {"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":["*"]},"Action":["s3:GetBucketLocation","s3:ListBucketMultipartUploads"],"Resource":["arn:aws:s3:::writeonly"]},{"Effect":"Allow","Principal":{"AWS":["*"]},"Action":["s3:AbortMultipartUpload","s3:DeleteObject","s3:ListMultipartUploadParts","s3:PutObject"],"Resource":["arn:aws:s3:::writeonly/*"]}]} Policy for bucket: readwrite {"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":["*"]},"Action":["s3:GetBucketLocation","s3:ListBucket","s3:ListBucketMultipartUploads"],"Resource":["arn:aws:s3:::readwrite"]},{"Effect":"Allow","Principal":{"AWS":["*"]},"Action":["s3:AbortMultipartUpload","s3:DeleteObject","s3:GetObject","s3:ListMultipartUploadParts","s3:PutObject"],"Resource":["arn:aws:s3:::readwrite/*"]}]}
17. Přiřazení verze k objektům, uložení více verzí objektu se stejným jménem
V této kapitole si ukážeme, že některé vlastnosti, které uživatelům nabízí AWS S3, prozatím v Miniu nenajdeme. Ostatně si můžeme ocitovat slova jednoho z vývojářů Minia:
„Amazon keeps introducing new features to stay ahead of others. Minio will instead focus on minimalist design. We only need to implement what we see as a core functionality for object storage. „Core“ is defined as bare essential features to store and retrieve objects.“
Jednou z vlastností, kterou v Miniu nenalezneme, je podpora pro „verzování“ objektů, tj. možnost mít jeden objekt uložený ve více verzích. V Miniu je nutné použít jiný přístup, například:
„Idea of application layer versioning is to show that „versioning“ doesn't belong to the core. Saving newer objects with .1, .2 and so on (or with a timestamp suffix) is a simple idea that applications may do it themselves. I proposed to do it inside Minio library just for convenience.“
Na druhou stranu ovšem SDK pro Go umožňuje verzování povolit, ovšem funkční bude pouze při připojení do AWS S3. V dalším příkladu je jeden objekt uložen v deseti verzích:
package main import ( "flag" "fmt" "github.com/minio/minio-go/v6" "log" ) func createBucket(minioClient *minio.Client, bucket string) { err := minioClient.MakeBucket(bucket, "us-east-1") if err != nil { log.Fatalln(err) } log.Println("New bucket has been created") } func enableVersioning(minioClient *minio.Client, bucket string) { err := minioClient.EnableVersioning(bucket) if err != nil { log.Fatalln(err) } log.Println("Versioning has been enabled") } func uploadFile(minioClient *minio.Client, bucket string, filename string) { length, err := minioClient.FPutObject(bucket, filename, filename, minio.PutObjectOptions{ ContentType: "text/plain;charset=UTF-8", }) if err != nil { fmt.Println(err) return } fmt.Println("Successfully uploaded bytes: ", length) } 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) createBucket(minioClient, "versioned") enableVersioning(minioClient, "versioned") for i := 1; i < 10; i++ { uploadFile(minioClient, "versioned", "minio13.go") } }
18. Konfigurace životního cyklu objektů
Další vlastnost, kterou v Miniu nelze použít, je konfigurace životního cyklu objektů. V AWS S3 je totiž možné určit, po jakou dobu budou objekty v S3 uložené platné. Je tak možné rozlišit například mezi logy, zdrojovými daty pro analýzu AI/ML, skutečná data (například obsah CRM) atd. Pro úplnost si ukažme, jak se takové nastavení může provést. Tentokrát se nepoužívá formát JSON, ale XML:
<LifecycleConfiguration> <Rule> <ID>expire-bucket</ID> <Prefix></Prefix> <Status>Enabled</Status> <Expiration> <Days>` + strconv.Itoa(days) + `</Days> </Expiration> </Rule> </LifecycleConfiguration>`
Celý příklad (nefunkční v Miniu):
package main import ( "flag" "github.com/minio/minio-go/v6" "log" "strconv" ) func createBucket(minioClient *minio.Client, bucket string) { err := minioClient.MakeBucket(bucket, "us-east-1") if err != nil { log.Fatalln(err) } log.Println("New bucket has been created") } func setExpiration(minioClient *minio.Client, bucket string, days int) { rules := ` <LifecycleConfiguration> <Rule> <ID>expire-bucket</ID> <Prefix></Prefix> <Status>Enabled</Status> <Expiration> <Days>` + strconv.Itoa(days) + `</Days> </Expiration> </Rule> </LifecycleConfiguration>` println(rules) err := minioClient.SetBucketLifecycle(bucket, rules) if err != nil { log.Fatalln(err) } log.Println("Expiration has been configured") } 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) //createBucket(minioClient, "temporary") setExpiration(minioClient, "temporary", 1) }
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 - MinIO Python Client API Reference
https://docs.min.io/docs/python-client-api-reference.html - 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