[{"content":"A piece of advice I often see when learning a new language is to read the standard library. So I\u0026rsquo;m taking a tour through go\u0026rsquo;s stdlib in order to get a more comprehensive understanding of the library.\nI\u0026rsquo;ve always liked the site Learn X in Y Minutes, and decided as I went through this to write out functions and code in a similar manner. The blog post has split out the comments into Markdown, but if you just wish to see the code, you can do so on github.\nMuch of this is heavily curated and toured by Jon Bodner via his book Learning Go.\nGetting Started The net/http package reads absolutely beautifully, and the introduction is no different:\n Package http provides HTTP client and server implementations.\n Perfect! Lets dive in by importing some necessary functions from the stdlib, as well as a third party package alice\n// main.go package main import ( \u0026#34;context\u0026#34; \u0026#34;encoding/json\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;log\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;time\u0026#34; \u0026#34;github.com/justinas/alice\u0026#34; ) The Client The client makes HTTP requests, and receives HTTP responses. Let’s take a look at the client definition:\ntype Client struct { Transport RoundTripper CheckRedirect func(req *Request, via []*Request) error Jar CookieJar Timeout time.Duration } There is a default client instance included, providing an empty \u0026amp;http.Client{}, but you should always use your own, as it has no timeout.\nvar dontUseDefault = http.DefaultClient Once you\u0026rsquo;ve created your http.Client, you don\u0026rsquo;t need another one. It\u0026rsquo;s designed to properly handle simultaneous requests.\nfunc newClient() *http.Client { client := \u0026amp;http.Client{ Timeout: 30 * time.Second, } return client } When you want to make a request, you create a new *http.Request instance with the http.NewRequestWithContext function\nfunc newReq() *http.Request { req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, \u0026#34;https://jsonplaceholder.typicode.com/todos/1\u0026#34;, nil) if err != nil { panic(err) } return req } Once you have an *http.Request instance, you can set any headers via the Headers() method of the instance. Once you\u0026rsquo;re done, you can use the Do() method on the *http.Client with your http.Request which returns an http.Response\nfunc makeReq() *http.Response { c := newClient() req := newReq() req.Header.Add(\u0026#34;X-My-Client\u0026#34;, \u0026#34;Learning Go\u0026#34;) resp, err := c.Do(req) if err != nil { panic(err) } return resp } The real work is being done in http.Client.Do() - while there are methods included in the http.Client interface such as Get and Post, those force the usage of http.NewRequest, hindering your ability to pass a context.Context via http.NewRequestWithContext.\nThe *http.Response has several fields with information on the request:\n The status code The text response of the status code The response headers.  The headers type is a map[string][]string with methods Get() and Set() to respectively get and set HTTP headers.\nfunc handleResponse(r *http.Response) { code := r.StatusCode // e.g. 200 \tcodeText := r.Status // e.g. \u0026#34;200 OK\u0026#34; \theaders := r.Header contentType := headers.Get(\u0026#34;Content-Type\u0026#34;) fmt.Printf(\u0026#34;%d\\n%s\\n%s\u0026#34;, code, codeText, contentType) } Response bodies can be used json.Decoder to process REST API responses\nfunc parseJson(r *http.Response) { // the response body is an io.ReadCloser, which means it can be used to parse json \tbody := r.Body var data struct { UserID int `json:\u0026#34;userId\u0026#34;` ID int `json:\u0026#34;id\u0026#34;` Title string `json:\u0026#34;title\u0026#34;` Complete bool `json:\u0026#34;completed\u0026#34;` } err := json.NewDecoder(body).Decode(\u0026amp;data) if err != nil { panic(err) } } The Server The HTTP server is built around the http.Server and the http.Handler interfaces. http.Server listens for HTTP requests. Requests to the server are handled by implementations of http.Handler, which have a single method ServeHTTP\nSince http.Handlers are so important, lets have a look at the interface definition\ntype Handler interface { ServeHTTP(ResponseWriter, *Request) } ServeHTTP takes two arguments - we\u0026rsquo;ve already looked at http.Request, so lets take a look at http.ResponseWriter\ntype ResponseWriter interface { Header() Header Write([]byte) (int, error) WriteHeader(statusCode int) } Lets jump in define our own handler to explore what these methods do.\ntype NewHandler struct{} func (n NewHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // the methods in http.ResponseWriter must be called in a certain order \t// the first, Header(), gives you an instnace of http.Header so you can set \t// any headers you need. \t// If you don\u0026#39;t need it, don\u0026#39;t call it  // Next, call WriteHeader() with the HTTP status code for your response \tw.WriteHeader(http.StatusAccepted) // if you are sending a 200, you can skip it  // Write() sets the body for the response \tw.Write([]byte(\u0026#34;Hello, world!\\n\u0026#34;)) } Now that we have our server, we can make our handler\nfunc newServer() http.Server { s := http.Server{ Addr: \u0026#34;:8000\u0026#34;, // TCP address to listen. host:port - if not provided, listen on all hosts on port 80 \tReadTimeout: 30 * time.Second, // Time to wait to read request headers \tWriteTimeout: 30 * time.Second, // Time to wait for the write of the response \tIdleTimeout: 120 * time.Second, // Time to wait for the next request when keep-alives are enabled \tHandler: NewHandler{}, // We invoke our handler when we get a request to our server \t} return s } The ListenAndServeMethod starts the the HTTP server\nfunc listen(s http.Server) { err := s.ListenAndServe() if err != nil { panic(err) } } The ServeMux The main problem with our server is that it only handles one path, thankfully *http.ServeMux meets the http.Handler interface, and also serves as a router - sending the requests to the correct http.Handler instance, Servemux instances are the most common way of implementing multiple handlers and are critical to any REST API or HTTP server.\nYou can look at the details of how http.ServeMux does this in its ServeHTTP method and its [Handler] method https://cs.opensource.google/go/go/+/refs/tags/go1.17.6:src/net/http/server.go;l=2361 but, essentially, http.ServeMux implements ServeHTTP(w http.ResponseWriter, r *http.Request), satisfying the http.Handler interface, so it can be passed in as a Handler to http.Server. It’s ServeHTTP implementation calls the Handler, which parses the path and returns the http.Handler used for the request, and then calls that handlers ServeHTTP method.\n// That sounded a bit confusing, so let\u0026#39;s test this out func newServeMux() { m := http.NewServeMux() // creates a blank *http.ServeMux  // create two handlers, an alive and ready path \tm.HandleFunc(\u0026#34;/alive\u0026#34;, func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(\u0026#34;ok!\\n\u0026#34;)) }) m.HandleFunc(\u0026#34;ready\u0026#34;, func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(\u0026#34;always\\n\u0026#34;)) }) s := http.Server{ Addr: \u0026#34;:8000\u0026#34;, ReadTimeout: 30 * time.Second, WriteTimeout: 30 * time.Second, IdleTimeout: 120 * time.Second, Handler: m, // Our handler now handles two requests, /alive and /ready \t} s.ListenAndServe() } http.ServeMux instances, since they themselves route requests to http.Handler instances, and since http.ServeMux is itself an instance of a http.Handler interface, an http.ServeMux can handle other http.ServeMux instances. This lets us route nested paths:\nfunc parentChildMux() { user := http.NewServeMux() user.HandleFunc(\u0026#34;/fetch\u0026#34;, func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(\u0026#34;This is a user!\u0026#34;)) }) record := http.NewServeMux() record.HandleFunc(\u0026#34;/fetch\u0026#34;, func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(\u0026#34;This is a record!\u0026#34;)) }) mux := http.NewServeMux() // mux will handle both the user and record path. \t// http.StripPrefix removes the part of the path that\u0026#39;s already been processed, \t// as the previous handles don\u0026#39;t expect the first path \tmux.Handle(\u0026#34;/user/\u0026#34;, http.StripPrefix(\u0026#34;/user\u0026#34;, user)) mux.Handle(\u0026#34;/record\u0026#34;, http.StripPrefix(\u0026#34;/record\u0026#34;, record)) } A small aside, the HandlerFunc In previous examples, you saw implementations of a http.ServeMux that served functions directly, like this:\nfunc handlerFunc() { m := http.NewServeMux() m.HandleFunc(\u0026#34;/path\u0026#34;, func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(\u0026#34;Hello, world!\u0026#34;)) }) The http.HandlerFunc is a function that is also a http.Handler. In this case, I think the type definition is more useful then a description:\n// The HandlerFunc type is an adapter to allow the use of // ordinary functions as HTTP handlers. If f is a function // with the appropriate signature, HandlerFunc(f) is a // Handler that calls f. type HandlerFunc func(ResponseWriter, *Request) // ServeHTTP calls f(w, r). func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r) } Now look at the definition of http.ServeMux.HandleFunc\n// HandleFunc registers the handler function for the given pattern. func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { if handler == nil { panic(\u0026#34;http: nil handler\u0026#34;) } mux.Handle(pattern, HandlerFunc(handler)) } It\u0026rsquo;s more clear what\u0026rsquo;s happening having looked at these two definitions. http.ServeMux.HandleFunc takes a path, and a func(w http.ResponseWriter, r *http.Request) and typecasts the function to a HandlerFunc. Since HandlerFunc implements ServeHTTP, it\u0026rsquo;s a valid http.Handler interface, and can be used in a call to mux.Handle\nOne last pitfall The http library offers functions that work with the package instance of *http.ServeMux, declared as:\nvar dontUseDefaultMux = http.DefaultServeMux These functions are http.ListenAndServe, http.HandleFunc, and http.ListenAndServeTLS. http.ListenAndServe and http.ListenAndServeTLS both serve with the default http server, which as mentioned does not contain properties such as timeouts.\nfunc dontUselistenAndServeDefault() { err := http.ListenAndServe(\u0026#34;:80\u0026#34;, nil) if err != nil { panic(err) } err = http.ListenAndServeTLS(\u0026#34;:80\u0026#34;, \u0026#34;\u0026#34;, \u0026#34;\u0026#34;, nil) } Using these methods can allow third party packages that have added additional handlers to http.DefaultServeMux to inject vulnerabilities\nMiddleware There is no middleware type in go, just a pattern involving http.Handler instances. Here is our example scenario:\nWe want to implement a path /hello which returns Hello, world!. This sounds simple enough, we simply need a http.ServeMux with the path /hello and a handler that writes out our message. However, we want an additional piece of logic to run before responding to our request:\n We want a check to make sure the user has the secret password in header X-Secret-Password before hitting our /hello handler We want a timer that logs the length of time it takes to read and respond to the request.  We can define our two middleware functions like this:\nRequestTimer takes a http.Handler variable h, and returns in itself a http.Handler. What is this handler? It starts a timer, then runs h, our original handler via h.ServeHTTP, and then logs the length of time it took to complete the function.\nfunc RequestTimer(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { start := time.Now() h.ServeHTTP(w, r) end := time.Now() log.Printf(\u0026#34;request time for %ss: %v\u0026#34;, r.URL.Path, end.Sub(start)) }) } TerribleSecurityProvider is slightly more complicated. It takes a password string and returns a function - but what does that function do? That function takes a http.Handler and returns a http.Handler, which reads our X-Secret-Password header, and checks to see if its equal to the password when TerribleSecurityProvider was initially called. If the values, match, it runs ServeHTTP on the handler that was passed in.\nvar securityMsg = []byte(\u0026#34;You didn\u0026#39;t give the secret password\\n\u0026#34;) func TerribleSecurityProvider(password string) func(http.Handler) http.Handler { return func(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Header.Get(\u0026#34;X-Secret-Password\u0026#34;) != password { w.WriteHeader(http.StatusUnauthorized) w.Write(securityMsg) return } h.ServeHTTP(w, r) }) } } In a nutshell, here\u0026rsquo;s how we use ts\nts := TerribleSecurityProvider(\u0026#34;password\u0026#34;) handler := ts(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(\u0026#34;Hello, world\\n\u0026#34;)) })) Now for the implementation! Our mux handles the /hello path with TerribleSecurityProvider, instantiated as ts. As ts is itself a function that takes a handler, we can then pass in RequestTimer to it.\n// If ts passes its security check, it runs h.ServeHTTP, in this case RequestTimer. // If not, it returns, ending the request and breaking the chain. // // If it passes, it calls RequestTimer which itself takes a handler that writes \u0026#34;Hello, World\u0026#34; // RequestTimer starts timing the request, and then runs h.ServeHTTP, ultimately serving \u0026#34;Hello, world\\n\u0026#34; // on path /hello func muxWithMiddleware() { ts := TerribleSecurityProvider(\u0026#34;PASSWORD\u0026#34;) mux := http.NewServeMux() mux.Handle(\u0026#34;/hello\u0026#34;, ts(RequestTimer( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(\u0026#34;Hello, world!\\n\u0026#34;)) })))) } Third party packages: Perhaps it’s just me, but chaining middleware can be a confusing pattern. Alice makes chaining middleware as simple as:\nfunc helloWorldHandler(w http.ResponseWriter, r *http.Request) { w.Write([]byte(\u0026#34;hello, world!\u0026#34;)) } func timeoutHandler(h http.Handler) http.Handler { return http.TimeoutHandler(h, 1*time.Second, \u0026#34;timed out\u0026#34;) } func aliceWithMiddleWare() { ts := TerribleSecurityProvider(\u0026#34;PASSWORD\u0026#34;) handler := http.HandlerFunc(helloWorldHandler) a := alice.New(ts, timeoutHandler).Then(handler) s := http.Server{ Addr: \u0026#34;:8000\u0026#34;, ReadTimeout: 30 * time.Second, WriteTimeout: 30 * time.Second, IdleTimeout: 120 * time.Second, Handler: a, } s.ListenAndServe() } Gorilla is too big to cover here but in a nutshell it has a mux package which allows easy use of creating dynamic paths such as /user/{user_id}/name\nWrapping Up Honestly, reading the go stdlib is an incredibly fun, and informative process. Next stop, io\n","permalink":"https://thorntonmc.github.io/posts/go/2022-01-17-net-http/","summary":"A piece of advice I often see when learning a new language is to read the standard library. So I\u0026rsquo;m taking a tour through go\u0026rsquo;s stdlib in order to get a more comprehensive understanding of the library.\nI\u0026rsquo;ve always liked the site Learn X in Y Minutes, and decided as I went through this to write out functions and code in a similar manner. The blog post has split out the comments into Markdown, but if you just wish to see the code, you can do so on github.","title":"Reading the Go Standard Library - net/http"},{"content":"One thing I\u0026rsquo;ve been tasked with working on recently is splitting out microservices from a monorepo into their own separate repos. While this may sound trivial, one thing that is essential is to keep the git history associated with these services - which is more complicated than it looks. There\u0026rsquo;s a native tool built into git, git filter-branch - but it is quite finicky.\nThankfully, a tool exists which offers a one command solution - git filter repo. In fact, the man page for git filter-branch even recommends it be used as the tool of choice.\nA Note for MacOS Users You may receive an error such as\nfatal: cannot lock ref 'refs/heads/branchname': Unable to create '/Users/your_user_name/your_repo/.git/refs/heads/your_branch.lock': File exists. Another git process seems to be running in this repository, e.g. an editor opened by 'git commit'. Please make sure all processes are terminated then try again. If it still fails, a git process may have crashed in this repository earlier: remove the file manually to continue. git update-ref failed; see above For me, this was because there were two branches with the same name, but with different casing. Since MacOS ships with a default of a non-case sensitive filesystem, I needed to run this in a Linux VM.\nPrerequisites Per the docs, git filter-repo requires:\n git \u0026gt;= 2.22.0 at a minimum; some features require git \u0026gt;= 2.24.0 or later\n However, I just updated to the newest version of git. If you can\u0026rsquo;t update to this natively (I had issues on Ubuntu 18.04), you can do the following:\nsudo add-apt-repository ppa:git-core/ppa sudo apt update sudo apt install git Next, install git filter repo - if you\u0026rsquo;re on macOS you can simply brew install git filter repo, if you\u0026rsquo;re on Linux, depending on your distro, you may have to build from source - here\u0026rsquo;s a slightly modified version of the instructions taken from the INSTALL.md\ngit clone https://github.com/newren/git-filter-repo cd git-filter-repo cp -a git-filter-repo $(git --exec-path) cp -a git-filter-repo.1 $(git --man-path)/man1 \u0026amp;\u0026amp; mandb cp -a git-filter-repo.html $(git --html-path) ln -s $(git --exec-path)/git-filter-repo \\ $(python -c \u0026quot;import site; print(site.getsitepackages()[-1])\u0026quot;)/git_filter_repo.py Performing The Initial Split First, I recommend cloning a fresh copy of your repo as we don\u0026rsquo;t want to mess up any version you may actually be working in\ngit clone https://github.com/${your_repo} -o your_repo_filter_clone Next, in the freshly cloned repo, you can simply run\ngit filter-repo --subdirectory-filter ${/YOUR/BROKEN/OUT/DIR} This command takes everything in /your/broken/out/dir, puts it in the root directory, rewriting the commits so that the only thing that now exists in this repo are the contents of the directory, along with only the commit history of that directory. Neat!\nNow all that\u0026rsquo;s left to do is set up your remote instance, but before we do that\u0026hellip;\nIf you need to rename directories It’s best to do this before committing anything to remote repositories – you can do this by simply using\ngit filter-repo --path-rename ${original_path}:${new_path} For example, as I break out these microservices, I\u0026rsquo;d like to restructure them according to the standard go project layout - here\u0026rsquo;s an instance of me renaming the app directory to what should be the internal directory:\ngit filter-repo --path-rename app/my-app.go:internal/my-app.go This renames, and rewrites the history for app/smoke-test.go under internal/smoke-test.go\nSyncing your new repo back to the remote To sync the repo back to GitHub, or whatever you\u0026rsquo;re using, you can simply set the remote. git filter-repo removes the remote origin, so you shouldn\u0026rsquo;t be able to commit back to the repo you cloned it from by accident:\n git remote add origin https://github.com/${YOUR_ORG}/${YOUR_NEW_REPO}.git git branch -M main git push ","permalink":"https://thorntonmc.github.io/posts/misc/2022-01-12-splitting-out-git-repos/","summary":"One thing I\u0026rsquo;ve been tasked with working on recently is splitting out microservices from a monorepo into their own separate repos. While this may sound trivial, one thing that is essential is to keep the git history associated with these services - which is more complicated than it looks. There\u0026rsquo;s a native tool built into git, git filter-branch - but it is quite finicky.\nThankfully, a tool exists which offers a one command solution - git filter repo.","title":"Split out a Git Repo and Keep it's Commit History"},{"content":"Introduction There was a thread on the devops subreddit going around about a interview challenge for a junior SRE position. Considering I\u0026rsquo;ve been doing more work that might be classified under this role, I figured I\u0026rsquo;d give it a try. Lets take a look at the README.md, I\u0026rsquo;ve reworked it a bit for brevity, you can see the original on github, along with all of the code described in this post:\n   Using Go, write a simple application, http server, which serves 2 endpoints:\n /requests, which increments the hits in in redis and additionally returns hello, world to the caller “/queries” will return the number of hits recorded in redis    Build a helm chart of the http server you wrote. Explain which kind of k8s resource you picked and why? Add liveness and readiness Expose the service port to 3030 Install kind to test you application and install the helm chart\n  Build a github actions file that triggers a build on specific wildcard PR (build) and one merge to master and create tags, which builds your container, run unit tests, and saves to the registry with the correct version.\n   There were originally some other items which I won\u0026rsquo;t be tackling at this time (I won\u0026rsquo;t be terraforming to EKS or GCP, though I did use terraform as my original deployment)\nWriting the HTTP Server Let\u0026rsquo;s get started and write our HTTP server so we can get into the fun stuff, starting with our basic struct:\ntype App struct { listenAddr string mux *http.ServeMux rdb *redis.Client } This is pretty straightforward, our App does three things: it listens on a desired address and port, routes paths via a ServeMux, and gets and sets our redis cache. The rest of this article will be implementing this functionality.\nConfig Options While this is a fairly barebones API, there is some level of configuration we should pass; namely the redis address and password, and the port we should listen on. Let\u0026rsquo;s define our struct:\ntype Config struct { port int redisAddress string redisPassword string } In this example, we\u0026rsquo;ll simply set the value from our enviornment variables, so our init function is quite simple\nfunc NewConfigFromEnv() Config { return newConfigFromEnv() } func newConfigFromEnv() Config { portOverride, ok := os.LookupEnv(\u0026#34;APP_PORT\u0026#34;) if ok { portOverride, err := strconv.Atoi(portOverride) if err != nil { log.Fatalf(\u0026#34;Failed to parse port override\u0026#34;) } port = portOverride } redisAddressOverride, ok := os.LookupEnv(\u0026#34;REDIS_ADDRESS\u0026#34;) if ok { redisAddress = redisAddressOverride } redisPortOverride, ok := os.LookupEnv(\u0026#34;REDIS_PORT\u0026#34;) if ok { log.Printf(\u0026#34;have redisPortOverride %s\u0026#34;, redisPortOverride) redisPortOverride, err := strconv.Atoi(redisPortOverride) if err != nil { log.Fatalf(\u0026#34;Failed to parse redis port override\u0026#34;) } redisPort = redisPortOverride } return Config{ port: port, redisAddress: fmt.Sprintf(\u0026#34;%s:%d\u0026#34;, redisAddress, redisPort), redisPassword: redisPassword, } } Creating the Redis Client Our application needs a redis client so that it can get/set keys upon request, so let\u0026rsquo;s create this in a new file redis.go\nfunc NewRedisClient(addr, password string) *redis.Client { log.Printf(\u0026#34;configuring redis at %s\u0026#34;, addr) rdb := redis.NewClient(\u0026amp;redis.Options{ Addr: addr, Password: password, DB: 0, }) return rdb } We\u0026rsquo;ll also want some functions to get/set our \u0026ldquo;hits\u0026rdquo; key, so let\u0026rsquo;s add them too:\nfunc (a *App) setHits(val string) error { err := a.rdb.Set(context.Background(), \u0026#34;hits\u0026#34;, val, 0).Err() if err != nil { return err } return nil } func (a *App) getHits() (string, error) { hits, err := a.rdb.Get(context.Background(), \u0026#34;hits\u0026#34;).Result() if err != nil { return \u0026#34;\u0026#34;, err } return hits, err } The requirement states that we need to either create the hits key or increment it each call to /requests, so we\u0026rsquo;ll create that logic as well:\nfunc (a *App) updateHits() error { var hits string hits, err := a.getHits() if err == redis.Nil { // key does not exist, so set it to 1 \terr := a.setHits(\u0026#34;1\u0026#34;) if err != nil { return err } return nil } if err != nil { return err } return a.incrementHits(hits) // increment the current value } func (a *App) incrementHits(hits string) error { hitsInt, err := strconv.Atoi(hits) if err != nil { return err } hitsInt += 1 hits = strconv.Itoa(hitsInt) return = a.setHits(hits) } Routing Requests The last thing our application needs a serveMux in order to route our requests. We need to handle two routes /requests and /queries \u0026ndash; since these routes will utilize the redis connection created as a part of our application, we\u0026rsquo;ll make them methods of our app:\nfunc (a *App) handleRequests(w http.ResponseWriter, r *http.Request) { err := a.updateHits() if err != nil { log.Println(err.Error()) w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) return } w.WriteHeader(http.StatusOK) w.Write([]byte(\u0026#34;Hello, World\\n\u0026#34;)) } func (a *App) handleQueries(w http.ResponseWriter, r *http.Request) { hits, err := a.getHits() if err == redis.Nil { w.Write([]byte(\u0026#34;0\\n\u0026#34;)) w.WriteHeader(http.StatusOK) return } if err != nil { w.Write([]byte(err.Error())) w.WriteHeader(http.StatusInternalServerError) return } w.Write([]byte(hits)) w.WriteHeader(http.StatusOK) } Now our app is ready to be used! Let\u0026rsquo;s throw in an init method so that we can easily run it from our calling function, and we also need a simple method to actually start the http server:\n// NewApp returns a new bare application with a webserver configured and ready func NewApp(c Config) *App { return newApp(c) } func newApp(c Config) *App { app := \u0026amp;App{} mux := http.NewServeMux() mux.HandleFunc(\u0026#34;/requests\u0026#34;, app.handleRequests) mux.HandleFunc(\u0026#34;/queries\u0026#34;, app.handleQueries) app.mux = mux app.rdb = NewRedisClient(c.redisAddress, c.redisPassword) app.listenAddr = fmt.Sprintf(\u0026#34;:%d\u0026#34;, port) return app } func (a *App) Serve() { log.Printf(\u0026#34;Listening on %s\u0026#34;, a.listenAddr) log.Fatal(http.ListenAndServe(a.listenAddr, a.mux)) } Excellent! Now our app can be initialized, and the listener can be started simply by importing the package! This is the last missing piece of this puzzle. To run the application from the command line easily, I used the cobra package. The usage readme and user guide are helpful with getting setup, but in a nutshell, cobra creates a main.go in your root directory and also creates cmd/root.go. main.go then imports cmd/root.go which contains all the information around how your program handles flags and commands. Ours is simple, as we can see:\nmain.go\npackage main import ( \u0026#34;github.com/thorntonmc/sre-challenge/cmd\u0026#34; ) func main() { cmd.Execute() } cmd/root.go\nimport ( \u0026#34;fmt\u0026#34; \u0026#34;os\u0026#34; app \u0026#34;github.com/thorntonmc/sre-challenge/internal\u0026#34; \u0026#34;github.com/spf13/cobra\u0026#34; \u0026#34;github.com/spf13/viper\u0026#34; ) // This file also contains the scaffolding for your cli application, but what\u0026#39;s important is what is in rootCmd var rootCmd = \u0026amp;cobra.Command{ Use: \u0026#34;sre-challenge\u0026#34;, Short: \u0026#34;Junior SRE Interview\u0026#34;, Long: \u0026#34;Junior SRE Interview\u0026#34;, Run: func(cmd *cobra.Command, args []string) { c := app.NewConfigFromEnv() a := app.NewApp(c) a.Serve() }, } In a nutshell, cobra will run what\u0026rsquo;s inside rootCmd when the application is run with no parameters, which is what we want as there aren\u0026rsquo;t any flags that are used in this program. The Run function is very simple, we simply initialize our config via the package we wrote earlier, then create a new application via NewApp, and then run our server.\nNow we\u0026rsquo;re ready to test it out! Since we want to keep our a build, tests, and deploys idempotent, lets keep our habits in check by putting our build steps in a Makefile\n./Makefile\n.DEFAULT_GOAL := build # default to the build target MAKEFLAGS= -s . # makefile is silent BUILD_DIR=./build fmt: go fmt ./ build: fmt go build -o $(BUILD_DIR)/sre-challenge.go ./main.go install: fmt build go install clean: rm -rf $(BUILD_DIR) Cool! Now we when we want to build our application we can simply type make build and the binary will be created in ./build/sre-challenge.go \u0026ndash; and since we\u0026rsquo;ve also set build to be our default goal, we can actually just type make to build our application.\nThe install target will also install our binary to $GOPATH/bin\nmichaelthornton@work:~/sre-challenge [main] $ make install michaelthornton@work:~/sre-challenge [main] $ ls $GOPATH/bin cobra cr go-outline gopls sre-challenge So now we can run our app! Let\u0026rsquo;s try it out\nmichaelthornton@work:~/sre-challenge [main] $ sre-challenge 2022/01/11 09:56:45 configuring redis at redis.sre-challenge:6379 2022/01/11 09:56:45 Listening on :8080 This is great! However since we have no redis server installed - the server is basically useless. In our next post, we\u0026rsquo;ll deploy this application and a redis cache to a local kubernetes cluster.\n","permalink":"https://thorntonmc.github.io/posts/sre-challenge/2022-01-09-sre-challenge/","summary":"Introduction There was a thread on the devops subreddit going around about a interview challenge for a junior SRE position. Considering I\u0026rsquo;ve been doing more work that might be classified under this role, I figured I\u0026rsquo;d give it a try. Lets take a look at the README.md, I\u0026rsquo;ve reworked it a bit for brevity, you can see the original on github, along with all of the code described in this post:","title":"A Reddit SRE Challenge - Part 1"}]