From 29fb4daa361e37b1cbb66bb83b8ab71d53487ffb Mon Sep 17 00:00:00 2001 From: Edwin Wong Date: Fri, 18 Dec 2020 11:14:55 +0800 Subject: [PATCH 1/3] Delete 'user default on' in redis config --- storage/redis/kubernetes/redis/redis-configmap.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/storage/redis/kubernetes/redis/redis-configmap.yaml b/storage/redis/kubernetes/redis/redis-configmap.yaml index ec0884d..4384ee3 100644 --- a/storage/redis/kubernetes/redis/redis-configmap.yaml +++ b/storage/redis/kubernetes/redis/redis-configmap.yaml @@ -1839,4 +1839,3 @@ data: # Set bgsave child process to cpu affinity 1,10,11 # bgsave_cpulist 1,10-11 # Generated by CONFIG REWRITE - user default on #fd2729bf2f67ef4a0941d633dd9055aebae62734b63505a4937e98ca1ecbcbad ~* +@all From 776e42da90fee0d0cb511cbf44b200b5ececeee8 Mon Sep 17 00:00:00 2001 From: marcel-dempers Date: Fri, 25 Dec 2020 07:28:53 +1100 Subject: [PATCH 2/3] go intro --- golang/introduction/app/app.go | 36 +++ golang/introduction/app/customers.go | 28 ++ golang/introduction/app/go.mod | 3 + golang/introduction/dockerfile | 13 + golang/introduction/readme.md | 429 +++++++++++++++++++++++++++ 5 files changed, 509 insertions(+) create mode 100644 golang/introduction/app/app.go create mode 100644 golang/introduction/app/customers.go create mode 100644 golang/introduction/app/go.mod create mode 100644 golang/introduction/dockerfile create mode 100644 golang/introduction/readme.md diff --git a/golang/introduction/app/app.go b/golang/introduction/app/app.go new file mode 100644 index 0000000..e0d9d0a --- /dev/null +++ b/golang/introduction/app/app.go @@ -0,0 +1,36 @@ +package main + +import ( + "fmt" +) + +func main() { + + customers := GetCustomers() + + for _, customer := range customers { + //we can access the "customer" variable in this approach + fmt.Println(customer) +} + +} + +func getData() (customers []string) { + + customers = []string{ "Marcel Dempers", "Bob Smith", "John Smith"} + + customers = append(customers, "Ben Spain") + customers = append(customers, "Aleem Janmohamed") + customers = append(customers, "Jamie le Notre") + customers = append(customers, "Victor Savkov") + customers = append(customers, "P The Admin") + customers = append(customers, "Adrian Oprea") + customers = append(customers, "Jonathan D") + + for _, customer := range customers { + + fmt.Println(customer) + } + + return customers +} \ No newline at end of file diff --git a/golang/introduction/app/customers.go b/golang/introduction/app/customers.go new file mode 100644 index 0000000..2865711 --- /dev/null +++ b/golang/introduction/app/customers.go @@ -0,0 +1,28 @@ +package main + +type ( + Customer struct { + FirstName string + LastName string + FullName string + } +) + +func GetCustomers()(customers []Customer) { + + marcel := Customer{ FirstName: "Marcel", LastName: "Dempers"} + + customers = append(customers, marcel, + Customer{ FirstName: "Ben", LastName: "Spain" }, + Customer{ FirstName: "Aleem", LastName: "Janmohamed" }, + Customer{ FirstName: "Jamie", LastName: "le Notre" }, + Customer{ FirstName: "Victor", LastName: "Savkov" }, + Customer{ FirstName: "P", LastName: "The Admin" }, + Customer{ FirstName: "Adrian", LastName: "Oprea" }, + Customer{ FirstName: "Jonathan", LastName: "D" }, + + ) + + return customers + +} \ No newline at end of file diff --git a/golang/introduction/app/go.mod b/golang/introduction/app/go.mod new file mode 100644 index 0000000..853db6b --- /dev/null +++ b/golang/introduction/app/go.mod @@ -0,0 +1,3 @@ +module github.com/docker-development-youtube-series/golang/introdouction/app + +go 1.15 diff --git a/golang/introduction/dockerfile b/golang/introduction/dockerfile new file mode 100644 index 0000000..539283d --- /dev/null +++ b/golang/introduction/dockerfile @@ -0,0 +1,13 @@ +FROM golang:1.15 as dev + +WORKDIR /work + +FROM golang:1.15 as build + +WORKDIR /app +COPY ./app/* /app/ +RUN go build -o app + +FROM alpine as runtime +COPY --from=build /app/app / +CMD ./app \ No newline at end of file diff --git a/golang/introduction/readme.md b/golang/introduction/readme.md new file mode 100644 index 0000000..3261b4a --- /dev/null +++ b/golang/introduction/readme.md @@ -0,0 +1,429 @@ +# Introduction to Learning Go + +Go can be downloaded from [golang.org](https://golang.org/doc/install)
+ +Test your `go` installation: + +``` +go version +``` + +# Run Go in Docker + +We can also run go in a small docker container:
+ +``` +cd golang\introduction + +docker build --target dev . -t go +docker run -it -v ${PWD}:/work go sh +go version + +``` + +# Code Structure + +https://golang.org/doc/code.html + +* Package: + - Source files in same directory that are compiled together + - Have visibility on all source files in the same package + +* Modules: + - Collection of packages that are released together + +Our repository can contain one or more go modules, but usually 1. + - At the root of the repo + +`go.mod` Declares module path + import path for packages. (Where to download them) + - When we write our own program, we can define a module path + - This allows us to publish our code (if we want), so others can download it + - The module path could be something like `github.com/google/go-cmp` + - Makes it easy for other programs to consume our module + +# Our first Program + +* Create a folder containing our application + +``` +mkdir app + +``` + +* Define a module path (github.com/docker-development-youtube-series/golang/introdouction/app) + +``` +# change directory to your application source code + +cd app + +# create a go module file + +go mod init github.com/docker-development-youtube-series/golang/introdouction/app + +``` + +* Create basic source code + +In the `app` folder, create a program called `app.go` +Paste the following content into it. + +``` +package main + +import "fmt" + +func main() { + fmt.Println("Hello, world.") +} +``` + +* Run your application code + +You can run your application + +``` +go run app.go +``` + +# Building our Program + +Build your application into a static binary:
+ +``` +go build +``` + +This will produce a compiled program called `app` +You can run this program easily: + +``` +./app +``` + +# Install your application (optional) + +"This command builds the app command, producing an executable binary.
+It then installs that binary as $HOME/go/bin/app (or, under Windows, %USERPROFILE%\go\bin\app.exe)" + +``` +go install github.com/docker-development-youtube-series/golang/introdouction/app +``` + +# The Code + +## Functions + +In the video we cover writing functions.
+It allows us to execute a block of code
+You want to give your function a single purpose
+Functions can have an input and return an output
+Well thought out functions makes it easier to write tests
+ +Instead of doing a boring `x + y` function that adds two numbers, let's do something a little +more realistic but still basic: + +``` +// This function returns some data +// The data could be coming from a database, or a file. +// The person calling the function should not care +// Since the function does not leak its Data provider + +func getData(inputs)(outputs){ +} + +// functions can take multiple inputs, and return multiple outputs + +// lets say we have 1) customers and 2) the cities they are from +// we may want to 1) get a list of customers and 2) get a list of cities +// therefore we have 2 types of data, 1) customers 2) cities +// let's improve our function so its gets data based on the type + +func getData(customerId int) (customer string) { +} +``` + +## Variables + +To hold data in programming languages, we use variables.
+Variables take up space in memory, so we want to keep it minimal.
+Let's declare variables in our function + +``` +func getData(customerId int) (customer string) { + var firstName = "Marcel" + lastName := "Dempers" + + fullName := firstName + " " + lastName + return fullName + + //or we can return the computation instead of adding another variable! + return firstName + " " + lastName + + //or we dont even need to declare variables :) + return "Marcel Dempers" + +} + +``` + +## Control Flows (if\else) + +You can see we're not using the `customerId` input in our function.
+Let's use it!
+ +Control flows allow us to add "rules" to our code.
+"If this is the case, then do that, else do something else". + +So let's say we have a customer ID 1 coming in, we may only want to +return our customer if it matches the `customerId` + +``` + +func getData(customerId int) (customer string) { + + if customerId == 1 { + return "Marcel Dempers" + } else if customerId == 2 { + return "Bob Smith" + } else { + return "" + } + +} + + +``` + +Let's invoke our function : + +``` +//in the main() function + +//get our customer +customer := getData(1) +fmt.Println(customer) + +//get the wrong customer +customer := getData(3) +fmt.Println(customer) + + +``` +## Arrays + +At the moment, we can only return 1 customer at a time on our function.
+Realisticly we need the ability to return more data, not just a single customer.
+ +Arrays allow us to make a collection of variables of the same type.
+We can now return a list of customers!
+ +Let's change our function to get an array of customers! + +``` +func getData() (customers [2]string) { + //create 1 record + customer := "Marcel Dempers" + + //assign our customer to the array + customers[0] = customer + + //OR we can assign it like this + customers[1] = "Bob Smith" + + //send it back to the caller + return customers + +} + +``` + +Now we also have to change our calling function to expect an array: + +``` +customers := getData() + +fmt.Println(customers) + +``` + +## Slices + +Since arrays are fixed size, Slices are a dynamically-sized view into arrays. +Let's create a slice instead of array so we can add customers dynamically! + +``` +func getData() (customers []string) { + + //initialise our slice of type string + customers = []string{ "Marcel Dempers", "Bob Smith", "John Smith"} + + //add more legendary customers dynamically + customers = append(customers, "Ben Spain") + customers = append(customers, "Aleem Janmohamed") + customers = append(customers, "Jamie le Notre") + customers = append(customers, "Victor Savkov") + customers = append(customers, "P The Admin") + customers = append(customers, "Adrian Oprea") + customers = append(customers, "Jonathan D") + + //send it back to the caller + return customers + +} + + +``` + +## Loops + +Loops are used to iterate over collections, lists, arrays etc.
+Let's say we need to loop through our customers + +In the `main()` function, we can grab the list of customers and loop them. +In this demo, we'll cover a basic for loop, but there are several approaches to writing loops. + +``` +//loop forever +for { + //any code in here will run forever! + + fmt.Println("Infinite Loop 1") + time.Sleep(time.Second) + + //unless we break out the loop like this + break +} + +//loop for x number of loops +for x := 0; x < 10; x++ { + + //any code in here will run 10 times! (unless we break!) + fmt.Println(customers[x]) + +} + +//loop for ALL our customer + +for x, customer := range customers { + + //we can access the "customer" variable in this approach + customer = customers[x] + fmt.Println(customer) + + //OR + //we can use the supplied customer from the loop + // and silence the x variable, replace it with a _ character + fmt.Println(customer) +} + +``` + +## Structs + +So far so good, however, customer data is not useful as strings.
+Customers can have a firstname, lastname, and more properties.
+ +For this purpose we'd like to group some variables into a single variable.
+This is what `struct` allows us to do.
+Let's create a `struct` for our customer + +Let's create a new `go` file called `customers.go` + +``` +package main + +Customer struct { + FirstName string + LastName string + FullName string +} + +``` + +Let's put it all together: + +In `customers.go`, let's create a function to get customers + +``` +func GetCustomers()(customers []Customer) { + + //we can declare customers like this: + marcel := Customer{ FirstName: "Marcel", LastName: "Dempers" } + + customers = append(customers, + Customer{ FirstName: "Marcel", LastName: "Dempers" }, + Customer{ FirstName: "Ben", LastName: "Spain" }, + Customer{ FirstName: "Aleem", LastName: "Janmohamed" }, + Customer{ FirstName: "Jamie", LastName: "le Notre" }, + Customer{ FirstName: "Victor", LastName: "Savkov" }, + Customer{ FirstName: "P", LastName: "The Admin" }, + Customer{ FirstName: "Adrian", LastName: "Oprea" }, + Customer{ FirstName: "Jonathan", LastName: "D" }, + ) + + return customers + +} +``` + +In `main()` we can now call our shiny new function + + +``` +customers := GetCustomers() + +for _, customer := range customers { + //we can access the "customer" variable in this approach + fmt.Println(customer) +} +``` + +# Docker + +For our dev environment, we have a simple image using `go`
+We also set a work directory and alias the target as `dev` + +This means we can use this container layer as a development environment.
+Later down the track we can add debuggers in here for example.
+Checkout my debugging video for go: https://youtu.be/kToyI16IFxs
+ + +## Development environment + +``` +FROM golang:1.15 as dev + +WORKDIR /work +``` + +## Building our code + +``` +FROM golang:1.15 as build + +WORKDIR /app +COPY ./app/* /app/ +RUN go build -o app +``` + +## The Runtime + +``` +FROM alpine as runtime +COPY --from=build /app/app / +CMD ./app +``` + +## Building the Container + +``` +docker build . -t customer-app + +``` + +## Running the Container + +``` +docker run customer-app +``` From 75e20b239f8848fb477925500a728d24acfe908d Mon Sep 17 00:00:00 2001 From: marcel-dempers Date: Mon, 4 Jan 2021 21:17:54 +1100 Subject: [PATCH 3/3] part 1,2,3 --- golang/introduction/dockerfile | 4 +- golang/introduction/part-2.json/dockerfile | 3 + golang/introduction/part-2.json/readme.md | 324 +++++++++++++ golang/introduction/part-2.json/videos/go.mod | 3 + .../introduction/part-2.json/videos/main.go | 17 + .../part-2.json/videos/videos-updated.json | 16 + .../introduction/part-2.json/videos/videos.go | 44 ++ .../part-2.json/videos/videos.json | 16 + golang/introduction/part-3.http/dockerfile | 14 + golang/introduction/part-3.http/go.mod | 3 + golang/introduction/part-3.http/readme.md | 433 ++++++++++++++++++ .../introduction/part-3.http/videos/main.go | 54 +++ .../part-3.http/videos/videos-updated.json | 16 + .../introduction/part-3.http/videos/videos.go | 46 ++ .../part-3.http/videos/videos.json | 16 + golang/introduction/readme.md | 2 +- 16 files changed, 1008 insertions(+), 3 deletions(-) create mode 100644 golang/introduction/part-2.json/dockerfile create mode 100644 golang/introduction/part-2.json/readme.md create mode 100644 golang/introduction/part-2.json/videos/go.mod create mode 100644 golang/introduction/part-2.json/videos/main.go create mode 100644 golang/introduction/part-2.json/videos/videos-updated.json create mode 100644 golang/introduction/part-2.json/videos/videos.go create mode 100644 golang/introduction/part-2.json/videos/videos.json create mode 100644 golang/introduction/part-3.http/dockerfile create mode 100644 golang/introduction/part-3.http/go.mod create mode 100644 golang/introduction/part-3.http/readme.md create mode 100644 golang/introduction/part-3.http/videos/main.go create mode 100644 golang/introduction/part-3.http/videos/videos-updated.json create mode 100644 golang/introduction/part-3.http/videos/videos.go create mode 100644 golang/introduction/part-3.http/videos/videos.json diff --git a/golang/introduction/dockerfile b/golang/introduction/dockerfile index 539283d..4d9e3d3 100644 --- a/golang/introduction/dockerfile +++ b/golang/introduction/dockerfile @@ -1,8 +1,8 @@ -FROM golang:1.15 as dev +FROM golang:1.15-alpine as dev WORKDIR /work -FROM golang:1.15 as build +FROM golang:1.15-alpine as build WORKDIR /app COPY ./app/* /app/ diff --git a/golang/introduction/part-2.json/dockerfile b/golang/introduction/part-2.json/dockerfile new file mode 100644 index 0000000..6022558 --- /dev/null +++ b/golang/introduction/part-2.json/dockerfile @@ -0,0 +1,3 @@ +FROM golang:1.15-alpine as dev + +WORKDIR /work diff --git a/golang/introduction/part-2.json/readme.md b/golang/introduction/part-2.json/readme.md new file mode 100644 index 0000000..d93645d --- /dev/null +++ b/golang/introduction/part-2.json/readme.md @@ -0,0 +1,324 @@ +# Introduction to Go: JSON + +In programming languages, you will very often deal with data structures internally.
+Sometimes, you need to pass data outside of your application or read data from another application, or even a file.
+ +API, for example often expose data in `json`, `xml`, `grpc` and all sorts of formats. +Before we can really take a look at building APIs and even storing data in databases, +we need some fundamental knowledge about how to convert these data structures, into structures +that our application can understand.
+ +In part 1, we dealt with [Variables]("https://tour.golang.org/basics/8") and more importantly,
we dealt with `struct` data type, which made us build a `customer` object. + +If we wanted to pass the `customer` to a database, or an external system, it's often required that we convert this to `json` format. + + +## Dev Environment + +The same as Part 1, we start with a [dockerfile](./dockerfile) where we declare our version of `go`. + +``` +cd golang\introduction\part-2.json + +docker build --target dev . -t go +docker run -it -v ${PWD}:/work go sh +go version +``` + +## Create our App + +Create a new directory that holds defines our `repository` and holds our `module` + +``` +mkdir videos + +``` + +* Define a module path + +``` +# change directory to your application source code + +cd videos + +# create a go module file + +go mod init videos + +``` + +## Create our base code + +We start out all our applications with a `main.go` defining our `package`, declaring our `import` dependencies
+and our entrypoint `main()` function + +``` +package main + +import "fmt" + +func main() { + fmt.Println("Hello, world.") +} +``` + +## Create our Videos app + +Firstly, we create a seperate code file `videos.go` that deals with our YouTube videos
+ +Similar to Part 1, we define the file, we define what a video looks like using a `struct`
+and a function for returning a slice of videos. + + +``` +package main + +import () + +type video struct { + Id string + Title string + Description string + Imageurl string + Url string +} + +func getVideos()(videos []video){ + //Get our videos here, + //and return them + return videos +} + + +``` + +## Populating our Video Struct + +Similar to our customers app, we created a `struct` and populated it with data: +Let's add the following to our `getVideos()` function: + +``` +video1 := video{ + Id : "eyvLwK5C2dw", + Title : "Kubernetes on Azure", + Imageurl : "https://i.ytimg.com/vi/eyvLwK5C2dw/mqdefault.jpg?sqp=CISC_PoF&rs=AOn4CLDo7kizrJozB0pxBhxL9JbyiW_EPw", + Url : "https://youtu.be/eyvLwK5C2dw", + Description : "", +} + +video2 := video{ + Id : "QThadS3Soig", + Title : "Kubernetes on Amazon", + Imageurl : "https://i.ytimg.com/vi/QThadS3Soig/sddefault.jpg", + Url : "https://youtu.be/QThadS3Soig", + Description : "", +} + +return []video{ video1, video2} + +``` + +We need to also invoke our function that will return the videos +Don't worry about where the videos will come from.
+We always start with the building blocks and move on from there
+ +In our `main()` function:
+ +``` +func main() { + videos := getVideos() + fmt.Println(videos) +} +``` + +## Files + +Now, Ideally, we do not want to "hard code" our videos like this.
+Currently, our videos are embedded into the code and we can only return 2 videos.
+If we want to introduce a new video, we have to rebuild the application.
+ +In the real world, videos are our data.
+Data lives outside of the application. Like in a database. + +Technically, we can use a database, but that is too big of a step for now.
+So let's start with a file instead. + +### Introducing ioutil + +It's important to learn how to navigate and read go documentation. +Let's go to https://golang.org/pkg/io/ioutil/ + +We can import this library since its part of the Go standard library set. + +``` +import ( + "io/ioutil" +) +``` + +Let's move our videos into a `json` file called `videos.json` : + +``` +[ + { + "id" : "QThadS3Soig", + "title" : "Kubernetes on Amazon", + "imageurl" : "https://i.ytimg.com/vi/QThadS3Soig/sddefault.jpg", + "url" : "https://youtu.be/QThadS3Soig", + "description" : "" + }, + { + "id" : "eyvLwK5C2dw", + "title" : "Kubernetes on Azure", + "imageurl" : "https://i.ytimg.com/vi/eyvLwK5C2dw/mqdefault.jpg?sqp=CISC_PoF&rs=AOn4CLDo7kizrJozB0pxBhxL9JbyiW_EPw", + "url" : "https://youtu.be/eyvLwK5C2dw", + "description" : "" + } +] + +``` + +## Reading a file + +Let's take another look at https://golang.org/pkg/io/ioutil/
+Notice the `ReadFile` function +We can read a file from disk: + +``` + fileBytes, err := ioutil.ReadFile("./videos.json") + + if err != nil { + panic(err) + } + + fileContent := string(fileBytes) + fmt.Println(fileContent) + +``` + +## Panic + +Also notice the function `panic`.
+ +"A panic typically means something went unexpectedly wrong. Mostly we use it to fail fast on errors that shouldn’t occur during normal operation, or that we aren’t prepared to handle gracefully." +- https://gobyexample.com/panic + +For now, we will panic on every potential error.
+In a future video, we'll cover Error handling in more depth + +## JSON + +Working with JSON is pretty straightforward in go.
+`struct` objects are pretty well synergized with `json` + +Let's take a look at the `json` package that is part of go. +https://golang.org/pkg/encoding/json/ + +``` +"encoding/json" +``` + +This package allows us to marshal and unmarshal JSON.
+This allows conversion between a `struct` or a go type, and `json` + + +## Unmarshal + +From JSON(bytes) ==> Struct\Go type + +``` + err = json.Unmarshal(fileBytes, &videos) + + if err != nil { + panic(err) + } + + return videos +``` +## Using our Data + +Now in the real world, you would use your data to do something.
+Let's say we'd like to update some common terms and conditions to the video description
+ +In our `main` function, let's loop the videos & update the description. +Feel free to checkout Part 1 of the series on `loops`! + +``` + for _, video := range videos { + } +``` + +Now we dont want to override the description, we want to inject the terms and conditions on a new line + +``` + video.Description = video.Description + "\n Remember to Like & Subscribe!" +``` + +Note that when we run this, it does not print our new description field
+This is because the loop `range` is giving us a copy of the video object.
+This means we are updating a copy, but printing out the original.
+We need to use the loop indexer and update the original object in the slice.
+ + +``` + for i, _ := range videos { + videos[i].Description = videos[i].Description + "\n Remember to Like & Subscribe!" + } +``` + +Run our app again and now it updates the original video! + +## Marshal + +https://golang.org/pkg/encoding/json/#Marshal +From Struct\Go type ==> JSON(bytes) + +Let's create a new function for saving our video back to file + +``` +func saveVideos(videos []video)(){ + + videoBytes, err := json.Marshal(videos) + if err != nil { + panic(err) + } + +} + +``` + +## Writing to File + +https://golang.org/pkg/io/ioutil/ + +``` + + err = ioutil.WriteFile("./videos-updated.json", videoBytes, 0644) + if err != nil { + panic(err) + } +``` + +Then in our `main` function we can save our videos after the update: + +``` + saveVideos(videos) +``` + +## Mapping fields + + +In our example our `json` and our struct fields match exactly. +Sometimes this is not possible to maintain. +Sometimes we need to tell go, to map certain `json` fields to certain fields in our `struct` +We can do it like so: + +``` +type video struct { + Id string `json:"id"` + Title string `json:"title"` + Description string `json:"description"` + Imageurl string `json:"imageurl"` + Url string `json:"url"` +} +``` \ No newline at end of file diff --git a/golang/introduction/part-2.json/videos/go.mod b/golang/introduction/part-2.json/videos/go.mod new file mode 100644 index 0000000..5f1d47e --- /dev/null +++ b/golang/introduction/part-2.json/videos/go.mod @@ -0,0 +1,3 @@ +module videos + +go 1.15 diff --git a/golang/introduction/part-2.json/videos/main.go b/golang/introduction/part-2.json/videos/main.go new file mode 100644 index 0000000..f65df9c --- /dev/null +++ b/golang/introduction/part-2.json/videos/main.go @@ -0,0 +1,17 @@ +package main + +import "fmt" + +func main() { + + videos := getVideos() + + for i, _ := range videos { + videos[i].Description = videos[i].Description + "\n Remember to Like & Subscribe!" + } + + fmt.Println(videos) + + saveVideos(videos) + +} \ No newline at end of file diff --git a/golang/introduction/part-2.json/videos/videos-updated.json b/golang/introduction/part-2.json/videos/videos-updated.json new file mode 100644 index 0000000..5d7fa78 --- /dev/null +++ b/golang/introduction/part-2.json/videos/videos-updated.json @@ -0,0 +1,16 @@ +[ + { + "id": "QThadS3Soig", + "title": "Kubernetes on Amazon", + "description": "\n Remember to Like \u0026 Subscribe!", + "imageurl": "https://i.ytimg.com/vi/QThadS3Soig/sddefault.jpg", + "url": "https://youtu.be/QThadS3Soig" + }, + { + "id": "eyvLwK5C2dw", + "title": "Kubernetes on Azure", + "description": "\n Remember to Like \u0026 Subscribe!", + "imageurl": "https://i.ytimg.com/vi/eyvLwK5C2dw/mqdefault.jpg?sqp=CISC_PoF\u0026rs=AOn4CLDo7kizrJozB0pxBhxL9JbyiW_EPw", + "url": "https://youtu.be/eyvLwK5C2dw" + } +] \ No newline at end of file diff --git a/golang/introduction/part-2.json/videos/videos.go b/golang/introduction/part-2.json/videos/videos.go new file mode 100644 index 0000000..17b058a --- /dev/null +++ b/golang/introduction/part-2.json/videos/videos.go @@ -0,0 +1,44 @@ +package main + +import ( + "io/ioutil" + "encoding/json" +) + +type video struct { + Id string `json:"id"` + Title string `json:"title"` + Description string `json:"description"` + Imageurl string `json:"imageurl"` + Url string `json:"url"` +} + +func getVideos()(videos []video){ + + fileBytes, err := ioutil.ReadFile("./videos.json") + + if err != nil { + panic(err) + } + + err = json.Unmarshal(fileBytes, &videos) + + if err != nil { + panic(err) + } + + return videos +} + +func saveVideos(videos []video){ + + videoBytes, err := json.Marshal(videos) + if err != nil { + panic(err) + } + + err = ioutil.WriteFile("./videos-updated.json", videoBytes, 0644) + if err != nil { + panic(err) + } +} \ No newline at end of file diff --git a/golang/introduction/part-2.json/videos/videos.json b/golang/introduction/part-2.json/videos/videos.json new file mode 100644 index 0000000..a623342 --- /dev/null +++ b/golang/introduction/part-2.json/videos/videos.json @@ -0,0 +1,16 @@ +[ + { + "id" : "QThadS3Soig", + "title" : "Kubernetes on Amazon", + "imageurl" : "https://i.ytimg.com/vi/QThadS3Soig/sddefault.jpg", + "url" : "https://youtu.be/QThadS3Soig", + "description" : "" + }, + { + "id" : "eyvLwK5C2dw", + "title" : "Kubernetes on Azure", + "imageurl" : "https://i.ytimg.com/vi/eyvLwK5C2dw/mqdefault.jpg?sqp=CISC_PoF&rs=AOn4CLDo7kizrJozB0pxBhxL9JbyiW_EPw", + "url" : "https://youtu.be/eyvLwK5C2dw", + "description" : "" + } +] \ No newline at end of file diff --git a/golang/introduction/part-3.http/dockerfile b/golang/introduction/part-3.http/dockerfile new file mode 100644 index 0000000..5272ada --- /dev/null +++ b/golang/introduction/part-3.http/dockerfile @@ -0,0 +1,14 @@ +FROM golang:1.15-alpine as dev + +WORKDIR /work + +FROM golang:1.15-alpine as build + +WORKDIR /videos +COPY ./videos/* /videos/ +RUN go build -o videos + +FROM alpine as runtime +COPY --from=build /videos/videos / +COPY ./videos/videos.json / +CMD ./videos \ No newline at end of file diff --git a/golang/introduction/part-3.http/go.mod b/golang/introduction/part-3.http/go.mod new file mode 100644 index 0000000..5f1d47e --- /dev/null +++ b/golang/introduction/part-3.http/go.mod @@ -0,0 +1,3 @@ +module videos + +go 1.15 diff --git a/golang/introduction/part-3.http/readme.md b/golang/introduction/part-3.http/readme.md new file mode 100644 index 0000000..a8cb1f4 --- /dev/null +++ b/golang/introduction/part-3.http/readme.md @@ -0,0 +1,433 @@ +# Introduction to Go: HTTP + +HTTP is a fundamental part of Microservices and Web distributed systems
+ +Go has a built in HTTP web server package. The package can be found [here](https://golang.org/pkg/net/http/)
+We simply have to import the `http` package: + +``` +import ( + "net/http" +) +``` + +[In part 1](../readme.md), we covered the fundamentals of writing basic Go
+[In part 2](../part-2.json/readme.md), we've learn how to use basic data structures like `json` so we can send\receive data.
+ +We'll be combining both these techniques so we can serve our `videos` data over a web endpoint. + +As always, let's start with our `dockerfile` , `main.go` and `videos.go` we created in Part 2 + + +## Dev Environment + +The same as Part 1+2, we start with a [dockerfile](./dockerfile) where we declare our version of `go`. + +``` +cd golang\introduction\part-3.http + +docker build --target dev . -t go +docker run -it -v ${PWD}:/work go sh +go version +``` + +## Create our App + +Create a new directory that holds defines our `repository` and holds our `module` + +``` +mkdir videos + +``` + +* Define a module path + +``` +# change directory to your application source code + +cd videos + +# create a go module file + +go mod init videos + +``` + +## Create our base code + +We start out all our applications with a `main.go` defining our `package`, declaring our `import` dependencies
+and our entrypoint `main()` function + +``` +package main + +import "fmt" + +func main() { + fmt.Println("Hello, world.") +} +``` + +## Create our Videos app + +Firstly, we create a seperate code file `videos.go` that deals with our YouTube videos
+ +The `videos.go` file defines what a video `struct` looks like, a `getVideos()` function to retrieve
+videos list as a slice and a `saveVideos()` function to save videos to a file locally.
+ +Let's copy the following content from Part 2 and create `videos.go` : + +We want `videos.go` to be part of package main: + +``` +package main + +``` + +We import 2 packages, 1 for reading and writing files, and another for dealing with `json` + +``` +import ( + "io/ioutil" + "encoding/json" +) + +``` + +Then we define what a video `struct` looks like: + +``` + +type video struct { + Id string + Title string + Description string + Imageurl string + Url string +} + +``` + +We have a function for retrieving `video` objects as a list of type `slice` : + +``` + +func getVideos()(videos []video){ + + fileBytes, err := ioutil.ReadFile("./videos.json") + + if err != nil { + panic(err) + } + + err = json.Unmarshal(fileBytes, &videos) + + if err != nil { + panic(err) + } + + return videos +} + +``` + +We also need to copy our `videos.json` file which contains our video data.
+ +And finally, we have a function that accepts a list of type `slice` and stores the videos to a local file + +``` +func saveVideos(videos []video)(){ + + videoBytes, err := json.Marshal(videos) + if err != nil { + panic(err) + } + + err = ioutil.WriteFile("./videos-updated.json", videoBytes, 0644) + if err != nil { + panic(err) + } + +} +``` + +## HTTP Package + +https://golang.org/pkg/net/http/ + +The HTTP package allows us to implement an HTTP client and a server. +A client is a component that makes HTTP calls. +A server is a component that receives or serves HTTP. + +The HTTP package is capable of sending HTTP requests as well as defining a server +for receiving HTTP requests. + +We can use this to run an HTTP server to serve files, or serve data, like an API. + +Let's define a server in `main.go` : + +``` +# just one line :) + +http.ListenAndServe(":8080", nil) + +# ListenAndServe starts an HTTP server with a given address and handler. +# The handler is usually nil, which means to use DefaultServeMux. +# Handle and HandleFunc add handlers to DefaultServeMux + +``` + +Now before we run this, since we're running in Docker, we want to exit the container
+and rerun it, but this time open port `8080` + +``` +docker run -it -p 8080:8080 -v ${PWD}:/work go sh +cd videos + +go run main.go +# you will notice the application pausing +``` + +We should see our server with a 404 on http://localhost:8080/ + +## Handle HTTP requests + +In order to handle requests, we can tell the HTTP service that we want it to run a function
+for the request coming in. + +We can see the `http` package has a `HandleFunc` function: https://golang.org/pkg/net/http/ + +To see this in action, lets create a `Hello()` function: + +``` +func Hello(){ +} +``` + +And tell our `http` service to run it: + +``` +http.HandleFunc("/", Hello) +``` + +We cannot run this yet. As per `http` documentation, our `Hello` function needs to take in some inputs. +`func HandleFunc(pattern string, handler func(ResponseWriter, *Request))` + +Therefore we need to add inputs to our function: + +``` +func Hello(w http.ResponseWriter, r *http.Request){ +} +``` + +This allows us to get the request, its `body`, `headers` and a write where we can send a response. +Run this in the browser and you will notice the 404 goes away, but we now get an empty response. + +## HTTP Response + +Let's write a reponse to the incoming request.
+The response write has a `Write()` function that takes a bunch of bytes.
+We can convert string to bytes by casting a `string` to a `[]byte`
like: +`[]byte("Hello!")`. Let's convert it and write "Hello" to the response: + +``` +w.Write([]byte("Hello!")) +``` + +IF we run this code, we can see "Hello!" in the response body + +## HTTP Headers + +Headers play an important role in HTTP communication.
+Lets access all the headers of the incoming request! +If we look at the Header definition [here](https://golang.org/pkg/net/http), we can see how to access it. +Let's use the `for` loop we learnt in [part 1](../readme.md) + +``` +for i, value := range r.Header { +} +``` + +We learn't from our loop, we have in indexer and a value. +For `i`, we can rename it to header since it represents the header key in the dictionary. +And the `value` is the value of type `[]string`, containing the value of the header: + +``` +for header, value := range r.Header { + fmt.Printf("Key: %v \t Value: %v \n", header, value) +} +``` + +We can use `fmt` to print out the values and look at the headers. + +We can also set headers on our response.
+If we take a look at the `http` docs, we can see header is also a dictionary or strings. + +``` +w.Header().Add("TestHeader", "TestValue") +``` + +You can now see the headers in the response value if you use `curl` or your browser development tools + +## HTTP Methods | GET + +Web servers can serve data in a number of ways and support multiple type of HTTP methods. +`GET` is used to request data from a specified resource. +So far, our HTTP route for our Hello function is using the `GET` method. + +Let's make our `GET` method more useful by serving our video data
+Let's rename our `Hello()` function to `HandleGetVideos()`.
+Our `/` route will return all videos: + +``` +videos := getVideos() +``` + +In [part 2](../part-2.json/readme.md) we covered `JSON`. +We need to convert our video `slice` of `struct`'s to `JSON` in order to return it to the client. + +For this we learnt about the Marshall function: + +Import the `JSON` package: +``` +"encoding/json" +``` + +Convert our videos to `JSON` : + +``` +videoBytes, err := json.Marshal(videos) + +if err != nil { + panic(err) +} + +w.Write(videoBytes) + +``` + +If we run this code and hit our `/` endpoint, we can now see `JSON` data being returned.
+This is a core part of building an API in Go.
+ +## HTTP Methods | POST + +A `POST` method is used to send data to a server to create/update a resource.
+Since we built a `saveVideos` function, lets use that so a client can update videos!
+ +We need to define a new route, we can all it `/update` : + +``` +http.HandleFunc("/update", HandleUpdateVideos) +``` + +And we need to define an `HandleUpdateVideos()` function: + +``` +func HandleUpdateVideos(w http.ResponseWriter, r *http.Request){ +} +``` + +Let's validate the request method to ensure its `POST` +We need to also ensure we send a status code to inform the user of method not allowed. + +https://golang.org/pkg/net/http/#ResponseWriter + +``` + if r.Method == "POST" { + //update our videos here! + } else { + w.WriteHeader(405) + fmt.Fprintf(w, "Method not Supported!") + } +``` + +Now we need to accept `JSON` from the `POST` request body +https://golang.org/pkg/net/http/#Request + +In the docs above, we can see the request Body is of type `Body io.ReadCloser` +To read that, we can use the `ioutil` package +https://golang.org/pkg/io/ioutil/ + +``` +import "io/ioutil" +``` + +Then we can read the body into a `slice` of `bytes`: + +``` +body, err := ioutil.ReadAll(r.Body) + +if err != nil { + panic(err) +} +``` + +Now that we have the body in a `[]byte`, we need to use our knowledge from [part 2](../part-2.json/readme.md) where we
+convert `[]byte` to a `slice` of `video` items. + +``` + +var videos []video +err = json.Unmarshal(body, &videos) +if err != nil { + panic(err) +} +``` + +Creating our video objects allows us to do some validation if we wanted to.
+We can ensure the request body adheres to our API contract for this videos API.
+So instead of calling `panic`, lets return a `400` Bad request status code if we cannot
+Unmarshal the `JSON` data. This might help with some basic validation. + +``` +w.WriteHeader(400) +fmt.Fprintf(w, "Bad request") +``` + +And Finally, let's update our videos file! : + +``` +saveVideos(videos) +``` + +# Build our Docker container + +Let's uncomment all the build lines in the `dockerfile` +Full `dockerfile` : + +``` +FROM golang:1.15-alpine as dev + +WORKDIR /work + +FROM golang:1.15-alpine as build + +WORKDIR /videos +COPY ./videos/* /videos/ +RUN go build -o videos + + +FROM alpine as runtime +COPY --from=build /videos/videos / +COPY ./videos/videos.json / +CMD ./videos + +``` + +Build : +``` +cd golang\introduction\part-3.http +docker build . -t videos +``` + +Run : +``` +docker run -it -p 8080:8080 videos +``` + +## Things to know + +* SSL for secure web connection +* Authentication +* Good API validation +* Support a backwards compatible contract (Inputs remain consistent) diff --git a/golang/introduction/part-3.http/videos/main.go b/golang/introduction/part-3.http/videos/main.go new file mode 100644 index 0000000..8f5be8f --- /dev/null +++ b/golang/introduction/part-3.http/videos/main.go @@ -0,0 +1,54 @@ +package main + +import ( + "net/http" + "encoding/json" + "io/ioutil" + "fmt" +) + +func main() { + + http.HandleFunc("/", HandleGetVideos) + http.HandleFunc("/update", HandleUpdateVideos) + + http.ListenAndServe(":8080", nil) +} + + +func HandleGetVideos(w http.ResponseWriter, r *http.Request){ + + videos := getVideos() + + videoBytes, err := json.Marshal(videos) + + if err != nil { + panic(err) + } + + w.Write(videoBytes) +} + +func HandleUpdateVideos(w http.ResponseWriter, r *http.Request){ + + if r.Method == "POST" { + + body, err := ioutil.ReadAll(r.Body) + if err != nil { + panic(err) + } + + var videos []video + err = json.Unmarshal(body, &videos) + if err != nil { + w.WriteHeader(400) + fmt.Fprintf(w, "Bad request") + } + + saveVideos(videos) + + } else { + w.WriteHeader(405) + fmt.Fprintf(w, "Method not Supported!") + } +} \ No newline at end of file diff --git a/golang/introduction/part-3.http/videos/videos-updated.json b/golang/introduction/part-3.http/videos/videos-updated.json new file mode 100644 index 0000000..7c7848f --- /dev/null +++ b/golang/introduction/part-3.http/videos/videos-updated.json @@ -0,0 +1,16 @@ +[ + { + "Id": "QThadS3Soig", + "Title": "Kubernetes on Amazon", + "Description": "TEST", + "Imageurl": "https://i.ytimg.com/vi/QThadS3Soig/sddefault.jpg", + "Url": "https://youtu.be/QThadS3Soig" + }, + { + "Id": "eyvLwK5C2dw", + "Title": "Kubernetes on Azure", + "Description": "TEST", + "Imageurl": "https://i.ytimg.com/vi/eyvLwK5C2dw/mqdefault.jpg?sqp=CISC_PoF\u0026rs=AOn4CLDo7kizrJozB0pxBhxL9JbyiW_EPw", + "Url": "https://youtu.be/eyvLwK5C2dw" + } +] \ No newline at end of file diff --git a/golang/introduction/part-3.http/videos/videos.go b/golang/introduction/part-3.http/videos/videos.go new file mode 100644 index 0000000..23c6356 --- /dev/null +++ b/golang/introduction/part-3.http/videos/videos.go @@ -0,0 +1,46 @@ +package main + +import ( + "io/ioutil" + "encoding/json" +) + +type video struct { + Id string + Title string + Description string + Imageurl string + Url string +} + + +func getVideos()(videos []video){ + + fileBytes, err := ioutil.ReadFile("./videos.json") + + if err != nil { + panic(err) + } + + err = json.Unmarshal(fileBytes, &videos) + + if err != nil { + panic(err) + } + + return videos +} + +func saveVideos(videos []video)(){ + + videoBytes, err := json.Marshal(videos) + if err != nil { + panic(err) + } + + err = ioutil.WriteFile("./videos-updated.json", videoBytes, 0644) + if err != nil { + panic(err) + } + +} \ No newline at end of file diff --git a/golang/introduction/part-3.http/videos/videos.json b/golang/introduction/part-3.http/videos/videos.json new file mode 100644 index 0000000..a623342 --- /dev/null +++ b/golang/introduction/part-3.http/videos/videos.json @@ -0,0 +1,16 @@ +[ + { + "id" : "QThadS3Soig", + "title" : "Kubernetes on Amazon", + "imageurl" : "https://i.ytimg.com/vi/QThadS3Soig/sddefault.jpg", + "url" : "https://youtu.be/QThadS3Soig", + "description" : "" + }, + { + "id" : "eyvLwK5C2dw", + "title" : "Kubernetes on Azure", + "imageurl" : "https://i.ytimg.com/vi/eyvLwK5C2dw/mqdefault.jpg?sqp=CISC_PoF&rs=AOn4CLDo7kizrJozB0pxBhxL9JbyiW_EPw", + "url" : "https://youtu.be/eyvLwK5C2dw", + "description" : "" + } +] \ No newline at end of file diff --git a/golang/introduction/readme.md b/golang/introduction/readme.md index 3261b4a..41bdf9a 100644 --- a/golang/introduction/readme.md +++ b/golang/introduction/readme.md @@ -213,7 +213,7 @@ fmt.Println(customer) ## Arrays At the moment, we can only return 1 customer at a time on our function.
-Realisticly we need the ability to return more data, not just a single customer.
+Realistically we need the ability to return more data, not just a single customer.
Arrays allow us to make a collection of variables of the same type.
We can now return a list of customers!