{"id":28270100,"url":"https://github.com/chryscloud/go-microkit-plugins","last_synced_at":"2026-04-10T16:32:41.059Z","repository":{"id":57537299,"uuid":"285154372","full_name":"chryscloud/go-microkit-plugins","owner":"chryscloud","description":"Set of back-end GO libraries unifying development across multiple distributed micro-services","archived":false,"fork":false,"pushed_at":"2021-02-11T23:50:46.000Z","size":129,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-06-17T03:42:05.849Z","etag":null,"topics":["docker","gin-gonic","golang","jwt","microservices"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/chryscloud.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-08-05T02:16:17.000Z","updated_at":"2022-03-24T14:36:02.000Z","dependencies_parsed_at":"2022-09-04T12:52:53.473Z","dependency_job_id":null,"html_url":"https://github.com/chryscloud/go-microkit-plugins","commit_stats":null,"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"purl":"pkg:github/chryscloud/go-microkit-plugins","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chryscloud%2Fgo-microkit-plugins","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chryscloud%2Fgo-microkit-plugins/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chryscloud%2Fgo-microkit-plugins/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chryscloud%2Fgo-microkit-plugins/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/chryscloud","download_url":"https://codeload.github.com/chryscloud/go-microkit-plugins/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chryscloud%2Fgo-microkit-plugins/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":270602456,"owners_count":24614260,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-15T02:00:12.559Z","response_time":110,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["docker","gin-gonic","golang","jwt","microservices"],"created_at":"2025-05-20T16:17:17.816Z","updated_at":"2026-04-10T16:32:36.009Z","avatar_url":"https://github.com/chryscloud.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# go-microkit-plugins\n\n[![](https://godoc.org/github.com/chryscloud/go-microkit-plugins?status.svg)](https://godoc.org/github.com/chryscloud/go-microkit-plugins)\n\n## Introduction\n\nMicrokit Plugins is a set of backend GO libraries unifying development across multiple distributed microservices. It contains set of best practices and simple reusable modules most commonly used with RESTful microservices.\n\nBuilt on top of existing open source projects:\n- [Gin Web Framework](https://github.com/gin-gonic/gin)\n- [Zap Logging](https://github.com/uber-go/zap)\n- [JWT Go](https://github.com/dgrijalva/jwt-go)\n\n## Features\n\nThe framework is built with `modularity`, `reusability`, `extensibility` and `deployability` in mind.\n\n- **Authentication** - Configurable JWT and Token Middlewares\n- **Configuration** - Simple and extendable `yaml` server configuration\n- **Logging** - Zap logging plugin with custom output JSON format suitable for fluentd logging (e.g. Entry_L2, stack output on Google GKE clusters)\n- **Crypto** - Best practices for encrypting and validating user passwords, HMAC256 for external physical device authorization\n- **Server** - Simple RESTful server with graceful start and stop on top of `Gin Web Framework`\n- **Backpressure** - Optimizes event data warehousing. Collects individual events into `listed` chunks to be stored in custom data warehouse through a simple `PutMulti` interface.\n\n### Integration plugins\n\n- **Docker** - Most common interfaces to Docker containers (local sockets, over TCP and self signed certificates)\n\n## Contents\n\n* [Quick Start](#quick-start)\n* [Authenticaton](#authentication)\n\t* [JWT Authentication](#jwt-authentication)\n\t* [Token Identity](#token-identity)\n* [Configuration](#configuration)\n\t* [Extending Configuration](extending-configuration)\n* [Logging](#logging)\n* [Crypto](#crypto)\n\t* [Passwords](#passwords)\n\t* [Secret Key Generator](#secret-key-generator)\n\t* [HMAC-X](#hmac-x)\n* [Backpressure](#backpressure)\n* [Docker](#docker)\n* [Contributing](#contributing)\n* [Versioning](#versioning)\n* [Authors](#authors)\n* [License](#license)\n* [Acknowledgments](#acknowledgments)\n\n## Quick Start\n\nCreate a configuration file in the root folder of your project\n\n```yaml\nversion: 1.0\nport: 8080\ntitle: Go Micro kit service framework\ndescription: Go Micro kit service framework\nswagger: false\nmode: debug # \"debug\": or \"release\"\nauth_token:\n  enabled: false\n  header: \"authkey\"\n  token: \"abc\"\n\njwt_token:\n  enabled: false\n  secret_key: \"abcedf\"\n  cookie_name: \"mycookie\"\n```\n\nCreate main.go for your microservice:\n\n```go\npackage main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/signal\"\n\n\tcfg \"github.com/chryscloud/go-microkit-plugins/config\"\n\t\"github.com/chryscloud/go-microkit-plugins/endpoints\"\n\tmclog \"github.com/chryscloud/go-microkit-plugins/log\"\n\tmsrv \"github.com/chryscloud/go-microkit-plugins/server\"\n)\n\n// Log global wide logging\nvar Log mclog.Logger\n\n// Conf global config\nvar Conf Config\n\n// useful in case we extend the configuration\ntype Config struct {\n\tcfg.YamlConfig `yaml:\",inline\"`\n}\n\n// init logging\nfunc init() {\n\tl, err := mclog.NewEntry2ZapLogger(\"myservice\")\n\tif err != nil {\n\t\tpanic(\"failed to initialize logging\")\n\t}\n\tLog = l\n}\n\nfunc main() {\n\tvar (\n\t\tconfigFile string\n\t)\n\t// configuration file optional path. Default:  current dir with  filename conf.yaml\n\tflag.StringVar(\u0026configFile, \"c\", \"conf.yaml\", \"Configuration file path.\")\n\tflag.StringVar(\u0026configFile, \"config\", \"conf.yaml\", \"Configuration file path.\")\n\tflag.Usage = usage\n\tflag.Parse()\n\n    // init configuration from conf.yaml\n\terr := cfg.NewYamlConfig(configFile, \u0026Conf)\n\tif err != nil {\n\t\tLog.Error(err, \"conf.yaml failed to load\")\n\t\tpanic(\"Failed to load conf.yaml\")\n\t}\n\n\t// server wait to shutdown monitoring channels\n\tdone := make(chan bool, 1)\n\tquit := make(chan os.Signal, 1)\n\n\tsignal.Notify(quit, os.Interrupt)\n\n\t// init routing (for endpoints)\n\trouter := msrv.NewAPIRouter(\u0026Conf.YamlConfig)\n\n\troot := router.Group(\"/\")\n\t{\n\t\troot.GET(\"/\", endpoints.PingEndpoint)\n\t}\n\n\t// start server\n\tsrv := msrv.Start(\u0026Conf.YamlConfig, router, Log)\n\t// wait for server shutdown\n\tgo msrv.Shutdown(srv, Log, quit, done)\n\n\tLog.Info(\"Server is ready to handle requests at\", Conf.Port)\n\tif err := srv.ListenAndServe(); err != nil \u0026\u0026 err != http.ErrServerClosed {\n\t\tLog.Error(\"Could not listen on %s: %v\\n\", Conf.Port, err)\n\t}\n\n\t\u003c-done\n}\n\n// usage will print out the flag options for the server.\nfunc usage() {\n\tusageStr := `Usage: operator [options]\n\tServer Options:\n\t-c, --config \u003cfile\u003e              Configuration file path\n`\n\tfmt.Printf(\"%s\\n\", usageStr)\n\tos.Exit(0)\n}\n\n```\n\nRun:\n```bash\ngo run main.go\n```\n\nVisit: `http://localhost:8080`\n\nExpected output:\n```json\n\n{\"message\":\"pong at 20200804203903\"}\n```\n\n## Authentication\n\n### JWT Authentication\n\nEnable JWT token authentication in `conf.yaml` and define your secret key:\n\n```yaml\njwt_token:\n  enabled: true\n  secret_key: \"abcedf\"\n```\n\nOptionally define `cookie_name: \"mycookie\"` which JWT middleware checks if no `Authorization` header found. Make sure, if storing JWT tokens as cookies on the client side, to use `httpOnly` to avoid XSS.\n\nUsage:\n\n```go\nimport (\n\tmicroKitAuth \"github.com/chryscloud/go-microkit-plugins/auth\"\n)\n// init JWT Authentication for private endpoints\nkeys := func(token *jwt.Token) (interface{}, error) {\n\treturn []byte(Conf.JWTToken.SecretKey), nil\n}\nauhtMiddleware := microKitAuth.JwtMiddleware(\u0026Conf.YamlConfig, \u0026models.UserClaim{}, jwt.SigningMethodHS256, keys)\n\nroot := router.Group(\"/\", auhtMiddleware)\n{\n\troot.GET(\"/\", endpoints.PingEndpoint)\n}\n```\n\nGenerating JWT Tokens on authentication request:\n```go\nuserClaim := models.UserClaim{\n\tID:    \"userID\",\n\tRoles:   []string{\"role1\",\"role2\"},\n\tEnabled: true,\n}\ntoken, err := microKitAuth.NewJWTToken([]byte(Conf.JWTToken.SecretKey), jwt.SigningMethodHS256, userClaim)\n```\n\nDo not store passwords or any sensitive information into the `userClaim`!\n\n### Token Identity\n\nToken identity is named `identity` since it's not a complete solution for authorization. Nonetheless, it's included in this plugins package as it may be handy for `read-only` operations in some cases.\n\n\nEnable token identity in the `conf.yaml`:\n```yaml\nauth_token:\n  enabled: true\n  header: \"mycustomauthkey\"\n  token: \"secret_token\"\n  path: \"/*\"\n```\n\nUsage: \n```go\nimport (\n\tmicroKitAuth \"github.com/chryscloud/go-microkit-plugins/auth\"\n)\n\ntokenMiddleware := microKitAuth.TokenMiddleware(\u0026Cong.YamlConfig)\n\nroot := router.Group(\"/\", tokenMiddleware)\n{\n\troot.GET(\"/\", endpoints.PingEndpoint)\n}\n```\n\n## Configuration\n\nExample configuration:\n\n```yaml\nversion: 1.0\nport: 8080\ntitle: Go Micro kit service framework\ndescription: Go Micro kit service framework\nswagger: true\nmode: debug # \"debug\": or \"release\"\nauth_token:\n  enabled: false\n  header: \"authkey\"\n  token: \"abc\"\n\njwt_token:\n  enabled: false\n  secret_key: \"abcedf\"\n  cookie_name: \"mycookie\"\n\n# extended custom config\ntest_endpoint: \"this is test\"\n```\n\nLoading default configuration:\n```go\nvar conf cfg.YamlConfig\nerr := cfg.NewYamlConfig(\"/path/to/conf.yaml\", \u0026conf)\n```\n\n### Extending configuration\n\nTo extend default configuration define your own structure:\n```go\nvar conf Config\n\ntype Config struct {\n\tcfg.YamlConfig \t`yaml:\",inline\"`\n\tTestEndpoint \t`yaml:\"test_endpoint\"`\n}\n\n// in your main.go file load extended configuration\n \nerr := cfg.NewYamlConfig(\"/path/to/conf.yaml\", \u0026Conf)\nif err != nil {\n\tLog.Error(err, \"conf.yaml failed to load\")\n\tpanic(\"Failed to load conf.yaml\")\n}\n```\n\n## Logging\n\nLogging implements tagged style logging with [Ubers zap logger](https://github.com/uber-go/zap)\n\nThe output of the logger is in JSON format, adapted to fluentd logging requirements (stackdriver logging on Google or customizable to work on AWS EKS)\n\n\nTagged style logging methods\n```go\nfunc (z *ZapLogger) Error(keyvals ...interface{})\nfunc (z *ZapLogger) Warn(keyvals ...interface{})\nfunc (z *ZapLogger) Info(keyvals ...interface{})\n```\n\nError function extracts golang style stacktrace.\n\nExample to init logging into `stdout`:\n```go\nimport (\n\tmclog \"github.com/chryscloud/go-microkit-plugins/log\"\n)\n\n...\n\nlog, err := mclog.NewZapLogger(\"info\")\n```\n\nCompatible example with Google GKE logging:\n```go\nimport (\n\tmclog \"github.com/chryscloud/go-microkit-plugins/log\"\n)\n\n...\n\nlog, err := mclog.NewEntry2ZapLogger(\"nameofmyservice\")\n```\n\n## Crypto\n\nCrypto plugin has prepared functions for generating and validating strong cryptographic passwords. \n\n### Passwwords\n\nPassword generator uses `bcrypt` algorithm with `cost` of `14` currently. The cost factor defines the time it takes to generate password. Higher the cost, longer the time needed to generate it. \n\nUsage:\n```go\nimport (\n\tc \"github.com/chryscloud/go-microkit-plugins/crypto\"\n)\n...\npass, err := c.HashPassword(\"this is my password\")\nok := c.CheckPasswordHash(\"this is my password\", pass)\n```\n\n### Secret Key Generator\n\nSimple method for generating random secret key from Gos Crypto package. Input parameter indicates the length of the secret in bytes. Method returns hex encoded string.\n\n```go\nimport (\n\tc \"github.com/chryscloud/go-microkit-plugins/crypto\"\n)\n\nsecret := c.GenerateSecretKey(16)\n```\n\n### HMAC-X\n\nHash-based message authentication for verifying message integrity and authenticity (e.g. for external devices), with custom Hash algorithm.\n\n```go\nimport (\n\tc \"github.com/chryscloud/go-microkit-plugins/crypto\"\n)\n...\npayload := \"this is payload\"\nmac := ComputeHmac(sha256.New, payload, \"mysecret\")\n\nisValid := c.ValidateHmacSignature(sha256.New, payload, \"mysecret\", mac)\n```\n\n`sha256.New` can be exhanged for example with `sha512.New`. \n\nExample generating HMAC-256 (sha256) using curl:\n```bash\n$secret=\"mysecret\"\nnonce=$(date +%s)\napisig=`echo -n \"$nonce$key\" | openssl dgst -sha256 -hmac $secret -binary | xxd -p -c 256`\n\ncurl -s --connect-timeout 15  \"https://example.com/api/v1/myapi?timestamp=$nonce\u0026signature=$apisig\n```\n\n## Backpressure\n\nHandling of backpressure in case of event spikes when storing to Data Warehouse (such as BigQuery, Redshift,...) by batching them together. \n\n- Producer side pressure handling\n- **Parallel processing** - custom number of workers\n- **Configurable** - custom batch buffer and behaviour controls\n- **Dropping** - dropping event on buffer full\n\nImplementing `PutMulti` method:\n```go\ntype batchWorker struct {\n}\nfunc (bw *batchWorker) PutMulti(events []interface{}) error {\n\t// custom implementation (e.g. storing to database in batches, email sending, file writing,...)\n\treturn nil\n}\n```\n\nUse:\n```go\ntype event struct {\n\tname string\n}\n\nbw := \u0026batchWorker{}\n\nbckPress, err := backpressure.NewBackpressureContext(bw, BatchMaxSize(300), BatchTimeMs(100), Workers(100))\ndefer bckPress.Close()\n\ne := event{\n\tname: \"example event\",\n}\nerr := bckPress.Add(e)\n```\n\n- **BatchMaxSize** is the maximum number of items in a single batch\n- **BatchTimeMs** is the time to wait to collect the items in a single batch\n- **Workers** is number of workers (go routines)\n- **Log** is logging (compatible only with /log/log.go interface)\n\nBackpressure is mainly intended for high load batching and streaming to BigData such as BigQuery. It can also be used for loads that come occasionally in bursts, such as email (e.g. Mailgun supports batch sending) or any other scenario that involves batch processing or a large amount of small tasks.\n\nA Worker is a single blocking (synchronous) worker. It enqueues items and processes them in a blocking manner.\n\nWith defining e.g. `Workers(N\u003e1)` it employs multiple background workers.\n\nYour custom PutMulti implementation is activated according to item amount `BatchMaxSize(N)` and time threshold for each background worker `BatchTimeMs(MS)`.\n\n## Docker\n\nConvenient methods programmatic manipulation of Docker containers.\n\nPrepared 3 types of Docker access:\n- Local Socket Connection\n- Local TCP IP Connection\n- Remote TCP IP Connection using TLS self-signed certificates\n\n### TLS Client with self-signed certificates\n\n```go\ncert, _ := ioutil.ReadFile(\"/pathto/certificate/file\")\ncaCert, _ := ioutil.ReadFile(\"/pathto/certificateCA/file\")\ncertKey, _ := ioutil.ReadFile(\"/pathto/certificateKey/file\")\ncl := NewTLSClient(Host(\"tcp://xxx.xxx.xxxx.xxxx:2376\"), APIVersion(\"1.40\"), CACert(cacert), CertKey(certKey), Cert(cert))\n```\n\nBash script for creating client/server self-signed certificates:\n```bash\n!/bin/bash\nset -ex\nmkdir certs \u0026\u0026 cd certs\necho \"Creating server keys...\"\necho 01 \u003e ca.srl\nopenssl genrsa -des3 -out ca-key.pem\nopenssl req -new -x509 -days 3650 -key ca-key.pem -out ca.pem\nopenssl genrsa -des3 -out server-key.pem\nopenssl req -new -key server-key.pem -out server.csr\nopenssl x509 -req -days 365 -in server.csr -CA ca.pem -CAkey ca-key.pem \\\n    -out server-cert.pem\n\necho \"Creating client keys...\"\nopenssl genrsa -des3 -out client-key.pem\nopenssl req -new -key client-key.pem -out client.csr\necho extendedKeyUsage = clientAuth \u003e extfile.cnf\nopenssl x509 -req -days 365 -in client.csr -CA ca.pem -CAkey ca-key.pem \\\n    -out client-cert.pem -extfile extfile.cnf\n\necho \"Stripping passwords from keys...\"\nopenssl rsa -in server-key.pem -out server-key.pem\nopenssl rsa -in client-key.pem -out client-key.pem\n```\n\n### Enable remote access to docker\n\nCreate `daemon.json` file in folder: `/etc/docker/daemon.json`\n\n```json\n{\n    \"hosts\": [\"fd://\", \"tcp://0.0.0.0:2376\"],\n    \"log-driver\": \"json-file\",\n    \"log-opts\": {\"max-size\": \"10m\", \"max-file\": \"3\"},\n    \"tlscacert\": \"/root/.docker/ca.pem\",\n    \"tlscert\": \"/root/.docker/server-cert.pem\",\n    \"tlskey\": \"/root/.docker/server-key.pem\",\n    \"tlsverify\": true\n}\n```\n\nModify `docker.service` config. Open `/lib/systemd/system/docker.service` and comment out ExecStart and replace:\n\n```\n#ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock\nExecStart=\nExecStart=/usr/bin/dockerd\n```\n\nReload daemon:\n```\nsystemctl daemon-reload\n```\n\nRestart docker service:\n```\nsystemctl restart docker.service\n```\n\n## Enable access to local Docker Socket\n\nCreate `daemon.json` file in `/etc/docker` folder and add in:\n```json\n{\n  \"hosts\": [\n    \"fd://\",\n    \"unix:///var/run/docker.sock\"\n  ]\n}\n```\n\nCreate a new file `/etc/systemd/system/docker.service.d/docker.conf` with the following contents:\n```\n[Service]\nExecStart=\nExecStart=/usr/bin/dockerd\n```\n\nReload daemon:\n```\nsudo systemctl daemon-reload\n```\n\nRestart docker:\n```\nsudo service docker restart\n```\n\nConnect to docker socket:\n```go\nimport (\n\t\"github.com/chryscloud/go-microkit-plugins/docker\"\n)\ncl := docker.NewSocketClient(docker.Log(g.Log), docker.Host(\"unix:///var/run/docker.sock\"))\n```\nAvailable docker methods:\n```go\nContainersList() ([]types.Container, error)\nContainersListWithOptions(opts types.ContainerListOptions) ([]types.Container, error)\nContainerLogs(containerID string, tailNumberLines int, sinceTimestamp time.Time) (*models.DockerLogs, error)\n\n// ContainerLogsStream streams logs to output channel until done is received. User is responsible to close the passed in channel\nContainerLogsStream(containerID string, output chan []byte, done chan bool) error\n\n// Container CRUD operations\nContainerCreate(name string, config *container.Config, hostConfig *container.HostConfig, networkConfig *network.NetworkingConfig) (*container.ContainerCreateCreatedBody, error)\nContainerStart(containerID string) error\nContainerRestart(containerID string, waitForRestartLimit time.Duration) error\nContainersPrune(pruneFilter filters.Args) (*types.ContainersPruneReport, error)\nContainerStop(containerID string, killAfterTimeout *time.Duration) error\nContainerGet(containerID string) (*types.ContainerJSON, error)\nContainerStats(containerID string) (*types.StatsJSON, error)\nImagesList() ([]types.ImageSummary, error)\nImagePullDockerHub(image, tag string, username, password string) (string, error)\nImageRemove(imageID string) ([]types.ImageDelete, error)\nVolumesPrune(pruneFilter filters.Args) (*types.VolumesPruneReport, error)\nGetDockerClient() *client.Client\nCalculateStats(jsonStats *types.StatsJSON) *models.Stats\nContainerReplace(containerID) error\n```\n\n## Listing all tag versions from DockerHub\n\nInterface\n```go\ntype DockerHub interface {\n\tTags(repostiory string) ([]string, error)\n}\n```\n\nUsage:\n```go\nvar option Option\ncl := NewClient(option)\ntags, err := cl.Tags(\"chryscloud/chrysedgeproxy\")\n```\n\n\n\n# Contributing\n\nPlease read `CONTRIBUTING.md` for details on our code of conduct, and the process of submitting pull requests to us. \n\n# Versioning\n\nCurrent version is initial release - v1.0.0\n\n# Authors\n\n- **Igor Rendulic** - Initial work - [Chrysalis Cloud](https://chryscloud.com)\n\n# License\n\nThis project is licensed under Apache 2.0 License - see the `LICENSE` for details.\n\n\n# Acknowledgments\n\n\u003cp align=\"center\"\u003e\n    \u003cimg src=\"https://storage.googleapis.com/chrysaliswebassets/logo_small.png\" title=\"Chrysalis Cloud\" /\u003e\n\u003cp align=\"center\"\u003e","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchryscloud%2Fgo-microkit-plugins","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchryscloud%2Fgo-microkit-plugins","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchryscloud%2Fgo-microkit-plugins/lists"}