├── .gitignore ├── LICENSE ├── README.md ├── schema.sql ├── todo.yml └── todo ├── go.mod ├── go.sum └── handler.go /.gitignore: -------------------------------------------------------------------------------- 1 | template 2 | build 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Alex Ellis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kubernetes TODO API written in Go 2 | 3 | Follow along with the blog post to build and deploy a todo app from scratch in Go - deployed to Kubernetes. 4 | 5 | ![Intro image](https://miro.medium.com/max/1400/1*WKKH0QyfhEAM2ImvQCQZUg.jpeg) 6 | 7 | > This is post is aimed at newcomers to Kubernetes who want to work through a practical example of how to write a Golang API for managing your todo list and then how to deploy it to Kubernetes. 8 | 9 | * [Building a TODO API in Golang with Kubernetes](https://levelup.gitconnected.com/building-a-todo-api-in-golang-with-kubernetes-1ec593f85029) 10 | 11 | ## Built for developers by developers 12 | 13 | 14 | Sponsor this project 15 | 16 | 17 | License: MIT 18 | -------------------------------------------------------------------------------- /schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE todo ( 2 | id INT GENERATED ALWAYS AS IDENTITY, 3 | description text NOT NULL, 4 | created_date timestamp NOT NULL, 5 | completed_date timestamp NULL 6 | ); -------------------------------------------------------------------------------- /todo.yml: -------------------------------------------------------------------------------- 1 | version: 1.0 2 | provider: 3 | name: openfaas 4 | gateway: http://127.0.0.1:8080 5 | functions: 6 | todo: 7 | lang: golang-middleware 8 | handler: ./todo 9 | image: alexellis2/todo:latest 10 | secrets: 11 | - host 12 | - password 13 | - username 14 | environment: 15 | postgres_db: postgres 16 | postgres_sslmode: "disable" 17 | postgres_port: 5432 18 | -------------------------------------------------------------------------------- /todo/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/alexellis/todo1/todo 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/alexellis/hmac v0.0.0-20180624211220-5c52ab81c0de // indirect 7 | github.com/lib/pq v1.3.0 8 | github.com/openfaas/faas-provider v0.15.0 // indirect 9 | github.com/openfaas/openfaas-cloud v0.0.0-20200319114858-76ce15eb291a 10 | ) 11 | -------------------------------------------------------------------------------- /todo/go.sum: -------------------------------------------------------------------------------- 1 | github.com/alexellis/hmac v0.0.0-20180624211220-5c52ab81c0de h1:jiPEvtW8VT0KwJxRyjW2VAAvlssjj9SfecsQ3Vgv5tk= 2 | github.com/alexellis/hmac v0.0.0-20180624211220-5c52ab81c0de/go.mod h1:uAbpy8G7sjNB4qYdY6ymf5OIQ+TLDPApBYiR0Vc3lhk= 3 | github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= 4 | github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= 5 | github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU= 6 | github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 7 | github.com/openfaas/faas-provider v0.15.0 h1:3x5ma90FL7AqP4NOD6f03AY24y3xBeVF6xGLUx6Xrlc= 8 | github.com/openfaas/faas-provider v0.15.0/go.mod h1:8Fagi2UeMfL+gZAqZWSMQg86i+w1+hBOKtwKRP5sLFI= 9 | github.com/openfaas/openfaas-cloud v0.0.0-20200319114858-76ce15eb291a h1:UDtx6lgS7/J7cClTLkauPzip8K+qfNMVtaMwNCu9m5M= 10 | github.com/openfaas/openfaas-cloud v0.0.0-20200319114858-76ce15eb291a/go.mod h1:rzuJzd08m8hXz8xQ/CtVdiB8UYhDIroaJCJzGthBzME= 11 | go.uber.org/goleak v0.10.0/go.mod h1:VCZuO8V8mFPlL0F5J5GK1rtHV3DrFcQ1R8ryq7FK0aI= 12 | -------------------------------------------------------------------------------- /todo/handler.go: -------------------------------------------------------------------------------- 1 | package function 2 | 3 | import ( 4 | "database/sql" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "net/http" 10 | "os" 11 | "time" 12 | 13 | _ "github.com/lib/pq" 14 | "github.com/openfaas/openfaas-cloud/sdk" 15 | "github.com/pkg/errors" 16 | ) 17 | 18 | var db *sql.DB 19 | 20 | // init establishes a persistent connection to the remote database 21 | // the function will panic if it cannot establish a link and the 22 | // container will restart / go into a crash/back-off loop 23 | func init() { 24 | if _, err := os.Stat("/var/openfaas/secrets/password"); err == nil { 25 | password, _ := sdk.ReadSecret("password") 26 | user, _ := sdk.ReadSecret("username") 27 | host, _ := sdk.ReadSecret("host") 28 | dbName := os.Getenv("postgres_db") 29 | port := os.Getenv("postgres_port") 30 | sslmode := os.Getenv("postgres_sslmode") 31 | connStr := "postgres://" + user + ":" + password + "@" + host + ":" + port + "/" + dbName + "?sslmode=" + sslmode 32 | var err error 33 | db, err = sql.Open("postgres", connStr) 34 | if err != nil { 35 | panic(err.Error()) 36 | } 37 | err = db.Ping() 38 | if err != nil { 39 | panic(err.Error()) 40 | } 41 | } 42 | } 43 | 44 | type Todo struct { 45 | ID int `json:"id"` 46 | Description string `json:"description"` 47 | CreatedDate *time.Time `json:"created_date"` 48 | CompletedDate *time.Time `json:"completed_date,omitempty"` 49 | } 50 | 51 | func Handle(w http.ResponseWriter, r *http.Request) { 52 | if r.Method == http.MethodPost && r.URL.Path == "/create" { 53 | defer r.Body.Close() 54 | body, _ := ioutil.ReadAll(r.Body) 55 | 56 | if err := insert(string(body)); err != nil { 57 | http.Error(w, fmt.Sprintf("unable to insert todo: %s", err.Error()), http.StatusInternalServerError) 58 | return 59 | } 60 | 61 | } else if r.Method == http.MethodGet && r.URL.Path == "/list" { 62 | todos, err := selectTodos() 63 | 64 | if err != nil { 65 | http.Error(w, fmt.Sprintf("unable to get todos: %s", err.Error()), http.StatusInternalServerError) 66 | return 67 | } 68 | 69 | out, err := json.Marshal(todos) 70 | if err != nil { 71 | http.Error(w, fmt.Sprintf("unable to marshal todos: %s", err.Error()), http.StatusInternalServerError) 72 | return 73 | } 74 | 75 | w.Header().Set("Content-Type", "application/json") 76 | w.Write(out) 77 | } 78 | } 79 | 80 | func insert(description string) error { 81 | res, err := db.Query(`insert into todo (id, description, created_date) values (DEFAULT, $1, now());`, 82 | description) 83 | 84 | if err != nil { 85 | return err 86 | } 87 | 88 | defer res.Close() 89 | return nil 90 | } 91 | 92 | func selectTodos() ([]Todo, error) { 93 | rows, getErr := db.Query(`select id, description, created_date, completed_date from todo;`) 94 | 95 | if getErr != nil { 96 | return []Todo{}, errors.Wrap(getErr, "unable to get from todo table") 97 | } 98 | 99 | todos := []Todo{} 100 | defer rows.Close() 101 | for rows.Next() { 102 | result := Todo{} 103 | scanErr := rows.Scan(&result.ID, &result.Description, &result.CreatedDate, &result.CompletedDate) 104 | if scanErr != nil { 105 | log.Println("scan err:", scanErr) 106 | } 107 | todos = append(todos, result) 108 | } 109 | 110 | return todos, nil 111 | } 112 | --------------------------------------------------------------------------------